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