xref: /src/sbin/bectl/bectl.c (revision 2e020c84cb5ee1452e448f27ff7a7b6076c0147a)
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