1 /*
2 * Copyright (c) 2017 Kyle J. Kneitinger <kyle@kneit.in>
3 *
4 * SPDX-License-Identifier: BSD-2-Clause
5 */
6
7 #include <sys/param.h>
8 #include <sys/mount.h>
9 #include <err.h>
10 #include <errno.h>
11 #include <libutil.h>
12 #include <stdbool.h>
13 #include <stdio.h>
14 #include <stdint.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <sysexits.h>
18 #include <time.h>
19 #include <unistd.h>
20
21 #include <be.h>
22
23 #include "bectl.h"
24
25 static int bectl_cmd_activate(int argc, char *argv[]);
26 static int bectl_cmd_check(int argc, char *argv[]);
27 static int bectl_cmd_create(int argc, char *argv[]);
28 static int bectl_cmd_destroy(int argc, char *argv[]);
29 static int bectl_cmd_export(int argc, char *argv[]);
30 static int bectl_cmd_import(int argc, char *argv[]);
31 #if SOON
32 static int bectl_cmd_add(int argc, char *argv[]);
33 #endif
34 static int bectl_cmd_mount(int argc, char *argv[]);
35 static int bectl_cmd_rename(int argc, char *argv[]);
36 static int bectl_cmd_unmount(int argc, char *argv[]);
37
38 libbe_handle_t *be;
39
40 int
usage(bool explicit)41 usage(bool explicit)
42 {
43 FILE *fp;
44
45 fp = explicit ? stdout : stderr;
46 fprintf(fp, "%s",
47 "Usage:\tbectl {-h | subcommand [args...]}\n"
48 #if SOON
49 "\tbectl [-r beroot] add (path)*\n"
50 #endif
51 "\tbectl [-r beroot] activate [-t] beName\n"
52 "\tbectl [-r beroot] activate [-T]\n"
53 "\tbectl [-r beroot] check\n"
54 "\tbectl [-r beroot] create [-r] [-e {nonActiveBe | beName@snapshot}] beName\n"
55 "\tbectl [-r beroot] create [-r] beName@snapshot\n"
56 "\tbectl [-r beroot] create -E beName\n"
57 "\tbectl [-r beroot] destroy [-Fo] {beName | beName@snapshot}\n"
58 "\tbectl [-r beroot] export sourceBe\n"
59 "\tbectl [-r beroot] import targetBe\n"
60 "\tbectl [-r beroot] jail [-bU] [{-o key=value | -u key}]... beName\n"
61 "\t [utility [argument ...]]\n"
62 "\tbectl [-r beroot] list [-aDHs] [{-c property | -C property}]\n"
63 "\tbectl [-r beroot] mount beName [mountpoint]\n"
64 "\tbectl [-r beroot] rename origBeName newBeName\n"
65 "\tbectl [-r beroot] {ujail | unjail} {jailID | jailName | beName}\n"
66 "\tbectl [-r beroot] {umount | unmount} [-f] beName\n");
67
68 return (explicit ? 0 : EX_USAGE);
69 }
70
71
72 /*
73 * Represents a relationship between the command name and the parser action
74 * that handles it.
75 */
76 struct command_map_entry {
77 const char *command;
78 int (*fn)(int argc, char *argv[]);
79 /* True if libbe_print_on_error should be disabled */
80 bool silent;
81 bool save_history;
82 };
83
84 static struct command_map_entry command_map[] =
85 {
86 { "activate", bectl_cmd_activate,false, true },
87 { "create", bectl_cmd_create, false, true },
88 { "destroy", bectl_cmd_destroy, false, true },
89 { "export", bectl_cmd_export, false, true },
90 { "import", bectl_cmd_import, false, true },
91 #if SOON
92 { "add", bectl_cmd_add, false, true },
93 #endif
94 { "jail", bectl_cmd_jail, false, false },
95 { "list", bectl_cmd_list, false, false },
96 { "mount", bectl_cmd_mount, false, false },
97 { "rename", bectl_cmd_rename, false, true },
98 { "unjail", bectl_cmd_unjail, false, false },
99 { "ujail", bectl_cmd_unjail, false, false },
100 { "unmount", bectl_cmd_unmount, false, false },
101 { "umount", bectl_cmd_unmount, false, false },
102 { "check", bectl_cmd_check, true, false },
103 };
104
105 static struct command_map_entry *
get_cmd_info(const char * cmd)106 get_cmd_info(const char *cmd)
107 {
108 size_t i;
109
110 for (i = 0; i < nitems(command_map); ++i) {
111 if (strcmp(cmd, command_map[i].command) == 0)
112 return (&command_map[i]);
113 }
114
115 return (NULL);
116 }
117
118 static int
bectl_cmd_activate(int argc,char * argv[])119 bectl_cmd_activate(int argc, char *argv[])
120 {
121 int err, opt;
122 bool temp, reset;
123
124 temp = false;
125 reset = false;
126 while ((opt = getopt(argc, argv, "tT")) != -1) {
127 switch (opt) {
128 case 't':
129 if (reset)
130 return (usage(false));
131 temp = true;
132 break;
133 case 'T':
134 if (temp)
135 return (usage(false));
136 reset = true;
137 break;
138 default:
139 fprintf(stderr, "bectl activate: unknown option '-%c'\n",
140 optopt);
141 return (usage(false));
142 }
143 }
144
145 argc -= optind;
146 argv += optind;
147
148 if (argc != 1 && (!reset || argc != 0)) {
149 fprintf(stderr, "bectl activate: wrong number of arguments\n");
150 return (usage(false));
151 }
152
153 if (reset) {
154 if ((err = be_deactivate(be, NULL, reset)) == 0)
155 printf("Temporary activation removed\n");
156 else
157 printf("Failed to remove temporary activation\n");
158 return (err);
159 }
160
161 /* activate logic goes here */
162 if ((err = be_activate(be, argv[0], temp)) != 0)
163 /* XXX TODO: more specific error msg based on err */
164 printf("Did not successfully activate boot environment %s",
165 argv[0]);
166 else
167 printf("Successfully activated boot environment %s", argv[0]);
168
169 if (temp)
170 printf(" for next boot");
171
172 printf("\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
bectl_cmd_create(int argc,char * argv[])183 bectl_cmd_create(int argc, char *argv[])
184 {
185 char snapshot[BE_MAXPATHLEN];
186 char *atpos, *bootenv, *snapname;
187 int err, opt;
188 bool empty, recursive;
189
190 snapname = NULL;
191 empty = false;
192 recursive = false;
193 while ((opt = getopt(argc, argv, "e:Er")) != -1) {
194 switch (opt) {
195 case 'e':
196 snapname = optarg;
197 break;
198 case 'E':
199 empty = true;
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 if (empty) {
230 if (snapname || recursive) {
231 fprintf(stderr,
232 "bectl create: -E cannot be combined with -e or -r\n");
233 return (usage(false));
234 }
235 err = be_create_empty(be, bootenv);
236 } else {
237 if (snapname == NULL)
238 /* Create from currently booted BE */
239 err = be_snapshot(be, be_active_path(be), NULL,
240 recursive, snapshot);
241 else if (strchr(snapname, '@') != NULL)
242 /* Create from given snapshot */
243 strlcpy(snapshot, snapname, sizeof(snapshot));
244 else
245 /* Create from given BE */
246 err = be_snapshot(be, snapname, NULL, recursive,
247 snapshot);
248
249 if (err == BE_ERR_SUCCESS)
250 err = be_create_depth(be, bootenv, snapshot,
251 recursive == true ? -1 : 0);
252 }
253
254 switch (err) {
255 case BE_ERR_SUCCESS:
256 break;
257 case BE_ERR_INVALIDNAME:
258 fprintf(stderr,
259 "bectl create: boot environment name must not contain spaces\n");
260 break;
261 default:
262 if (atpos != NULL)
263 fprintf(stderr,
264 "Failed to create a snapshot '%s' of '%s'\n",
265 atpos, bootenv);
266 else if (snapname == NULL)
267 fprintf(stderr,
268 "Failed to create bootenv %s\n", bootenv);
269 else
270 fprintf(stderr,
271 "Failed to create bootenv %s from snapshot %s\n",
272 bootenv, snapname);
273 }
274
275 return (err);
276 }
277
278
279 static int
bectl_cmd_export(int argc,char * argv[])280 bectl_cmd_export(int argc, char *argv[])
281 {
282 char *bootenv;
283
284 if (argc == 1) {
285 fprintf(stderr, "bectl export: missing boot environment name\n");
286 return (usage(false));
287 }
288
289 if (argc > 2) {
290 fprintf(stderr, "bectl export: extra arguments provided\n");
291 return (usage(false));
292 }
293
294 bootenv = argv[1];
295
296 if (isatty(STDOUT_FILENO)) {
297 fprintf(stderr, "bectl export: must redirect output\n");
298 return (EX_USAGE);
299 }
300
301 be_export(be, bootenv, STDOUT_FILENO);
302
303 return (0);
304 }
305
306
307 static int
bectl_cmd_import(int argc,char * argv[])308 bectl_cmd_import(int argc, char *argv[])
309 {
310 char *bootenv;
311 int err;
312
313 if (argc == 1) {
314 fprintf(stderr, "bectl import: missing boot environment name\n");
315 return (usage(false));
316 }
317
318 if (argc > 2) {
319 fprintf(stderr, "bectl import: extra arguments provided\n");
320 return (usage(false));
321 }
322
323 bootenv = argv[1];
324
325 if (isatty(STDIN_FILENO)) {
326 fprintf(stderr, "bectl import: input can not be from terminal\n");
327 return (EX_USAGE);
328 }
329
330 err = be_import(be, bootenv, STDIN_FILENO);
331
332 return (err);
333 }
334
335 #if SOON
336 static int
bectl_cmd_add(int argc,char * argv[])337 bectl_cmd_add(int argc, char *argv[])
338 {
339
340 if (argc < 2) {
341 fprintf(stderr, "bectl add: must provide at least one path\n");
342 return (usage(false));
343 }
344
345 for (int i = 1; i < argc; ++i) {
346 printf("arg %d: %s\n", i, argv[i]);
347 /* XXX TODO catch err */
348 be_add_child(be, argv[i], true);
349 }
350
351 return (0);
352 }
353 #endif
354
355 static int
bectl_cmd_destroy(int argc,char * argv[])356 bectl_cmd_destroy(int argc, char *argv[])
357 {
358 nvlist_t *props;
359 char *target, targetds[BE_MAXPATHLEN];
360 const char *origin;
361 int err, flags, opt;
362
363 flags = 0;
364 while ((opt = getopt(argc, argv, "Fo")) != -1) {
365 switch (opt) {
366 case 'F':
367 flags |= BE_DESTROY_FORCE;
368 break;
369 case 'o':
370 flags |= BE_DESTROY_ORIGIN;
371 break;
372 default:
373 fprintf(stderr, "bectl destroy: unknown option '-%c'\n",
374 optopt);
375 return (usage(false));
376 }
377 }
378
379 argc -= optind;
380 argv += optind;
381
382 if (argc != 1) {
383 fprintf(stderr, "bectl destroy: wrong number of arguments\n");
384 return (usage(false));
385 }
386
387 target = argv[0];
388
389 /* We'll emit a notice if there's an origin to be cleaned up */
390 if ((flags & BE_DESTROY_ORIGIN) == 0 && strchr(target, '@') == NULL) {
391 flags |= BE_DESTROY_AUTOORIGIN;
392 if (be_root_concat(be, target, targetds) != 0)
393 goto destroy;
394 if (be_prop_list_alloc(&props) != 0)
395 goto destroy;
396 if (be_get_dataset_props(be, targetds, props) != 0) {
397 be_prop_list_free(props);
398 goto destroy;
399 }
400 if (nvlist_lookup_string(props, "origin", &origin) == 0 &&
401 !be_is_auto_snapshot_name(be, origin))
402 fprintf(stderr, "bectl destroy: leaving origin '%s' intact\n",
403 origin);
404 be_prop_list_free(props);
405 }
406
407 destroy:
408 err = be_destroy(be, target, flags);
409
410 return (err);
411 }
412
413 static int
bectl_cmd_mount(int argc,char * argv[])414 bectl_cmd_mount(int argc, char *argv[])
415 {
416 char result_loc[BE_MAXPATHLEN];
417 char *bootenv, *mountpoint;
418 int err, mntflags;
419
420 /* XXX TODO: Allow shallow */
421 mntflags = BE_MNT_DEEP;
422 if (argc < 2) {
423 fprintf(stderr, "bectl mount: missing argument(s)\n");
424 return (usage(false));
425 }
426
427 if (argc > 3) {
428 fprintf(stderr, "bectl mount: too many arguments\n");
429 return (usage(false));
430 }
431
432 bootenv = argv[1];
433 mountpoint = ((argc == 3) ? argv[2] : NULL);
434
435 err = be_mount(be, bootenv, mountpoint, mntflags, result_loc);
436
437 switch (err) {
438 case BE_ERR_SUCCESS:
439 printf("%s\n", result_loc);
440 break;
441 default:
442 fprintf(stderr,
443 (argc == 3) ? "Failed to mount bootenv %s at %s\n" :
444 "Failed to mount bootenv %s at temporary path %s\n",
445 bootenv, mountpoint);
446 }
447
448 return (err);
449 }
450
451
452 static int
bectl_cmd_rename(int argc,char * argv[])453 bectl_cmd_rename(int argc, char *argv[])
454 {
455 char *dest, *src;
456 int err;
457
458 if (argc < 3) {
459 fprintf(stderr, "bectl rename: missing argument\n");
460 return (usage(false));
461 }
462
463 if (argc > 3) {
464 fprintf(stderr, "bectl rename: too many arguments\n");
465 return (usage(false));
466 }
467
468 src = argv[1];
469 dest = argv[2];
470
471 err = be_rename(be, src, dest);
472 switch (err) {
473 case BE_ERR_SUCCESS:
474 break;
475 default:
476 fprintf(stderr, "Failed to rename bootenv %s to %s\n",
477 src, dest);
478 }
479
480 return (err);
481 }
482
483 static int
bectl_cmd_unmount(int argc,char * argv[])484 bectl_cmd_unmount(int argc, char *argv[])
485 {
486 char *bootenv, *cmd;
487 int err, flags, opt;
488
489 /* Store alias used */
490 cmd = argv[0];
491
492 flags = 0;
493 while ((opt = getopt(argc, argv, "f")) != -1) {
494 switch (opt) {
495 case 'f':
496 flags |= BE_MNT_FORCE;
497 break;
498 default:
499 fprintf(stderr, "bectl %s: unknown option '-%c'\n",
500 cmd, optopt);
501 return (usage(false));
502 }
503 }
504
505 argc -= optind;
506 argv += optind;
507
508 if (argc != 1) {
509 fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
510 return (usage(false));
511 }
512
513 bootenv = argv[0];
514
515 err = be_unmount(be, bootenv, flags);
516
517 switch (err) {
518 case BE_ERR_SUCCESS:
519 break;
520 default:
521 fprintf(stderr, "Failed to unmount bootenv %s\n", bootenv);
522 }
523
524 return (err);
525 }
526
527 static int
bectl_cmd_check(int argc,char * argv[]__unused)528 bectl_cmd_check(int argc, char *argv[] __unused)
529 {
530
531 /* The command is left as argv[0] */
532 if (argc != 1) {
533 fprintf(stderr, "bectl check: wrong number of arguments\n");
534 return (usage(false));
535 }
536
537 return (0);
538 }
539
540 static char *
save_cmdline(int argc,char * argv[])541 save_cmdline(int argc, char *argv[])
542 {
543 char *cmdline, *basename, *p;
544 int len, n, i;
545
546 len = MAXPATHLEN * 2 + 1; /* HIS_MAX_RECORD_LEN from zfs.h */
547 cmdline = p = malloc(len);
548 if (cmdline == NULL)
549 err(2, "malloc");
550
551 basename = strrchr(argv[0], '/');
552 if (basename == NULL)
553 basename = argv[0];
554 else
555 basename++;
556
557 n = strlcpy(p, basename, len);
558 for (i = 1; i < argc; i++) {
559 if (n >= len)
560 break;
561 p += n;
562 *p++ = ' ';
563 len -= (n + 1);
564 n = strlcpy(p, argv[i], len);
565 }
566
567 return (cmdline);
568 }
569
570 int
main(int argc,char * argv[])571 main(int argc, char *argv[])
572 {
573 struct command_map_entry *cmd;
574 const char *command;
575 char *root = NULL, *cmdline = NULL;
576 int opt, rc;
577
578 while ((opt = getopt(argc, argv, "hr:")) != -1) {
579 switch (opt) {
580 case 'h':
581 exit(usage(true));
582 case 'r':
583 root = strdup(optarg);
584 break;
585 default:
586 exit(usage(false));
587 }
588 }
589
590 argc -= optind;
591 argv += optind;
592
593 if (argc == 0)
594 exit(usage(false));
595
596 command = *argv;
597 optreset = 1;
598 optind = 1;
599
600 if ((cmd = get_cmd_info(command)) == NULL) {
601 fprintf(stderr, "Unknown command: %s\n", command);
602 return (usage(false));
603 }
604
605 if ((be = libbe_init(root)) == NULL) {
606 if (!cmd->silent)
607 fprintf(stderr, "libbe_init(\"%s\") failed.\n",
608 root != NULL ? root : "");
609 return (-1);
610 }
611
612 if (cmd->save_history)
613 cmdline = save_cmdline(argc+optind, argv-optind);
614
615 libbe_print_on_error(be, !cmd->silent);
616
617 rc = cmd->fn(argc, argv);
618
619 if (cmd->save_history) {
620 if (rc == 0)
621 be_log_history(be, cmdline);
622 free(cmdline);
623 }
624
625 libbe_close(be);
626 return (rc);
627 }
628