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