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 <errno.h>
10 #include <libutil.h>
11 #include <stdbool.h>
12 #include <stdio.h>
13 #include <stdint.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <sysexits.h>
17 #include <time.h>
18 #include <unistd.h>
19
20 #include <be.h>
21
22 #include "bectl.h"
23
24 static int bectl_cmd_activate(int argc, char *argv[]);
25 static int bectl_cmd_check(int argc, char *argv[]);
26 static int bectl_cmd_create(int argc, char *argv[]);
27 static int bectl_cmd_destroy(int argc, char *argv[]);
28 static int bectl_cmd_export(int argc, char *argv[]);
29 static int bectl_cmd_import(int argc, char *argv[]);
30 #if SOON
31 static int bectl_cmd_add(int argc, char *argv[]);
32 #endif
33 static int bectl_cmd_mount(int argc, char *argv[]);
34 static int bectl_cmd_rename(int argc, char *argv[]);
35 static int bectl_cmd_unmount(int argc, char *argv[]);
36
37 libbe_handle_t *be;
38
39 int
usage(bool explicit)40 usage(bool explicit)
41 {
42 FILE *fp;
43
44 fp = explicit ? stdout : stderr;
45 fprintf(fp, "%s",
46 "Usage:\tbectl {-h | subcommand [args...]}\n"
47 #if SOON
48 "\tbectl [-r beroot] add (path)*\n"
49 #endif
50 "\tbectl [-r beroot] activate [-t] beName\n"
51 "\tbectl [-r beroot] activate [-T]\n"
52 "\tbectl [-r beroot] check\n"
53 "\tbectl [-r beroot] create [-r] [-e {nonActiveBe | beName@snapshot}] beName\n"
54 "\tbectl [-r beroot] create [-r] beName@snapshot\n"
55 "\tbectl [-r beroot] destroy [-Fo] {beName | beName@snapshot}\n"
56 "\tbectl [-r beroot] export sourceBe\n"
57 "\tbectl [-r beroot] import targetBe\n"
58 "\tbectl [-r beroot] jail [-bU] [{-o key=value | -u key}]... beName\n"
59 "\t [utility [argument ...]]\n"
60 "\tbectl [-r beroot] list [-aDHs] [{-c property | -C property}]\n"
61 "\tbectl [-r beroot] mount beName [mountpoint]\n"
62 "\tbectl [-r beroot] rename origBeName newBeName\n"
63 "\tbectl [-r beroot] {ujail | unjail} {jailID | jailName | beName}\n"
64 "\tbectl [-r beroot] {umount | unmount} [-f] beName\n");
65
66 return (explicit ? 0 : EX_USAGE);
67 }
68
69
70 /*
71 * Represents a relationship between the command name and the parser action
72 * that handles it.
73 */
74 struct command_map_entry {
75 const char *command;
76 int (*fn)(int argc, char *argv[]);
77 /* True if libbe_print_on_error should be disabled */
78 bool silent;
79 };
80
81 static struct command_map_entry command_map[] =
82 {
83 { "activate", bectl_cmd_activate,false },
84 { "create", bectl_cmd_create, false },
85 { "destroy", bectl_cmd_destroy, false },
86 { "export", bectl_cmd_export, false },
87 { "import", bectl_cmd_import, false },
88 #if SOON
89 { "add", bectl_cmd_add, false },
90 #endif
91 { "jail", bectl_cmd_jail, false },
92 { "list", bectl_cmd_list, false },
93 { "mount", bectl_cmd_mount, false },
94 { "rename", bectl_cmd_rename, false },
95 { "unjail", bectl_cmd_unjail, false },
96 { "ujail", bectl_cmd_unjail, false },
97 { "unmount", bectl_cmd_unmount, false },
98 { "umount", bectl_cmd_unmount, false },
99 { "check", bectl_cmd_check, true },
100 };
101
102 static struct command_map_entry *
get_cmd_info(const char * cmd)103 get_cmd_info(const char *cmd)
104 {
105 size_t i;
106
107 for (i = 0; i < nitems(command_map); ++i) {
108 if (strcmp(cmd, command_map[i].command) == 0)
109 return (&command_map[i]);
110 }
111
112 return (NULL);
113 }
114
115 static int
bectl_cmd_activate(int argc,char * argv[])116 bectl_cmd_activate(int argc, char *argv[])
117 {
118 int err, opt;
119 bool temp, reset;
120
121 temp = false;
122 reset = false;
123 while ((opt = getopt(argc, argv, "tT")) != -1) {
124 switch (opt) {
125 case 't':
126 if (reset)
127 return (usage(false));
128 temp = true;
129 break;
130 case 'T':
131 if (temp)
132 return (usage(false));
133 reset = true;
134 break;
135 default:
136 fprintf(stderr, "bectl activate: unknown option '-%c'\n",
137 optopt);
138 return (usage(false));
139 }
140 }
141
142 argc -= optind;
143 argv += optind;
144
145 if (argc != 1 && (!reset || argc != 0)) {
146 fprintf(stderr, "bectl activate: wrong number of arguments\n");
147 return (usage(false));
148 }
149
150 if (reset) {
151 if ((err = be_deactivate(be, NULL, reset)) == 0)
152 printf("Temporary activation removed\n");
153 else
154 printf("Failed to remove temporary activation\n");
155 return (err);
156 }
157
158 /* activate logic goes here */
159 if ((err = be_activate(be, argv[0], temp)) != 0)
160 /* XXX TODO: more specific error msg based on err */
161 printf("Did not successfully activate boot environment %s",
162 argv[0]);
163 else
164 printf("Successfully activated boot environment %s", argv[0]);
165
166 if (temp)
167 printf(" for next boot");
168
169 printf("\n");
170
171 return (err);
172 }
173
174
175 /*
176 * TODO: when only one arg is given, and it contains an "@" the this should
177 * create that snapshot
178 */
179 static int
bectl_cmd_create(int argc,char * argv[])180 bectl_cmd_create(int argc, char *argv[])
181 {
182 char snapshot[BE_MAXPATHLEN];
183 char *atpos, *bootenv, *snapname;
184 int err, opt;
185 bool recursive;
186
187 snapname = NULL;
188 recursive = false;
189 while ((opt = getopt(argc, argv, "e:r")) != -1) {
190 switch (opt) {
191 case 'e':
192 snapname = optarg;
193 break;
194 case 'r':
195 recursive = true;
196 break;
197 default:
198 fprintf(stderr, "bectl create: unknown option '-%c'\n",
199 optopt);
200 return (usage(false));
201 }
202 }
203
204 argc -= optind;
205 argv += optind;
206
207 if (argc != 1) {
208 fprintf(stderr, "bectl create: wrong number of arguments\n");
209 return (usage(false));
210 }
211
212 bootenv = *argv;
213
214 err = BE_ERR_SUCCESS;
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 {
223 if (snapname == NULL)
224 /* Create from currently booted BE */
225 err = be_snapshot(be, be_active_path(be), NULL,
226 recursive, snapshot);
227 else if (strchr(snapname, '@') != NULL)
228 /* Create from given snapshot */
229 strlcpy(snapshot, snapname, sizeof(snapshot));
230 else
231 /* Create from given BE */
232 err = be_snapshot(be, snapname, NULL, recursive,
233 snapshot);
234
235 if (err == BE_ERR_SUCCESS)
236 err = be_create_depth(be, bootenv, snapshot,
237 recursive == true ? -1 : 0);
238 }
239
240 switch (err) {
241 case BE_ERR_SUCCESS:
242 break;
243 case BE_ERR_INVALIDNAME:
244 fprintf(stderr,
245 "bectl create: boot environment name must not contain spaces\n");
246 break;
247 default:
248 if (atpos != NULL)
249 fprintf(stderr,
250 "Failed to create a snapshot '%s' of '%s'\n",
251 atpos, bootenv);
252 else if (snapname == NULL)
253 fprintf(stderr,
254 "Failed to create bootenv %s\n", bootenv);
255 else
256 fprintf(stderr,
257 "Failed to create bootenv %s from snapshot %s\n",
258 bootenv, snapname);
259 }
260
261 return (err);
262 }
263
264
265 static int
bectl_cmd_export(int argc,char * argv[])266 bectl_cmd_export(int argc, char *argv[])
267 {
268 char *bootenv;
269
270 if (argc == 1) {
271 fprintf(stderr, "bectl export: missing boot environment name\n");
272 return (usage(false));
273 }
274
275 if (argc > 2) {
276 fprintf(stderr, "bectl export: extra arguments provided\n");
277 return (usage(false));
278 }
279
280 bootenv = argv[1];
281
282 if (isatty(STDOUT_FILENO)) {
283 fprintf(stderr, "bectl export: must redirect output\n");
284 return (EX_USAGE);
285 }
286
287 be_export(be, bootenv, STDOUT_FILENO);
288
289 return (0);
290 }
291
292
293 static int
bectl_cmd_import(int argc,char * argv[])294 bectl_cmd_import(int argc, char *argv[])
295 {
296 char *bootenv;
297 int err;
298
299 if (argc == 1) {
300 fprintf(stderr, "bectl import: missing boot environment name\n");
301 return (usage(false));
302 }
303
304 if (argc > 2) {
305 fprintf(stderr, "bectl import: extra arguments provided\n");
306 return (usage(false));
307 }
308
309 bootenv = argv[1];
310
311 if (isatty(STDIN_FILENO)) {
312 fprintf(stderr, "bectl import: input can not be from terminal\n");
313 return (EX_USAGE);
314 }
315
316 err = be_import(be, bootenv, STDIN_FILENO);
317
318 return (err);
319 }
320
321 #if SOON
322 static int
bectl_cmd_add(int argc,char * argv[])323 bectl_cmd_add(int argc, char *argv[])
324 {
325
326 if (argc < 2) {
327 fprintf(stderr, "bectl add: must provide at least one path\n");
328 return (usage(false));
329 }
330
331 for (int i = 1; i < argc; ++i) {
332 printf("arg %d: %s\n", i, argv[i]);
333 /* XXX TODO catch err */
334 be_add_child(be, argv[i], true);
335 }
336
337 return (0);
338 }
339 #endif
340
341 static int
bectl_cmd_destroy(int argc,char * argv[])342 bectl_cmd_destroy(int argc, char *argv[])
343 {
344 nvlist_t *props;
345 char *target, targetds[BE_MAXPATHLEN];
346 const char *origin;
347 int err, flags, opt;
348
349 flags = 0;
350 while ((opt = getopt(argc, argv, "Fo")) != -1) {
351 switch (opt) {
352 case 'F':
353 flags |= BE_DESTROY_FORCE;
354 break;
355 case 'o':
356 flags |= BE_DESTROY_ORIGIN;
357 break;
358 default:
359 fprintf(stderr, "bectl destroy: unknown option '-%c'\n",
360 optopt);
361 return (usage(false));
362 }
363 }
364
365 argc -= optind;
366 argv += optind;
367
368 if (argc != 1) {
369 fprintf(stderr, "bectl destroy: wrong number of arguments\n");
370 return (usage(false));
371 }
372
373 target = argv[0];
374
375 /* We'll emit a notice if there's an origin to be cleaned up */
376 if ((flags & BE_DESTROY_ORIGIN) == 0 && strchr(target, '@') == NULL) {
377 flags |= BE_DESTROY_AUTOORIGIN;
378 if (be_root_concat(be, target, targetds) != 0)
379 goto destroy;
380 if (be_prop_list_alloc(&props) != 0)
381 goto destroy;
382 if (be_get_dataset_props(be, targetds, props) != 0) {
383 be_prop_list_free(props);
384 goto destroy;
385 }
386 if (nvlist_lookup_string(props, "origin", &origin) == 0 &&
387 !be_is_auto_snapshot_name(be, origin))
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("%s\n", 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 switch (err) {
459 case BE_ERR_SUCCESS:
460 break;
461 default:
462 fprintf(stderr, "Failed to rename bootenv %s to %s\n",
463 src, dest);
464 }
465
466 return (err);
467 }
468
469 static int
bectl_cmd_unmount(int argc,char * argv[])470 bectl_cmd_unmount(int argc, char *argv[])
471 {
472 char *bootenv, *cmd;
473 int err, flags, opt;
474
475 /* Store alias used */
476 cmd = argv[0];
477
478 flags = 0;
479 while ((opt = getopt(argc, argv, "f")) != -1) {
480 switch (opt) {
481 case 'f':
482 flags |= BE_MNT_FORCE;
483 break;
484 default:
485 fprintf(stderr, "bectl %s: unknown option '-%c'\n",
486 cmd, optopt);
487 return (usage(false));
488 }
489 }
490
491 argc -= optind;
492 argv += optind;
493
494 if (argc != 1) {
495 fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
496 return (usage(false));
497 }
498
499 bootenv = argv[0];
500
501 err = be_unmount(be, bootenv, flags);
502
503 switch (err) {
504 case BE_ERR_SUCCESS:
505 break;
506 default:
507 fprintf(stderr, "Failed to unmount bootenv %s\n", bootenv);
508 }
509
510 return (err);
511 }
512
513 static int
bectl_cmd_check(int argc,char * argv[]__unused)514 bectl_cmd_check(int argc, char *argv[] __unused)
515 {
516
517 /* The command is left as argv[0] */
518 if (argc != 1) {
519 fprintf(stderr, "bectl check: wrong number of arguments\n");
520 return (usage(false));
521 }
522
523 return (0);
524 }
525
526 int
main(int argc,char * argv[])527 main(int argc, char *argv[])
528 {
529 struct command_map_entry *cmd;
530 const char *command;
531 char *root = NULL;
532 int opt, rc;
533
534 while ((opt = getopt(argc, argv, "hr:")) != -1) {
535 switch (opt) {
536 case 'h':
537 exit(usage(true));
538 case 'r':
539 root = strdup(optarg);
540 break;
541 default:
542 exit(usage(false));
543 }
544 }
545
546 argc -= optind;
547 argv += optind;
548
549 if (argc == 0)
550 exit(usage(false));
551
552 command = *argv;
553 optreset = 1;
554 optind = 1;
555
556 if ((cmd = get_cmd_info(command)) == NULL) {
557 fprintf(stderr, "Unknown command: %s\n", command);
558 return (usage(false));
559 }
560
561 if ((be = libbe_init(root)) == NULL) {
562 if (!cmd->silent)
563 fprintf(stderr, "libbe_init(\"%s\") failed.\n",
564 root != NULL ? root : "");
565 return (-1);
566 }
567
568 libbe_print_on_error(be, !cmd->silent);
569
570 rc = cmd->fn(argc, argv);
571
572 libbe_close(be);
573 return (rc);
574 }
575