1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2017 Kyle J. Kneitinger <[email protected]>
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
usage(bool explicit)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 *
get_cmd_info(const char * cmd)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
bectl_cmd_activate(int argc,char * argv[])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
bectl_cmd_create(int argc,char * argv[])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
bectl_cmd_export(int argc,char * argv[])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
bectl_cmd_import(int argc,char * argv[])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
bectl_cmd_add(int argc,char * argv[])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
bectl_cmd_destroy(int argc,char * argv[])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 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
393 destroy:
394 err = be_destroy(be, target, flags);
395
396 return (err);
397 }
398
399 static int
bectl_cmd_mount(int argc,char * argv[])400 bectl_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
438 static int
bectl_cmd_rename(int argc,char * argv[])439 bectl_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
470 static int
bectl_cmd_unmount(int argc,char * argv[])471 bectl_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
514 static int
bectl_cmd_check(int argc,char * argv[]__unused)515 bectl_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
527 int
main(int argc,char * argv[])528 main(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