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