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 48static int bectl_cmd_activate(int argc, char *argv[]); 49static int bectl_cmd_check(int argc, char *argv[]); 50static int bectl_cmd_create(int argc, char *argv[]); 51static int bectl_cmd_destroy(int argc, char *argv[]); 52static int bectl_cmd_export(int argc, char *argv[]); 53static int bectl_cmd_import(int argc, char *argv[]); 54#if SOON 55static int bectl_cmd_add(int argc, char *argv[]); 56#endif 57static int bectl_cmd_mount(int argc, char *argv[]); 58static int bectl_cmd_rename(int argc, char *argv[]); 59static int bectl_cmd_unmount(int argc, char *argv[]); 60 61libbe_handle_t *be; 62 63int 64usage(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 [-bU] [{-o key=value | -u key}]... beName\n" 82 "\t [utility [argument ...]]\n" 83 "\tbectl list [-aDHs] [{-c property | -C property}]\n" 84 "\tbectl mount beName [mountpoint]\n" 85 "\tbectl rename origBeName newBeName\n" 86 "\tbectl {ujail | unjail} {jailID | jailName | beName}\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 */ 97struct command_map_entry { 98 const char *command; 99 int (*fn)(int argc, char *argv[]); 100 /* True if libbe_print_on_error should be disabled */ 101 bool silent; 102}; 103 104static struct command_map_entry command_map[] = 105{ 106 { "activate", bectl_cmd_activate,false }, 107 { "create", bectl_cmd_create, false }, 108 { "destroy", bectl_cmd_destroy, false }, 109 { "export", bectl_cmd_export, false }, 110 { "import", bectl_cmd_import, false }, 111#if SOON 112 { "add", bectl_cmd_add, false }, 113#endif 114 { "jail", bectl_cmd_jail, false }, 115 { "list", bectl_cmd_list, false }, 116 { "mount", bectl_cmd_mount, false }, 117 { "rename", bectl_cmd_rename, false }, 118 { "unjail", bectl_cmd_unjail, false }, 119 { "unmount", bectl_cmd_unmount, false }, 120 { "check", bectl_cmd_check, true }, 121}; 122 123static struct command_map_entry * 124get_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 137static int 138bectl_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 */ 184static int 185bectl_cmd_create(int argc, char *argv[]) 186{ 187 char snapshot[BE_MAXPATHLEN]; 188 char *atpos, *bootenv, *snapname; 189 int err, opt; 190 bool recursive; 191 192 snapname = NULL; 193 recursive = false; 194 while ((opt = getopt(argc, argv, "e:r")) != -1) { 195 switch (opt) { 196 case 'e': 197 snapname = optarg; 198 break; 199 case 'r': 200 recursive = true; 201 break; 202 default: 203 fprintf(stderr, "bectl create: unknown option '-%c'\n", 204 optopt); 205 return (usage(false)); 206 } 207 } 208 209 argc -= optind; 210 argv += optind; 211 212 if (argc != 1) { 213 fprintf(stderr, "bectl create: wrong number of arguments\n"); 214 return (usage(false)); 215 } 216 217 bootenv = *argv; 218 219 err = BE_ERR_SUCCESS; 220 if (strchr(bootenv, ' ') != NULL) 221 /* BE datasets with spaces are not bootable */ 222 err = BE_ERR_INVALIDNAME; 223 else 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 case BE_ERR_INVALIDNAME: 252 fprintf(stderr, 253 "bectl create: boot environment name must not contain spaces\n"); 254 break; 255 default: 256 if (atpos != NULL) 257 fprintf(stderr, 258 "Failed to create a snapshot '%s' of '%s'\n", 259 atpos, bootenv); 260 else if (snapname == NULL) 261 fprintf(stderr, 262 "Failed to create bootenv %s\n", bootenv); 263 else 264 fprintf(stderr, 265 "Failed to create bootenv %s from snapshot %s\n", 266 bootenv, snapname); 267 } 268 269 return (err); 270} 271 272 273static int 274bectl_cmd_export(int argc, char *argv[]) 275{ 276 char *bootenv; 277 278 if (argc == 1) { 279 fprintf(stderr, "bectl export: missing boot environment name\n"); 280 return (usage(false)); 281 } 282 283 if (argc > 2) { 284 fprintf(stderr, "bectl export: extra arguments provided\n"); 285 return (usage(false)); 286 } 287 288 bootenv = argv[1]; 289 290 if (isatty(STDOUT_FILENO)) { 291 fprintf(stderr, "bectl export: must redirect output\n"); 292 return (EX_USAGE); 293 } 294 295 be_export(be, bootenv, STDOUT_FILENO); 296 297 return (0); 298} 299 300 301static int 302bectl_cmd_import(int argc, char *argv[]) 303{ 304 char *bootenv; 305 int err; 306 307 if (argc == 1) { 308 fprintf(stderr, "bectl import: missing boot environment name\n"); 309 return (usage(false)); 310 } 311 312 if (argc > 2) { 313 fprintf(stderr, "bectl import: extra arguments provided\n"); 314 return (usage(false)); 315 } 316 317 bootenv = argv[1]; 318 319 if (isatty(STDIN_FILENO)) { 320 fprintf(stderr, "bectl import: input can not be from terminal\n"); 321 return (EX_USAGE); 322 } 323 324 err = be_import(be, bootenv, STDIN_FILENO); 325 326 return (err); 327} 328 329#if SOON 330static int 331bectl_cmd_add(int argc, char *argv[]) 332{ 333 334 if (argc < 2) { 335 fprintf(stderr, "bectl add: must provide at least one path\n"); 336 return (usage(false)); 337 } 338 339 for (int i = 1; i < argc; ++i) { 340 printf("arg %d: %s\n", i, argv[i]); 341 /* XXX TODO catch err */ 342 be_add_child(be, argv[i], true); 343 } 344 345 return (0); 346} 347#endif 348 349static int 350bectl_cmd_destroy(int argc, char *argv[]) 351{ 352 nvlist_t *props; 353 char *origin, *target, targetds[BE_MAXPATHLEN]; 354 int err, flags, opt; 355 356 flags = 0; 357 while ((opt = getopt(argc, argv, "Fo")) != -1) { 358 switch (opt) { 359 case 'F': 360 flags |= BE_DESTROY_FORCE; 361 break; 362 case 'o': 363 flags |= BE_DESTROY_ORIGIN; 364 break; 365 default: 366 fprintf(stderr, "bectl destroy: unknown option '-%c'\n", 367 optopt); 368 return (usage(false)); 369 } 370 } 371 372 argc -= optind; 373 argv += optind; 374 375 if (argc != 1) { 376 fprintf(stderr, "bectl destroy: wrong number of arguments\n"); 377 return (usage(false)); 378 } 379 380 target = argv[0]; 381 382 /* We'll emit a notice if there's an origin to be cleaned up */ 383 if ((flags & BE_DESTROY_ORIGIN) == 0 && strchr(target, '@') == NULL) { 384 flags |= BE_DESTROY_AUTOORIGIN; 385 if (be_root_concat(be, target, targetds) != 0) 386 goto destroy; 387 if (be_prop_list_alloc(&props) != 0) 388 goto destroy; 389 if (be_get_dataset_props(be, targetds, props) != 0) { 390 be_prop_list_free(props); 391 goto destroy; 392 } 393 if (nvlist_lookup_string(props, "origin", &origin) == 0 && 394 !be_is_auto_snapshot_name(be, origin)) 395 fprintf(stderr, "bectl destroy: leaving origin '%s' intact\n", 396 origin); 397 be_prop_list_free(props); 398 } 399 400destroy: 401 err = be_destroy(be, target, flags); 402 403 return (err); 404} 405 406static int 407bectl_cmd_mount(int argc, char *argv[]) 408{ 409 char result_loc[BE_MAXPATHLEN]; 410 char *bootenv, *mountpoint; 411 int err, mntflags; 412 413 /* XXX TODO: Allow shallow */ 414 mntflags = BE_MNT_DEEP; 415 if (argc < 2) { 416 fprintf(stderr, "bectl mount: missing argument(s)\n"); 417 return (usage(false)); 418 } 419 420 if (argc > 3) { 421 fprintf(stderr, "bectl mount: too many arguments\n"); 422 return (usage(false)); 423 } 424 425 bootenv = argv[1]; 426 mountpoint = ((argc == 3) ? argv[2] : NULL); 427 428 err = be_mount(be, bootenv, mountpoint, mntflags, result_loc); 429 430 switch (err) { 431 case BE_ERR_SUCCESS: 432 printf("Successfully mounted %s at %s\n", bootenv, result_loc); 433 break; 434 default: 435 fprintf(stderr, 436 (argc == 3) ? "Failed to mount bootenv %s at %s\n" : 437 "Failed to mount bootenv %s at temporary path %s\n", 438 bootenv, mountpoint); 439 } 440 441 return (err); 442} 443 444 445static int 446bectl_cmd_rename(int argc, char *argv[]) 447{ 448 char *dest, *src; 449 int err; 450 451 if (argc < 3) { 452 fprintf(stderr, "bectl rename: missing argument\n"); 453 return (usage(false)); 454 } 455 456 if (argc > 3) { 457 fprintf(stderr, "bectl rename: too many arguments\n"); 458 return (usage(false)); 459 } 460 461 src = argv[1]; 462 dest = argv[2]; 463 464 err = be_rename(be, src, dest); 465 466 switch (err) { 467 case BE_ERR_SUCCESS: 468 break; 469 default: 470 fprintf(stderr, "Failed to rename bootenv %s to %s\n", 471 src, dest); 472 } 473 474 return (0); 475} 476 477static int 478bectl_cmd_unmount(int argc, char *argv[]) 479{ 480 char *bootenv, *cmd; 481 int err, flags, opt; 482 483 /* Store alias used */ 484 cmd = argv[0]; 485 486 flags = 0; 487 while ((opt = getopt(argc, argv, "f")) != -1) { 488 switch (opt) { 489 case 'f': 490 flags |= BE_MNT_FORCE; 491 break; 492 default: 493 fprintf(stderr, "bectl %s: unknown option '-%c'\n", 494 cmd, optopt); 495 return (usage(false)); 496 } 497 } 498 499 argc -= optind; 500 argv += optind; 501 502 if (argc != 1) { 503 fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd); 504 return (usage(false)); 505 } 506 507 bootenv = argv[0]; 508 509 err = be_unmount(be, bootenv, flags); 510 511 switch (err) { 512 case BE_ERR_SUCCESS: 513 break; 514 default: 515 fprintf(stderr, "Failed to unmount bootenv %s\n", bootenv); 516 } 517 518 return (err); 519} 520 521static int 522bectl_cmd_check(int argc, char *argv[] __unused) 523{ 524 525 /* The command is left as argv[0] */ 526 if (argc != 1) { 527 fprintf(stderr, "bectl check: wrong number of arguments\n"); 528 return (usage(false)); 529 } 530 531 return (0); 532} 533 534int 535main(int argc, char *argv[]) 536{ 537 struct command_map_entry *cmd; 538 const char *command; 539 char *root; 540 int rc; 541 542 cmd = NULL; 543 root = NULL; 544 if (argc < 2) 545 return (usage(false)); 546 547 if (strcmp(argv[1], "-r") == 0) { 548 if (argc < 4) 549 return (usage(false)); 550 root = strdup(argv[2]); 551 command = argv[3]; 552 argc -= 3; 553 argv += 3; 554 } else { 555 command = argv[1]; 556 argc -= 1; 557 argv += 1; 558 } 559 560 /* Handle command aliases */ 561 if (strcmp(command, "umount") == 0) 562 command = "unmount"; 563 564 if (strcmp(command, "ujail") == 0) 565 command = "unjail"; 566 567 if ((strcmp(command, "-?") == 0) || (strcmp(command, "-h") == 0)) 568 return (usage(true)); 569 570 if ((cmd = get_cmd_info(command)) == NULL) { 571 fprintf(stderr, "Unknown command: %s\n", command); 572 return (usage(false)); 573 } 574 575 if ((be = libbe_init(root)) == NULL) 576 return (-1); 577 578 libbe_print_on_error(be, !cmd->silent); 579 580 rc = cmd->fn(argc, argv); 581 582 libbe_close(be); 583 return (rc); 584} 585