1 /* 2 * Copyright (c) 2017 Kyle J. Kneitinger <kyle@kneit.in> 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7 #include <sys/param.h> 8 #include <sys/mount.h> 9 #include <errno.h> 10 #include <libutil.h> 11 #include <stdbool.h> 12 #include <stdio.h> 13 #include <stdint.h> 14 #include <stdlib.h> 15 #include <string.h> 16 #include <sysexits.h> 17 #include <time.h> 18 #include <unistd.h> 19 20 #include <be.h> 21 22 #include "bectl.h" 23 24 static int bectl_cmd_activate(int argc, char *argv[]); 25 static int bectl_cmd_check(int argc, char *argv[]); 26 static int bectl_cmd_create(int argc, char *argv[]); 27 static int bectl_cmd_destroy(int argc, char *argv[]); 28 static int bectl_cmd_export(int argc, char *argv[]); 29 static int bectl_cmd_import(int argc, char *argv[]); 30 #if SOON 31 static int bectl_cmd_add(int argc, char *argv[]); 32 #endif 33 static int bectl_cmd_mount(int argc, char *argv[]); 34 static int bectl_cmd_rename(int argc, char *argv[]); 35 static int bectl_cmd_unmount(int argc, char *argv[]); 36 37 libbe_handle_t *be; 38 39 int 40 usage(bool explicit) 41 { 42 FILE *fp; 43 44 fp = explicit ? stdout : stderr; 45 fprintf(fp, "%s", 46 "Usage:\tbectl {-h | subcommand [args...]}\n" 47 #if SOON 48 "\tbectl [-r beroot] add (path)*\n" 49 #endif 50 "\tbectl [-r beroot] activate [-t] beName\n" 51 "\tbectl [-r beroot] activate [-T]\n" 52 "\tbectl [-r beroot] check\n" 53 "\tbectl [-r beroot] create [-r] [-e {nonActiveBe | beName@snapshot}] beName\n" 54 "\tbectl [-r beroot] create [-r] beName@snapshot\n" 55 "\tbectl [-r beroot] destroy [-Fo] {beName | beName@snapshot}\n" 56 "\tbectl [-r beroot] export sourceBe\n" 57 "\tbectl [-r beroot] import targetBe\n" 58 "\tbectl [-r beroot] jail [-bU] [{-o key=value | -u key}]... beName\n" 59 "\t [utility [argument ...]]\n" 60 "\tbectl [-r beroot] list [-aDHs] [{-c property | -C property}]\n" 61 "\tbectl [-r beroot] mount beName [mountpoint]\n" 62 "\tbectl [-r beroot] rename origBeName newBeName\n" 63 "\tbectl [-r beroot] {ujail | unjail} {jailID | jailName | beName}\n" 64 "\tbectl [-r beroot] {umount | unmount} [-f] beName\n"); 65 66 return (explicit ? 0 : EX_USAGE); 67 } 68 69 70 /* 71 * Represents a relationship between the command name and the parser action 72 * that handles it. 73 */ 74 struct command_map_entry { 75 const char *command; 76 int (*fn)(int argc, char *argv[]); 77 /* True if libbe_print_on_error should be disabled */ 78 bool silent; 79 }; 80 81 static struct command_map_entry command_map[] = 82 { 83 { "activate", bectl_cmd_activate,false }, 84 { "create", bectl_cmd_create, false }, 85 { "destroy", bectl_cmd_destroy, false }, 86 { "export", bectl_cmd_export, false }, 87 { "import", bectl_cmd_import, false }, 88 #if SOON 89 { "add", bectl_cmd_add, false }, 90 #endif 91 { "jail", bectl_cmd_jail, false }, 92 { "list", bectl_cmd_list, false }, 93 { "mount", bectl_cmd_mount, false }, 94 { "rename", bectl_cmd_rename, false }, 95 { "unjail", bectl_cmd_unjail, false }, 96 { "ujail", bectl_cmd_unjail, false }, 97 { "unmount", bectl_cmd_unmount, false }, 98 { "umount", bectl_cmd_unmount, false }, 99 { "check", bectl_cmd_check, true }, 100 }; 101 102 static struct command_map_entry * 103 get_cmd_info(const char *cmd) 104 { 105 size_t i; 106 107 for (i = 0; i < nitems(command_map); ++i) { 108 if (strcmp(cmd, command_map[i].command) == 0) 109 return (&command_map[i]); 110 } 111 112 return (NULL); 113 } 114 115 static int 116 bectl_cmd_activate(int argc, char *argv[]) 117 { 118 int err, opt; 119 bool temp, reset; 120 121 temp = false; 122 reset = false; 123 while ((opt = getopt(argc, argv, "tT")) != -1) { 124 switch (opt) { 125 case 't': 126 if (reset) 127 return (usage(false)); 128 temp = true; 129 break; 130 case 'T': 131 if (temp) 132 return (usage(false)); 133 reset = true; 134 break; 135 default: 136 fprintf(stderr, "bectl activate: unknown option '-%c'\n", 137 optopt); 138 return (usage(false)); 139 } 140 } 141 142 argc -= optind; 143 argv += optind; 144 145 if (argc != 1 && (!reset || argc != 0)) { 146 fprintf(stderr, "bectl activate: wrong number of arguments\n"); 147 return (usage(false)); 148 } 149 150 if (reset) { 151 if ((err = be_deactivate(be, NULL, reset)) == 0) 152 printf("Temporary activation removed\n"); 153 else 154 printf("Failed to remove temporary activation\n"); 155 return (err); 156 } 157 158 /* activate logic goes here */ 159 if ((err = be_activate(be, argv[0], temp)) != 0) 160 /* XXX TODO: more specific error msg based on err */ 161 printf("Did not successfully activate boot environment %s", 162 argv[0]); 163 else 164 printf("Successfully activated boot environment %s", argv[0]); 165 166 if (temp) 167 printf(" for next boot"); 168 169 printf("\n"); 170 171 return (err); 172 } 173 174 175 /* 176 * TODO: when only one arg is given, and it contains an "@" the this should 177 * create that snapshot 178 */ 179 static int 180 bectl_cmd_create(int argc, char *argv[]) 181 { 182 char snapshot[BE_MAXPATHLEN]; 183 char *atpos, *bootenv, *snapname; 184 int err, opt; 185 bool recursive; 186 187 snapname = NULL; 188 recursive = false; 189 while ((opt = getopt(argc, argv, "e:r")) != -1) { 190 switch (opt) { 191 case 'e': 192 snapname = optarg; 193 break; 194 case 'r': 195 recursive = true; 196 break; 197 default: 198 fprintf(stderr, "bectl create: unknown option '-%c'\n", 199 optopt); 200 return (usage(false)); 201 } 202 } 203 204 argc -= optind; 205 argv += optind; 206 207 if (argc != 1) { 208 fprintf(stderr, "bectl create: wrong number of arguments\n"); 209 return (usage(false)); 210 } 211 212 bootenv = *argv; 213 214 err = BE_ERR_SUCCESS; 215 if ((atpos = strchr(bootenv, '@')) != NULL) { 216 /* 217 * This is the "create a snapshot variant". No new boot 218 * environment is to be created here. 219 */ 220 *atpos++ = '\0'; 221 err = be_snapshot(be, bootenv, atpos, recursive, NULL); 222 } else { 223 if (snapname == NULL) 224 /* Create from currently booted BE */ 225 err = be_snapshot(be, be_active_path(be), NULL, 226 recursive, snapshot); 227 else if (strchr(snapname, '@') != NULL) 228 /* Create from given snapshot */ 229 strlcpy(snapshot, snapname, sizeof(snapshot)); 230 else 231 /* Create from given BE */ 232 err = be_snapshot(be, snapname, NULL, recursive, 233 snapshot); 234 235 if (err == BE_ERR_SUCCESS) 236 err = be_create_depth(be, bootenv, snapshot, 237 recursive == true ? -1 : 0); 238 } 239 240 switch (err) { 241 case BE_ERR_SUCCESS: 242 break; 243 case BE_ERR_INVALIDNAME: 244 fprintf(stderr, 245 "bectl create: boot environment name must not contain spaces\n"); 246 break; 247 default: 248 if (atpos != NULL) 249 fprintf(stderr, 250 "Failed to create a snapshot '%s' of '%s'\n", 251 atpos, bootenv); 252 else if (snapname == NULL) 253 fprintf(stderr, 254 "Failed to create bootenv %s\n", bootenv); 255 else 256 fprintf(stderr, 257 "Failed to create bootenv %s from snapshot %s\n", 258 bootenv, snapname); 259 } 260 261 return (err); 262 } 263 264 265 static int 266 bectl_cmd_export(int argc, char *argv[]) 267 { 268 char *bootenv; 269 270 if (argc == 1) { 271 fprintf(stderr, "bectl export: missing boot environment name\n"); 272 return (usage(false)); 273 } 274 275 if (argc > 2) { 276 fprintf(stderr, "bectl export: extra arguments provided\n"); 277 return (usage(false)); 278 } 279 280 bootenv = argv[1]; 281 282 if (isatty(STDOUT_FILENO)) { 283 fprintf(stderr, "bectl export: must redirect output\n"); 284 return (EX_USAGE); 285 } 286 287 be_export(be, bootenv, STDOUT_FILENO); 288 289 return (0); 290 } 291 292 293 static int 294 bectl_cmd_import(int argc, char *argv[]) 295 { 296 char *bootenv; 297 int err; 298 299 if (argc == 1) { 300 fprintf(stderr, "bectl import: missing boot environment name\n"); 301 return (usage(false)); 302 } 303 304 if (argc > 2) { 305 fprintf(stderr, "bectl import: extra arguments provided\n"); 306 return (usage(false)); 307 } 308 309 bootenv = argv[1]; 310 311 if (isatty(STDIN_FILENO)) { 312 fprintf(stderr, "bectl import: input can not be from terminal\n"); 313 return (EX_USAGE); 314 } 315 316 err = be_import(be, bootenv, STDIN_FILENO); 317 318 return (err); 319 } 320 321 #if SOON 322 static int 323 bectl_cmd_add(int argc, char *argv[]) 324 { 325 326 if (argc < 2) { 327 fprintf(stderr, "bectl add: must provide at least one path\n"); 328 return (usage(false)); 329 } 330 331 for (int i = 1; i < argc; ++i) { 332 printf("arg %d: %s\n", i, argv[i]); 333 /* XXX TODO catch err */ 334 be_add_child(be, argv[i], true); 335 } 336 337 return (0); 338 } 339 #endif 340 341 static int 342 bectl_cmd_destroy(int argc, char *argv[]) 343 { 344 nvlist_t *props; 345 char *target, targetds[BE_MAXPATHLEN]; 346 const char *origin; 347 int err, flags, opt; 348 349 flags = 0; 350 while ((opt = getopt(argc, argv, "Fo")) != -1) { 351 switch (opt) { 352 case 'F': 353 flags |= BE_DESTROY_FORCE; 354 break; 355 case 'o': 356 flags |= BE_DESTROY_ORIGIN; 357 break; 358 default: 359 fprintf(stderr, "bectl destroy: unknown option '-%c'\n", 360 optopt); 361 return (usage(false)); 362 } 363 } 364 365 argc -= optind; 366 argv += optind; 367 368 if (argc != 1) { 369 fprintf(stderr, "bectl destroy: wrong number of arguments\n"); 370 return (usage(false)); 371 } 372 373 target = argv[0]; 374 375 /* We'll emit a notice if there's an origin to be cleaned up */ 376 if ((flags & BE_DESTROY_ORIGIN) == 0 && strchr(target, '@') == NULL) { 377 flags |= BE_DESTROY_AUTOORIGIN; 378 if (be_root_concat(be, target, targetds) != 0) 379 goto destroy; 380 if (be_prop_list_alloc(&props) != 0) 381 goto destroy; 382 if (be_get_dataset_props(be, targetds, props) != 0) { 383 be_prop_list_free(props); 384 goto destroy; 385 } 386 if (nvlist_lookup_string(props, "origin", &origin) == 0 && 387 !be_is_auto_snapshot_name(be, origin)) 388 fprintf(stderr, "bectl destroy: leaving origin '%s' intact\n", 389 origin); 390 be_prop_list_free(props); 391 } 392 393 destroy: 394 err = be_destroy(be, target, flags); 395 396 return (err); 397 } 398 399 static int 400 bectl_cmd_mount(int argc, char *argv[]) 401 { 402 char result_loc[BE_MAXPATHLEN]; 403 char *bootenv, *mountpoint; 404 int err, mntflags; 405 406 /* XXX TODO: Allow shallow */ 407 mntflags = BE_MNT_DEEP; 408 if (argc < 2) { 409 fprintf(stderr, "bectl mount: missing argument(s)\n"); 410 return (usage(false)); 411 } 412 413 if (argc > 3) { 414 fprintf(stderr, "bectl mount: too many arguments\n"); 415 return (usage(false)); 416 } 417 418 bootenv = argv[1]; 419 mountpoint = ((argc == 3) ? argv[2] : NULL); 420 421 err = be_mount(be, bootenv, mountpoint, mntflags, result_loc); 422 423 switch (err) { 424 case BE_ERR_SUCCESS: 425 printf("%s\n", result_loc); 426 break; 427 default: 428 fprintf(stderr, 429 (argc == 3) ? "Failed to mount bootenv %s at %s\n" : 430 "Failed to mount bootenv %s at temporary path %s\n", 431 bootenv, mountpoint); 432 } 433 434 return (err); 435 } 436 437 438 static int 439 bectl_cmd_rename(int argc, char *argv[]) 440 { 441 char *dest, *src; 442 int err; 443 444 if (argc < 3) { 445 fprintf(stderr, "bectl rename: missing argument\n"); 446 return (usage(false)); 447 } 448 449 if (argc > 3) { 450 fprintf(stderr, "bectl rename: too many arguments\n"); 451 return (usage(false)); 452 } 453 454 src = argv[1]; 455 dest = argv[2]; 456 457 err = be_rename(be, src, dest); 458 switch (err) { 459 case BE_ERR_SUCCESS: 460 break; 461 default: 462 fprintf(stderr, "Failed to rename bootenv %s to %s\n", 463 src, dest); 464 } 465 466 return (err); 467 } 468 469 static int 470 bectl_cmd_unmount(int argc, char *argv[]) 471 { 472 char *bootenv, *cmd; 473 int err, flags, opt; 474 475 /* Store alias used */ 476 cmd = argv[0]; 477 478 flags = 0; 479 while ((opt = getopt(argc, argv, "f")) != -1) { 480 switch (opt) { 481 case 'f': 482 flags |= BE_MNT_FORCE; 483 break; 484 default: 485 fprintf(stderr, "bectl %s: unknown option '-%c'\n", 486 cmd, optopt); 487 return (usage(false)); 488 } 489 } 490 491 argc -= optind; 492 argv += optind; 493 494 if (argc != 1) { 495 fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd); 496 return (usage(false)); 497 } 498 499 bootenv = argv[0]; 500 501 err = be_unmount(be, bootenv, flags); 502 503 switch (err) { 504 case BE_ERR_SUCCESS: 505 break; 506 default: 507 fprintf(stderr, "Failed to unmount bootenv %s\n", bootenv); 508 } 509 510 return (err); 511 } 512 513 static int 514 bectl_cmd_check(int argc, char *argv[] __unused) 515 { 516 517 /* The command is left as argv[0] */ 518 if (argc != 1) { 519 fprintf(stderr, "bectl check: wrong number of arguments\n"); 520 return (usage(false)); 521 } 522 523 return (0); 524 } 525 526 int 527 main(int argc, char *argv[]) 528 { 529 struct command_map_entry *cmd; 530 const char *command; 531 char *root = NULL; 532 int opt, rc; 533 534 while ((opt = getopt(argc, argv, "hr:")) != -1) { 535 switch (opt) { 536 case 'h': 537 exit(usage(true)); 538 case 'r': 539 root = strdup(optarg); 540 break; 541 default: 542 exit(usage(false)); 543 } 544 } 545 546 argc -= optind; 547 argv += optind; 548 549 if (argc == 0) 550 exit(usage(false)); 551 552 command = *argv; 553 optreset = 1; 554 optind = 1; 555 556 if ((cmd = get_cmd_info(command)) == NULL) { 557 fprintf(stderr, "Unknown command: %s\n", command); 558 return (usage(false)); 559 } 560 561 if ((be = libbe_init(root)) == NULL) { 562 if (!cmd->silent) 563 fprintf(stderr, "libbe_init(\"%s\") failed.\n", 564 root != NULL ? root : ""); 565 return (-1); 566 } 567 568 libbe_print_on_error(be, !cmd->silent); 569 570 rc = cmd->fn(argc, argv); 571 572 libbe_close(be); 573 return (rc); 574 } 575