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 { "ujail", bectl_cmd_unjail, false }, 118 { "unmount", bectl_cmd_unmount, false }, 119 { "umount", bectl_cmd_unmount, false }, 120 { "check", bectl_cmd_check, true }, 121 }; 122 123 static struct command_map_entry * 124 get_cmd_info(const char *cmd) 125 { 126 size_t i; 127 128 for (i = 0; i < nitems(command_map); ++i) { 129 if (strcmp(cmd, command_map[i].command) == 0) 130 return (&command_map[i]); 131 } 132 133 return (NULL); 134 } 135 136 static int 137 bectl_cmd_activate(int argc, char *argv[]) 138 { 139 int err, opt; 140 bool temp, reset; 141 142 temp = false; 143 reset = false; 144 while ((opt = getopt(argc, argv, "tT")) != -1) { 145 switch (opt) { 146 case 't': 147 if (reset) 148 return (usage(false)); 149 temp = true; 150 break; 151 case 'T': 152 if (temp) 153 return (usage(false)); 154 reset = true; 155 break; 156 default: 157 fprintf(stderr, "bectl activate: unknown option '-%c'\n", 158 optopt); 159 return (usage(false)); 160 } 161 } 162 163 argc -= optind; 164 argv += optind; 165 166 if (argc != 1 && (!reset || argc != 0)) { 167 fprintf(stderr, "bectl activate: wrong number of arguments\n"); 168 return (usage(false)); 169 } 170 171 if (reset) { 172 if ((err = be_deactivate(be, NULL, reset)) == 0) 173 printf("Temporary activation removed\n"); 174 else 175 printf("Failed to remove temporary activation\n"); 176 return (err); 177 } 178 179 /* activate logic goes here */ 180 if ((err = be_activate(be, argv[0], temp)) != 0) 181 /* XXX TODO: more specific error msg based on err */ 182 printf("Did not successfully activate boot environment %s\n", 183 argv[0]); 184 else 185 printf("Successfully activated boot environment %s\n", argv[0]); 186 187 if (temp) 188 printf("for next boot\n"); 189 190 return (err); 191 } 192 193 194 /* 195 * TODO: when only one arg is given, and it contains an "@" the this should 196 * create that snapshot 197 */ 198 static int 199 bectl_cmd_create(int argc, char *argv[]) 200 { 201 char snapshot[BE_MAXPATHLEN]; 202 char *atpos, *bootenv, *snapname; 203 int err, opt; 204 bool recursive; 205 206 snapname = NULL; 207 recursive = false; 208 while ((opt = getopt(argc, argv, "e:r")) != -1) { 209 switch (opt) { 210 case 'e': 211 snapname = optarg; 212 break; 213 case 'r': 214 recursive = true; 215 break; 216 default: 217 fprintf(stderr, "bectl create: unknown option '-%c'\n", 218 optopt); 219 return (usage(false)); 220 } 221 } 222 223 argc -= optind; 224 argv += optind; 225 226 if (argc != 1) { 227 fprintf(stderr, "bectl create: wrong number of arguments\n"); 228 return (usage(false)); 229 } 230 231 bootenv = *argv; 232 233 err = BE_ERR_SUCCESS; 234 if ((atpos = strchr(bootenv, '@')) != NULL) { 235 /* 236 * This is the "create a snapshot variant". No new boot 237 * environment is to be created here. 238 */ 239 *atpos++ = '\0'; 240 err = be_snapshot(be, bootenv, atpos, recursive, NULL); 241 } else { 242 if (snapname == NULL) 243 /* Create from currently booted BE */ 244 err = be_snapshot(be, be_active_path(be), NULL, 245 recursive, snapshot); 246 else if (strchr(snapname, '@') != NULL) 247 /* Create from given snapshot */ 248 strlcpy(snapshot, snapname, sizeof(snapshot)); 249 else 250 /* Create from given BE */ 251 err = be_snapshot(be, snapname, NULL, recursive, 252 snapshot); 253 254 if (err == BE_ERR_SUCCESS) 255 err = be_create_depth(be, bootenv, snapshot, 256 recursive == true ? -1 : 0); 257 } 258 259 switch (err) { 260 case BE_ERR_SUCCESS: 261 break; 262 case BE_ERR_INVALIDNAME: 263 fprintf(stderr, 264 "bectl create: boot environment name must not contain spaces\n"); 265 break; 266 default: 267 if (atpos != NULL) 268 fprintf(stderr, 269 "Failed to create a snapshot '%s' of '%s'\n", 270 atpos, bootenv); 271 else if (snapname == NULL) 272 fprintf(stderr, 273 "Failed to create bootenv %s\n", bootenv); 274 else 275 fprintf(stderr, 276 "Failed to create bootenv %s from snapshot %s\n", 277 bootenv, snapname); 278 } 279 280 return (err); 281 } 282 283 284 static int 285 bectl_cmd_export(int argc, char *argv[]) 286 { 287 char *bootenv; 288 289 if (argc == 1) { 290 fprintf(stderr, "bectl export: missing boot environment name\n"); 291 return (usage(false)); 292 } 293 294 if (argc > 2) { 295 fprintf(stderr, "bectl export: extra arguments provided\n"); 296 return (usage(false)); 297 } 298 299 bootenv = argv[1]; 300 301 if (isatty(STDOUT_FILENO)) { 302 fprintf(stderr, "bectl export: must redirect output\n"); 303 return (EX_USAGE); 304 } 305 306 be_export(be, bootenv, STDOUT_FILENO); 307 308 return (0); 309 } 310 311 312 static int 313 bectl_cmd_import(int argc, char *argv[]) 314 { 315 char *bootenv; 316 int err; 317 318 if (argc == 1) { 319 fprintf(stderr, "bectl import: missing boot environment name\n"); 320 return (usage(false)); 321 } 322 323 if (argc > 2) { 324 fprintf(stderr, "bectl import: extra arguments provided\n"); 325 return (usage(false)); 326 } 327 328 bootenv = argv[1]; 329 330 if (isatty(STDIN_FILENO)) { 331 fprintf(stderr, "bectl import: input can not be from terminal\n"); 332 return (EX_USAGE); 333 } 334 335 err = be_import(be, bootenv, STDIN_FILENO); 336 337 return (err); 338 } 339 340 #if SOON 341 static int 342 bectl_cmd_add(int argc, char *argv[]) 343 { 344 345 if (argc < 2) { 346 fprintf(stderr, "bectl add: must provide at least one path\n"); 347 return (usage(false)); 348 } 349 350 for (int i = 1; i < argc; ++i) { 351 printf("arg %d: %s\n", i, argv[i]); 352 /* XXX TODO catch err */ 353 be_add_child(be, argv[i], true); 354 } 355 356 return (0); 357 } 358 #endif 359 360 static int 361 bectl_cmd_destroy(int argc, char *argv[]) 362 { 363 nvlist_t *props; 364 char *target, targetds[BE_MAXPATHLEN]; 365 const char *origin; 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("%s\n", 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 switch (err) { 478 case BE_ERR_SUCCESS: 479 break; 480 default: 481 fprintf(stderr, "Failed to rename bootenv %s to %s\n", 482 src, dest); 483 } 484 485 return (err); 486 } 487 488 static int 489 bectl_cmd_unmount(int argc, char *argv[]) 490 { 491 char *bootenv, *cmd; 492 int err, flags, opt; 493 494 /* Store alias used */ 495 cmd = argv[0]; 496 497 flags = 0; 498 while ((opt = getopt(argc, argv, "f")) != -1) { 499 switch (opt) { 500 case 'f': 501 flags |= BE_MNT_FORCE; 502 break; 503 default: 504 fprintf(stderr, "bectl %s: unknown option '-%c'\n", 505 cmd, optopt); 506 return (usage(false)); 507 } 508 } 509 510 argc -= optind; 511 argv += optind; 512 513 if (argc != 1) { 514 fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd); 515 return (usage(false)); 516 } 517 518 bootenv = argv[0]; 519 520 err = be_unmount(be, bootenv, flags); 521 522 switch (err) { 523 case BE_ERR_SUCCESS: 524 break; 525 default: 526 fprintf(stderr, "Failed to unmount bootenv %s\n", bootenv); 527 } 528 529 return (err); 530 } 531 532 static int 533 bectl_cmd_check(int argc, char *argv[] __unused) 534 { 535 536 /* The command is left as argv[0] */ 537 if (argc != 1) { 538 fprintf(stderr, "bectl check: wrong number of arguments\n"); 539 return (usage(false)); 540 } 541 542 return (0); 543 } 544 545 int 546 main(int argc, char *argv[]) 547 { 548 struct command_map_entry *cmd; 549 const char *command; 550 char *root = NULL; 551 int opt, rc; 552 553 while ((opt = getopt(argc, argv, "hr:")) != -1) { 554 switch (opt) { 555 case 'h': 556 exit(usage(true)); 557 case 'r': 558 root = strdup(optarg); 559 break; 560 default: 561 exit(usage(false)); 562 } 563 } 564 565 argc -= optind; 566 argv += optind; 567 568 if (argc == 0) 569 exit(usage(false)); 570 571 command = *argv; 572 optreset = 1; 573 optind = 1; 574 575 if ((cmd = get_cmd_info(command)) == NULL) { 576 fprintf(stderr, "Unknown command: %s\n", command); 577 return (usage(false)); 578 } 579 580 if ((be = libbe_init(root)) == NULL) { 581 if (!cmd->silent) 582 fprintf(stderr, "libbe_init(\"%s\") failed.\n", 583 root != NULL ? root : ""); 584 return (-1); 585 } 586 587 libbe_print_on_error(be, !cmd->silent); 588 589 rc = cmd->fn(argc, argv); 590 591 libbe_close(be); 592 return (rc); 593 } 594