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