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