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