xref: /freebsd/sbin/bectl/bectl.c (revision e2afbc45258f2fa4bdcf126e959ac660e76fc802)
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
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] destroy [-Fo] {beName | beName@snapshot}\n"
57 	    "\tbectl [-r beroot] export sourceBe\n"
58 	    "\tbectl [-r beroot] import targetBe\n"
59 	    "\tbectl [-r beroot] jail [-bU] [{-o key=value | -u key}]... beName\n"
60 	    "\t      [utility [argument ...]]\n"
61 	    "\tbectl [-r beroot] list [-aDHs] [{-c property | -C property}]\n"
62 	    "\tbectl [-r beroot] mount beName [mountpoint]\n"
63 	    "\tbectl [-r beroot] rename origBeName newBeName\n"
64 	    "\tbectl [-r beroot] {ujail | unjail} {jailID | jailName | beName}\n"
65 	    "\tbectl [-r beroot] {umount | unmount} [-f] beName\n");
66 
67 	return (explicit ? 0 : EX_USAGE);
68 }
69 
70 
71 /*
72  * Represents a relationship between the command name and the parser action
73  * that handles it.
74  */
75 struct command_map_entry {
76 	const char *command;
77 	int (*fn)(int argc, char *argv[]);
78 	/* True if libbe_print_on_error should be disabled */
79 	bool silent;
80 	bool save_history;
81 };
82 
83 static struct command_map_entry command_map[] =
84 {
85 	{ "activate", bectl_cmd_activate,false, true    },
86 	{ "create",   bectl_cmd_create,  false, true    },
87 	{ "destroy",  bectl_cmd_destroy, false, true    },
88 	{ "export",   bectl_cmd_export,  false, true    },
89 	{ "import",   bectl_cmd_import,  false, true    },
90 #if SOON
91 	{ "add",      bectl_cmd_add,     false, true    },
92 #endif
93 	{ "jail",     bectl_cmd_jail,    false, false   },
94 	{ "list",     bectl_cmd_list,    false, false   },
95 	{ "mount",    bectl_cmd_mount,   false, false   },
96 	{ "rename",   bectl_cmd_rename,  false, true    },
97 	{ "unjail",   bectl_cmd_unjail,  false, false   },
98 	{ "ujail",    bectl_cmd_unjail,  false, false   },
99 	{ "unmount",  bectl_cmd_unmount, false, false   },
100 	{ "umount",   bectl_cmd_unmount, false, false   },
101 	{ "check",    bectl_cmd_check,   true,  false   },
102 };
103 
104 static struct command_map_entry *
105 get_cmd_info(const char *cmd)
106 {
107 	size_t i;
108 
109 	for (i = 0; i < nitems(command_map); ++i) {
110 		if (strcmp(cmd, command_map[i].command) == 0)
111 			return (&command_map[i]);
112 	}
113 
114 	return (NULL);
115 }
116 
117 static int
118 bectl_cmd_activate(int argc, char *argv[])
119 {
120 	int err, opt;
121 	bool temp, reset;
122 
123 	temp = false;
124 	reset = false;
125 	while ((opt = getopt(argc, argv, "tT")) != -1) {
126 		switch (opt) {
127 		case 't':
128 			if (reset)
129 				return (usage(false));
130 			temp = true;
131 			break;
132 		case 'T':
133 			if (temp)
134 				return (usage(false));
135 			reset = true;
136 			break;
137 		default:
138 			fprintf(stderr, "bectl activate: unknown option '-%c'\n",
139 			    optopt);
140 			return (usage(false));
141 		}
142 	}
143 
144 	argc -= optind;
145 	argv += optind;
146 
147 	if (argc != 1 && (!reset || argc != 0)) {
148 		fprintf(stderr, "bectl activate: wrong number of arguments\n");
149 		return (usage(false));
150 	}
151 
152 	if (reset) {
153 		if ((err = be_deactivate(be, NULL, reset)) == 0)
154 			printf("Temporary activation removed\n");
155 		else
156 			printf("Failed to remove temporary activation\n");
157 		return (err);
158 	}
159 
160 	/* activate logic goes here */
161 	if ((err = be_activate(be, argv[0], temp)) != 0)
162 		/* XXX TODO: more specific error msg based on err */
163 		printf("Did not successfully activate boot environment %s",
164 		    argv[0]);
165 	else
166 		printf("Successfully activated boot environment %s", argv[0]);
167 
168 	if (temp)
169 		printf(" for next boot");
170 
171 	printf("\n");
172 
173 	return (err);
174 }
175 
176 
177 /*
178  * TODO: when only one arg is given, and it contains an "@" the this should
179  * create that snapshot
180  */
181 static int
182 bectl_cmd_create(int argc, char *argv[])
183 {
184 	char snapshot[BE_MAXPATHLEN];
185 	char *atpos, *bootenv, *snapname;
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 
216 	err = BE_ERR_SUCCESS;
217 	if ((atpos = strchr(bootenv, '@')) != NULL) {
218 		/*
219 		 * This is the "create a snapshot variant". No new boot
220 		 * environment is to be created here.
221 		 */
222 		*atpos++ = '\0';
223 		err = be_snapshot(be, bootenv, atpos, recursive, NULL);
224 	} else {
225 		if (snapname == NULL)
226 			/* Create from currently booted BE */
227 			err = be_snapshot(be, be_active_path(be), NULL,
228 			    recursive, snapshot);
229 		else if (strchr(snapname, '@') != NULL)
230 			/* Create from given snapshot */
231 			strlcpy(snapshot, snapname, sizeof(snapshot));
232 		else
233 			/* Create from given BE */
234 			err = be_snapshot(be, snapname, NULL, recursive,
235 			    snapshot);
236 
237 		if (err == BE_ERR_SUCCESS)
238 			err = be_create_depth(be, bootenv, snapshot,
239 					      recursive == true ? -1 : 0);
240 	}
241 
242 	switch (err) {
243 	case BE_ERR_SUCCESS:
244 		break;
245 	case BE_ERR_INVALIDNAME:
246 		fprintf(stderr,
247 		    "bectl create: boot environment name must not contain spaces\n");
248 		break;
249 	default:
250 		if (atpos != NULL)
251 			fprintf(stderr,
252 			    "Failed to create a snapshot '%s' of '%s'\n",
253 			    atpos, bootenv);
254 		else if (snapname == NULL)
255 			fprintf(stderr,
256 			    "Failed to create bootenv %s\n", bootenv);
257 		else
258 			fprintf(stderr,
259 			    "Failed to create bootenv %s from snapshot %s\n",
260 			    bootenv, snapname);
261 	}
262 
263 	return (err);
264 }
265 
266 
267 static int
268 bectl_cmd_export(int argc, char *argv[])
269 {
270 	char *bootenv;
271 
272 	if (argc == 1) {
273 		fprintf(stderr, "bectl export: missing boot environment name\n");
274 		return (usage(false));
275 	}
276 
277 	if (argc > 2) {
278 		fprintf(stderr, "bectl export: extra arguments provided\n");
279 		return (usage(false));
280 	}
281 
282 	bootenv = argv[1];
283 
284 	if (isatty(STDOUT_FILENO)) {
285 		fprintf(stderr, "bectl export: must redirect output\n");
286 		return (EX_USAGE);
287 	}
288 
289 	be_export(be, bootenv, STDOUT_FILENO);
290 
291 	return (0);
292 }
293 
294 
295 static int
296 bectl_cmd_import(int argc, char *argv[])
297 {
298 	char *bootenv;
299 	int err;
300 
301 	if (argc == 1) {
302 		fprintf(stderr, "bectl import: missing boot environment name\n");
303 		return (usage(false));
304 	}
305 
306 	if (argc > 2) {
307 		fprintf(stderr, "bectl import: extra arguments provided\n");
308 		return (usage(false));
309 	}
310 
311 	bootenv = argv[1];
312 
313 	if (isatty(STDIN_FILENO)) {
314 		fprintf(stderr, "bectl import: input can not be from terminal\n");
315 		return (EX_USAGE);
316 	}
317 
318 	err = be_import(be, bootenv, STDIN_FILENO);
319 
320 	return (err);
321 }
322 
323 #if SOON
324 static int
325 bectl_cmd_add(int argc, char *argv[])
326 {
327 
328 	if (argc < 2) {
329 		fprintf(stderr, "bectl add: must provide at least one path\n");
330 		return (usage(false));
331 	}
332 
333 	for (int i = 1; i < argc; ++i) {
334 		printf("arg %d: %s\n", i, argv[i]);
335 		/* XXX TODO catch err */
336 		be_add_child(be, argv[i], true);
337 	}
338 
339 	return (0);
340 }
341 #endif
342 
343 static int
344 bectl_cmd_destroy(int argc, char *argv[])
345 {
346 	nvlist_t *props;
347 	char *target, targetds[BE_MAXPATHLEN];
348 	const char *origin;
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 		flags |= BE_DESTROY_AUTOORIGIN;
380 		if (be_root_concat(be, target, targetds) != 0)
381 			goto destroy;
382 		if (be_prop_list_alloc(&props) != 0)
383 			goto destroy;
384 		if (be_get_dataset_props(be, targetds, props) != 0) {
385 			be_prop_list_free(props);
386 			goto destroy;
387 		}
388 		if (nvlist_lookup_string(props, "origin", &origin) == 0 &&
389 		    !be_is_auto_snapshot_name(be, origin))
390 			fprintf(stderr, "bectl destroy: leaving origin '%s' intact\n",
391 			    origin);
392 		be_prop_list_free(props);
393 	}
394 
395 destroy:
396 	err = be_destroy(be, target, flags);
397 
398 	return (err);
399 }
400 
401 static int
402 bectl_cmd_mount(int argc, char *argv[])
403 {
404 	char result_loc[BE_MAXPATHLEN];
405 	char *bootenv, *mountpoint;
406 	int err, mntflags;
407 
408 	/* XXX TODO: Allow shallow */
409 	mntflags = BE_MNT_DEEP;
410 	if (argc < 2) {
411 		fprintf(stderr, "bectl mount: missing argument(s)\n");
412 		return (usage(false));
413 	}
414 
415 	if (argc > 3) {
416 		fprintf(stderr, "bectl mount: too many arguments\n");
417 		return (usage(false));
418 	}
419 
420 	bootenv = argv[1];
421 	mountpoint = ((argc == 3) ? argv[2] : NULL);
422 
423 	err = be_mount(be, bootenv, mountpoint, mntflags, result_loc);
424 
425 	switch (err) {
426 	case BE_ERR_SUCCESS:
427 		printf("%s\n", result_loc);
428 		break;
429 	default:
430 		fprintf(stderr,
431 		    (argc == 3) ? "Failed to mount bootenv %s at %s\n" :
432 		    "Failed to mount bootenv %s at temporary path %s\n",
433 		    bootenv, mountpoint);
434 	}
435 
436 	return (err);
437 }
438 
439 
440 static int
441 bectl_cmd_rename(int argc, char *argv[])
442 {
443 	char *dest, *src;
444 	int err;
445 
446 	if (argc < 3) {
447 		fprintf(stderr, "bectl rename: missing argument\n");
448 		return (usage(false));
449 	}
450 
451 	if (argc > 3) {
452 		fprintf(stderr, "bectl rename: too many arguments\n");
453 		return (usage(false));
454 	}
455 
456 	src = argv[1];
457 	dest = argv[2];
458 
459 	err = be_rename(be, src, dest);
460 	switch (err) {
461 	case BE_ERR_SUCCESS:
462 		break;
463 	default:
464 		fprintf(stderr, "Failed to rename bootenv %s to %s\n",
465 		    src, dest);
466 	}
467 
468 	return (err);
469 }
470 
471 static int
472 bectl_cmd_unmount(int argc, char *argv[])
473 {
474 	char *bootenv, *cmd;
475 	int err, flags, opt;
476 
477 	/* Store alias used */
478 	cmd = argv[0];
479 
480 	flags = 0;
481 	while ((opt = getopt(argc, argv, "f")) != -1) {
482 		switch (opt) {
483 		case 'f':
484 			flags |= BE_MNT_FORCE;
485 			break;
486 		default:
487 			fprintf(stderr, "bectl %s: unknown option '-%c'\n",
488 			    cmd, optopt);
489 			return (usage(false));
490 		}
491 	}
492 
493 	argc -= optind;
494 	argv += optind;
495 
496 	if (argc != 1) {
497 		fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
498 		return (usage(false));
499 	}
500 
501 	bootenv = argv[0];
502 
503 	err = be_unmount(be, bootenv, flags);
504 
505 	switch (err) {
506 	case BE_ERR_SUCCESS:
507 		break;
508 	default:
509 		fprintf(stderr, "Failed to unmount bootenv %s\n", bootenv);
510 	}
511 
512 	return (err);
513 }
514 
515 static int
516 bectl_cmd_check(int argc, char *argv[] __unused)
517 {
518 
519 	/* The command is left as argv[0] */
520 	if (argc != 1) {
521 		fprintf(stderr, "bectl check: wrong number of arguments\n");
522 		return (usage(false));
523 	}
524 
525 	return (0);
526 }
527 
528 static char *
529 save_cmdline(int argc, char *argv[])
530 {
531 	char *cmdline, *basename, *p;
532 	int len, n, i;
533 
534 	len = MAXPATHLEN * 2 + 1; /* HIS_MAX_RECORD_LEN from zfs.h */
535 	cmdline = p = malloc(len);
536 	if (cmdline == NULL)
537 		err(2, "malloc");
538 
539 	basename = strrchr(argv[0], '/');
540 	if (basename == NULL)
541 		basename = argv[0];
542 	else
543 		basename++;
544 
545 	n = strlcpy(p, basename, len);
546 	for (i = 1; i < argc; i++) {
547 		if (n >= len)
548 			break;
549 		p += n;
550 		*p++ = ' ';
551 		len -= (n + 1);
552 		n = strlcpy(p, argv[i], len);
553 	}
554 
555 	return (cmdline);
556 }
557 
558 int
559 main(int argc, char *argv[])
560 {
561 	struct command_map_entry *cmd;
562 	const char *command;
563 	char *root = NULL, *cmdline = NULL;
564 	int opt, rc;
565 
566 	while ((opt = getopt(argc, argv, "hr:")) != -1) {
567 		switch (opt) {
568 		case 'h':
569 			exit(usage(true));
570 		case 'r':
571 			root = strdup(optarg);
572 			break;
573 		default:
574 			exit(usage(false));
575 		}
576 	}
577 
578 	argc -= optind;
579 	argv += optind;
580 
581 	if (argc == 0)
582 		exit(usage(false));
583 
584 	command = *argv;
585 	optreset = 1;
586 	optind = 1;
587 
588 	if ((cmd = get_cmd_info(command)) == NULL) {
589 		fprintf(stderr, "Unknown command: %s\n", command);
590 		return (usage(false));
591 	}
592 
593 	if ((be = libbe_init(root)) == NULL) {
594 		if (!cmd->silent)
595 			fprintf(stderr, "libbe_init(\"%s\") failed.\n",
596 			    root != NULL ? root : "");
597 		return (-1);
598 	}
599 
600 	if (cmd->save_history)
601 		cmdline = save_cmdline(argc+optind, argv-optind);
602 
603 	libbe_print_on_error(be, !cmd->silent);
604 
605 	rc = cmd->fn(argc, argv);
606 
607 	if (cmd->save_history) {
608 		if (rc == 0)
609 			be_log_history(be, cmdline);
610 		free(cmdline);
611 	}
612 
613 	libbe_close(be);
614 	return (rc);
615 }
616