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