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