bectl.c revision 352348
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: stable/11/sbin/bectl/bectl.c 352348 2019-09-15 02:46:40Z kevans $"); 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 49static int bectl_cmd_activate(int argc, char *argv[]); 50static int bectl_cmd_check(int argc, char *argv[]); 51static int bectl_cmd_create(int argc, char *argv[]); 52static int bectl_cmd_destroy(int argc, char *argv[]); 53static int bectl_cmd_export(int argc, char *argv[]); 54static int bectl_cmd_import(int argc, char *argv[]); 55#if SOON 56static int bectl_cmd_add(int argc, char *argv[]); 57#endif 58static int bectl_cmd_mount(int argc, char *argv[]); 59static int bectl_cmd_rename(int argc, char *argv[]); 60static int bectl_cmd_unmount(int argc, char *argv[]); 61 62libbe_handle_t *be; 63 64int 65usage(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 */ 99struct 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 106static 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 125static struct command_map_entry * 126get_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 139static int 140bectl_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 */ 186static int 187bectl_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 268static int 269bectl_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 296static int 297bectl_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 325static int 326bectl_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 344static int 345bectl_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 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 fprintf(stderr, "bectl destroy: leaving origin '%s' intact\n", 389 origin); 390 be_prop_list_free(props); 391 } 392 393destroy: 394 err = be_destroy(be, target, flags); 395 396 return (err); 397} 398 399static int 400bectl_cmd_mount(int argc, char *argv[]) 401{ 402 char result_loc[BE_MAXPATHLEN]; 403 char *bootenv, *mountpoint; 404 int err, mntflags; 405 406 /* XXX TODO: Allow shallow */ 407 mntflags = BE_MNT_DEEP; 408 if (argc < 2) { 409 fprintf(stderr, "bectl mount: missing argument(s)\n"); 410 return (usage(false)); 411 } 412 413 if (argc > 3) { 414 fprintf(stderr, "bectl mount: too many arguments\n"); 415 return (usage(false)); 416 } 417 418 bootenv = argv[1]; 419 mountpoint = ((argc == 3) ? argv[2] : NULL); 420 421 err = be_mount(be, bootenv, mountpoint, mntflags, result_loc); 422 423 switch (err) { 424 case BE_ERR_SUCCESS: 425 printf("successfully mounted %s at %s\n", bootenv, result_loc); 426 break; 427 default: 428 fprintf(stderr, 429 (argc == 3) ? "failed to mount bootenv %s at %s\n" : 430 "failed to mount bootenv %s at temporary path %s\n", 431 bootenv, mountpoint); 432 } 433 434 return (err); 435} 436 437 438static int 439bectl_cmd_rename(int argc, char *argv[]) 440{ 441 char *dest, *src; 442 int err; 443 444 if (argc < 3) { 445 fprintf(stderr, "bectl rename: missing argument\n"); 446 return (usage(false)); 447 } 448 449 if (argc > 3) { 450 fprintf(stderr, "bectl rename: too many arguments\n"); 451 return (usage(false)); 452 } 453 454 src = argv[1]; 455 dest = argv[2]; 456 457 err = be_rename(be, src, dest); 458 459 switch (err) { 460 case BE_ERR_SUCCESS: 461 break; 462 default: 463 fprintf(stderr, "failed to rename bootenv %s to %s\n", 464 src, dest); 465 } 466 467 return (0); 468} 469 470static int 471bectl_cmd_unmount(int argc, char *argv[]) 472{ 473 char *bootenv, *cmd; 474 int err, flags, opt; 475 476 /* Store alias used */ 477 cmd = argv[0]; 478 479 flags = 0; 480 while ((opt = getopt(argc, argv, "f")) != -1) { 481 switch (opt) { 482 case 'f': 483 flags |= BE_MNT_FORCE; 484 break; 485 default: 486 fprintf(stderr, "bectl %s: unknown option '-%c'\n", 487 cmd, optopt); 488 return (usage(false)); 489 } 490 } 491 492 argc -= optind; 493 argv += optind; 494 495 if (argc != 1) { 496 fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd); 497 return (usage(false)); 498 } 499 500 bootenv = argv[0]; 501 502 err = be_unmount(be, bootenv, flags); 503 504 switch (err) { 505 case BE_ERR_SUCCESS: 506 break; 507 default: 508 fprintf(stderr, "failed to unmount bootenv %s\n", bootenv); 509 } 510 511 return (err); 512} 513 514static int 515bectl_cmd_check(int argc, char *argv[] __unused) 516{ 517 518 /* The command is left as argv[0] */ 519 if (argc != 1) { 520 fprintf(stderr, "bectl check: wrong number of arguments\n"); 521 return (usage(false)); 522 } 523 524 return (0); 525} 526 527int 528main(int argc, char *argv[]) 529{ 530 struct command_map_entry *cmd; 531 const char *command; 532 char *root; 533 int rc; 534 535 cmd = NULL; 536 root = NULL; 537 if (argc < 2) 538 return (usage(false)); 539 540 if (strcmp(argv[1], "-r") == 0) { 541 if (argc < 4) 542 return (usage(false)); 543 root = strdup(argv[2]); 544 command = argv[3]; 545 argc -= 3; 546 argv += 3; 547 } else { 548 command = argv[1]; 549 argc -= 1; 550 argv += 1; 551 } 552 553 /* Handle command aliases */ 554 if (strcmp(command, "umount") == 0) 555 command = "unmount"; 556 557 if (strcmp(command, "ujail") == 0) 558 command = "unjail"; 559 560 if ((strcmp(command, "-?") == 0) || (strcmp(command, "-h") == 0)) 561 return (usage(true)); 562 563 if ((cmd = get_cmd_info(command)) == NULL) { 564 fprintf(stderr, "unknown command: %s\n", command); 565 return (usage(false)); 566 } 567 568 if ((be = libbe_init(root)) == NULL) 569 return (-1); 570 571 libbe_print_on_error(be, !cmd->silent); 572 573 rc = cmd->fn(argc, argv); 574 575 libbe_close(be); 576 return (rc); 577} 578