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