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