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