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\n", 162 argv[0]); 163 else 164 printf("Successfully activated boot environment %s\n", argv[0]); 165 166 if (temp) 167 printf("for next boot\n"); 168 169 return (err); 170 } 171 172 173 /* 174 * TODO: when only one arg is given, and it contains an "@" the this should 175 * create that snapshot 176 */ 177 static int 178 bectl_cmd_create(int argc, char *argv[]) 179 { 180 char snapshot[BE_MAXPATHLEN]; 181 char *atpos, *bootenv, *snapname; 182 int err, opt; 183 bool recursive; 184 185 snapname = NULL; 186 recursive = false; 187 while ((opt = getopt(argc, argv, "e:r")) != -1) { 188 switch (opt) { 189 case 'e': 190 snapname = optarg; 191 break; 192 case 'r': 193 recursive = true; 194 break; 195 default: 196 fprintf(stderr, "bectl create: unknown option '-%c'\n", 197 optopt); 198 return (usage(false)); 199 } 200 } 201 202 argc -= optind; 203 argv += optind; 204 205 if (argc != 1) { 206 fprintf(stderr, "bectl create: wrong number of arguments\n"); 207 return (usage(false)); 208 } 209 210 bootenv = *argv; 211 212 err = BE_ERR_SUCCESS; 213 if ((atpos = strchr(bootenv, '@')) != NULL) { 214 /* 215 * This is the "create a snapshot variant". No new boot 216 * environment is to be created here. 217 */ 218 *atpos++ = '\0'; 219 err = be_snapshot(be, bootenv, atpos, recursive, NULL); 220 } else { 221 if (snapname == NULL) 222 /* Create from currently booted BE */ 223 err = be_snapshot(be, be_active_path(be), NULL, 224 recursive, snapshot); 225 else if (strchr(snapname, '@') != NULL) 226 /* Create from given snapshot */ 227 strlcpy(snapshot, snapname, sizeof(snapshot)); 228 else 229 /* Create from given BE */ 230 err = be_snapshot(be, snapname, NULL, recursive, 231 snapshot); 232 233 if (err == BE_ERR_SUCCESS) 234 err = be_create_depth(be, bootenv, snapshot, 235 recursive == true ? -1 : 0); 236 } 237 238 switch (err) { 239 case BE_ERR_SUCCESS: 240 break; 241 case BE_ERR_INVALIDNAME: 242 fprintf(stderr, 243 "bectl create: boot environment name must not contain spaces\n"); 244 break; 245 default: 246 if (atpos != NULL) 247 fprintf(stderr, 248 "Failed to create a snapshot '%s' of '%s'\n", 249 atpos, bootenv); 250 else if (snapname == NULL) 251 fprintf(stderr, 252 "Failed to create bootenv %s\n", bootenv); 253 else 254 fprintf(stderr, 255 "Failed to create bootenv %s from snapshot %s\n", 256 bootenv, snapname); 257 } 258 259 return (err); 260 } 261 262 263 static int 264 bectl_cmd_export(int argc, char *argv[]) 265 { 266 char *bootenv; 267 268 if (argc == 1) { 269 fprintf(stderr, "bectl export: missing boot environment name\n"); 270 return (usage(false)); 271 } 272 273 if (argc > 2) { 274 fprintf(stderr, "bectl export: extra arguments provided\n"); 275 return (usage(false)); 276 } 277 278 bootenv = argv[1]; 279 280 if (isatty(STDOUT_FILENO)) { 281 fprintf(stderr, "bectl export: must redirect output\n"); 282 return (EX_USAGE); 283 } 284 285 be_export(be, bootenv, STDOUT_FILENO); 286 287 return (0); 288 } 289 290 291 static int 292 bectl_cmd_import(int argc, char *argv[]) 293 { 294 char *bootenv; 295 int err; 296 297 if (argc == 1) { 298 fprintf(stderr, "bectl import: missing boot environment name\n"); 299 return (usage(false)); 300 } 301 302 if (argc > 2) { 303 fprintf(stderr, "bectl import: extra arguments provided\n"); 304 return (usage(false)); 305 } 306 307 bootenv = argv[1]; 308 309 if (isatty(STDIN_FILENO)) { 310 fprintf(stderr, "bectl import: input can not be from terminal\n"); 311 return (EX_USAGE); 312 } 313 314 err = be_import(be, bootenv, STDIN_FILENO); 315 316 return (err); 317 } 318 319 #if SOON 320 static int 321 bectl_cmd_add(int argc, char *argv[]) 322 { 323 324 if (argc < 2) { 325 fprintf(stderr, "bectl add: must provide at least one path\n"); 326 return (usage(false)); 327 } 328 329 for (int i = 1; i < argc; ++i) { 330 printf("arg %d: %s\n", i, argv[i]); 331 /* XXX TODO catch err */ 332 be_add_child(be, argv[i], true); 333 } 334 335 return (0); 336 } 337 #endif 338 339 static int 340 bectl_cmd_destroy(int argc, char *argv[]) 341 { 342 nvlist_t *props; 343 char *target, targetds[BE_MAXPATHLEN]; 344 const char *origin; 345 int err, flags, opt; 346 347 flags = 0; 348 while ((opt = getopt(argc, argv, "Fo")) != -1) { 349 switch (opt) { 350 case 'F': 351 flags |= BE_DESTROY_FORCE; 352 break; 353 case 'o': 354 flags |= BE_DESTROY_ORIGIN; 355 break; 356 default: 357 fprintf(stderr, "bectl destroy: unknown option '-%c'\n", 358 optopt); 359 return (usage(false)); 360 } 361 } 362 363 argc -= optind; 364 argv += optind; 365 366 if (argc != 1) { 367 fprintf(stderr, "bectl destroy: wrong number of arguments\n"); 368 return (usage(false)); 369 } 370 371 target = argv[0]; 372 373 /* We'll emit a notice if there's an origin to be cleaned up */ 374 if ((flags & BE_DESTROY_ORIGIN) == 0 && strchr(target, '@') == NULL) { 375 flags |= BE_DESTROY_AUTOORIGIN; 376 if (be_root_concat(be, target, targetds) != 0) 377 goto destroy; 378 if (be_prop_list_alloc(&props) != 0) 379 goto destroy; 380 if (be_get_dataset_props(be, targetds, props) != 0) { 381 be_prop_list_free(props); 382 goto destroy; 383 } 384 if (nvlist_lookup_string(props, "origin", &origin) == 0 && 385 !be_is_auto_snapshot_name(be, origin)) 386 fprintf(stderr, "bectl destroy: leaving origin '%s' intact\n", 387 origin); 388 be_prop_list_free(props); 389 } 390 391 destroy: 392 err = be_destroy(be, target, flags); 393 394 return (err); 395 } 396 397 static int 398 bectl_cmd_mount(int argc, char *argv[]) 399 { 400 char result_loc[BE_MAXPATHLEN]; 401 char *bootenv, *mountpoint; 402 int err, mntflags; 403 404 /* XXX TODO: Allow shallow */ 405 mntflags = BE_MNT_DEEP; 406 if (argc < 2) { 407 fprintf(stderr, "bectl mount: missing argument(s)\n"); 408 return (usage(false)); 409 } 410 411 if (argc > 3) { 412 fprintf(stderr, "bectl mount: too many arguments\n"); 413 return (usage(false)); 414 } 415 416 bootenv = argv[1]; 417 mountpoint = ((argc == 3) ? argv[2] : NULL); 418 419 err = be_mount(be, bootenv, mountpoint, mntflags, result_loc); 420 421 switch (err) { 422 case BE_ERR_SUCCESS: 423 printf("%s\n", result_loc); 424 break; 425 default: 426 fprintf(stderr, 427 (argc == 3) ? "Failed to mount bootenv %s at %s\n" : 428 "Failed to mount bootenv %s at temporary path %s\n", 429 bootenv, mountpoint); 430 } 431 432 return (err); 433 } 434 435 436 static int 437 bectl_cmd_rename(int argc, char *argv[]) 438 { 439 char *dest, *src; 440 int err; 441 442 if (argc < 3) { 443 fprintf(stderr, "bectl rename: missing argument\n"); 444 return (usage(false)); 445 } 446 447 if (argc > 3) { 448 fprintf(stderr, "bectl rename: too many arguments\n"); 449 return (usage(false)); 450 } 451 452 src = argv[1]; 453 dest = argv[2]; 454 455 err = be_rename(be, src, dest); 456 switch (err) { 457 case BE_ERR_SUCCESS: 458 break; 459 default: 460 fprintf(stderr, "Failed to rename bootenv %s to %s\n", 461 src, dest); 462 } 463 464 return (err); 465 } 466 467 static int 468 bectl_cmd_unmount(int argc, char *argv[]) 469 { 470 char *bootenv, *cmd; 471 int err, flags, opt; 472 473 /* Store alias used */ 474 cmd = argv[0]; 475 476 flags = 0; 477 while ((opt = getopt(argc, argv, "f")) != -1) { 478 switch (opt) { 479 case 'f': 480 flags |= BE_MNT_FORCE; 481 break; 482 default: 483 fprintf(stderr, "bectl %s: unknown option '-%c'\n", 484 cmd, optopt); 485 return (usage(false)); 486 } 487 } 488 489 argc -= optind; 490 argv += optind; 491 492 if (argc != 1) { 493 fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd); 494 return (usage(false)); 495 } 496 497 bootenv = argv[0]; 498 499 err = be_unmount(be, bootenv, flags); 500 501 switch (err) { 502 case BE_ERR_SUCCESS: 503 break; 504 default: 505 fprintf(stderr, "Failed to unmount bootenv %s\n", bootenv); 506 } 507 508 return (err); 509 } 510 511 static int 512 bectl_cmd_check(int argc, char *argv[] __unused) 513 { 514 515 /* The command is left as argv[0] */ 516 if (argc != 1) { 517 fprintf(stderr, "bectl check: wrong number of arguments\n"); 518 return (usage(false)); 519 } 520 521 return (0); 522 } 523 524 int 525 main(int argc, char *argv[]) 526 { 527 struct command_map_entry *cmd; 528 const char *command; 529 char *root = NULL; 530 int opt, rc; 531 532 while ((opt = getopt(argc, argv, "hr:")) != -1) { 533 switch (opt) { 534 case 'h': 535 exit(usage(true)); 536 case 'r': 537 root = strdup(optarg); 538 break; 539 default: 540 exit(usage(false)); 541 } 542 } 543 544 argc -= optind; 545 argv += optind; 546 547 if (argc == 0) 548 exit(usage(false)); 549 550 command = *argv; 551 optreset = 1; 552 optind = 1; 553 554 if ((cmd = get_cmd_info(command)) == NULL) { 555 fprintf(stderr, "Unknown command: %s\n", command); 556 return (usage(false)); 557 } 558 559 if ((be = libbe_init(root)) == NULL) { 560 if (!cmd->silent) 561 fprintf(stderr, "libbe_init(\"%s\") failed.\n", 562 root != NULL ? root : ""); 563 return (-1); 564 } 565 566 libbe_print_on_error(be, !cmd->silent); 567 568 rc = cmd->fn(argc, argv); 569 570 libbe_close(be); 571 return (rc); 572 } 573