xref: /freebsd/sbin/bectl/bectl.c (revision 73577bf01de5c4677dc54d97f93310286c254780)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright (c) 2017 Kyle J. Kneitinger <kyle@kneit.in>
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
30 
31 #include <sys/param.h>
32 #include <sys/mount.h>
33 #include <errno.h>
34 #include <libutil.h>
35 #include <stdbool.h>
36 #include <stdio.h>
37 #include <stdint.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <sysexits.h>
41 #include <time.h>
42 #include <unistd.h>
43 
44 #include <be.h>
45 
46 #include "bectl.h"
47 
48 static int bectl_cmd_activate(int argc, char *argv[]);
49 static int bectl_cmd_check(int argc, char *argv[]);
50 static int bectl_cmd_create(int argc, char *argv[]);
51 static int bectl_cmd_destroy(int argc, char *argv[]);
52 static int bectl_cmd_export(int argc, char *argv[]);
53 static int bectl_cmd_import(int argc, char *argv[]);
54 #if SOON
55 static int bectl_cmd_add(int argc, char *argv[]);
56 #endif
57 static int bectl_cmd_mount(int argc, char *argv[]);
58 static int bectl_cmd_rename(int argc, char *argv[]);
59 static int bectl_cmd_unmount(int argc, char *argv[]);
60 
61 libbe_handle_t *be;
62 
63 int aok;
64 
65 int
66 usage(bool explicit)
67 {
68 	FILE *fp;
69 
70 	fp =  explicit ? stdout : stderr;
71 	fprintf(fp, "%s",
72 	    "Usage:\tbectl {-h | -? | subcommand [args...]}\n"
73 #if SOON
74 	    "\tbectl add (path)*\n"
75 #endif
76 	    "\tbectl activate [-t] beName\n"
77 	    "\tbectl activate [-T]\n"
78 	    "\tbectl check\n"
79 	    "\tbectl create [-r] [-e {nonActiveBe | beName@snapshot}] beName\n"
80 	    "\tbectl create [-r] beName@snapshot\n"
81 	    "\tbectl destroy [-F] {beName | beName@snapshot}\n"
82 	    "\tbectl export sourceBe\n"
83 	    "\tbectl import targetBe\n"
84 	    "\tbectl jail {-b | -U} [{-o key=value | -u key}]... "
85 	    "{jailID | jailName}\n"
86 	    "\t      bootenv [utility [argument ...]]\n"
87 	    "\tbectl list [-DHas] [{-c property | -C property}]\n"
88 	    "\tbectl mount beName [mountpoint]\n"
89 	    "\tbectl rename origBeName newBeName\n"
90 	    "\tbectl {ujail | unjail} {jailID | jailName} bootenv\n"
91 	    "\tbectl {umount | unmount} [-f] beName\n");
92 
93 	return (explicit ? 0 : EX_USAGE);
94 }
95 
96 
97 /*
98  * Represents a relationship between the command name and the parser action
99  * that handles it.
100  */
101 struct command_map_entry {
102 	const char *command;
103 	int (*fn)(int argc, char *argv[]);
104 	/* True if libbe_print_on_error should be disabled */
105 	bool silent;
106 };
107 
108 static struct command_map_entry command_map[] =
109 {
110 	{ "activate", bectl_cmd_activate,false   },
111 	{ "create",   bectl_cmd_create,  false   },
112 	{ "destroy",  bectl_cmd_destroy, false   },
113 	{ "export",   bectl_cmd_export,  false   },
114 	{ "import",   bectl_cmd_import,  false   },
115 #if SOON
116 	{ "add",      bectl_cmd_add,     false   },
117 #endif
118 	{ "jail",     bectl_cmd_jail,    false   },
119 	{ "list",     bectl_cmd_list,    false   },
120 	{ "mount",    bectl_cmd_mount,   false   },
121 	{ "rename",   bectl_cmd_rename,  false   },
122 	{ "unjail",   bectl_cmd_unjail,  false   },
123 	{ "unmount",  bectl_cmd_unmount, false   },
124 	{ "check",    bectl_cmd_check,   true    },
125 };
126 
127 static struct command_map_entry *
128 get_cmd_info(const char *cmd)
129 {
130 	size_t i;
131 
132 	for (i = 0; i < nitems(command_map); ++i) {
133 		if (strcmp(cmd, command_map[i].command) == 0)
134 			return (&command_map[i]);
135 	}
136 
137 	return (NULL);
138 }
139 
140 
141 static int
142 bectl_cmd_activate(int argc, char *argv[])
143 {
144 	int err, opt;
145 	bool temp, reset;
146 
147 	temp = false;
148 	reset = false;
149 	while ((opt = getopt(argc, argv, "tT")) != -1) {
150 		switch (opt) {
151 		case 't':
152 			if (reset)
153 				return (usage(false));
154 			temp = true;
155 			break;
156 		case 'T':
157 			if (temp)
158 				return (usage(false));
159 			reset = true;
160 			break;
161 		default:
162 			fprintf(stderr, "bectl activate: unknown option '-%c'\n",
163 			    optopt);
164 			return (usage(false));
165 		}
166 	}
167 
168 	argc -= optind;
169 	argv += optind;
170 
171 	if (argc != 1 && (!reset || argc != 0)) {
172 		fprintf(stderr, "bectl activate: wrong number of arguments\n");
173 		return (usage(false));
174 	}
175 
176 	if (reset) {
177 		if ((err = be_deactivate(be, NULL, reset)) == 0)
178 			printf("Temporary activation removed\n");
179 		else
180 			printf("Failed to remove temporary activation\n");
181 		return (err);
182 	}
183 
184 	/* activate logic goes here */
185 	if ((err = be_activate(be, argv[0], temp)) != 0)
186 		/* XXX TODO: more specific error msg based on err */
187 		printf("Did not successfully activate boot environment %s\n",
188 		    argv[0]);
189 	else
190 		printf("Successfully activated boot environment %s\n", argv[0]);
191 
192 	if (temp)
193 		printf("for next boot\n");
194 
195 	return (err);
196 }
197 
198 
199 /*
200  * TODO: when only one arg is given, and it contains an "@" the this should
201  * create that snapshot
202  */
203 static int
204 bectl_cmd_create(int argc, char *argv[])
205 {
206 	char snapshot[BE_MAXPATHLEN];
207 	char *atpos, *bootenv, *snapname;
208 	int err, opt;
209 	bool recursive;
210 
211 	snapname = NULL;
212 	recursive = false;
213 	while ((opt = getopt(argc, argv, "e:r")) != -1) {
214 		switch (opt) {
215 		case 'e':
216 			snapname = optarg;
217 			break;
218 		case 'r':
219 			recursive = true;
220 			break;
221 		default:
222 			fprintf(stderr, "bectl create: unknown option '-%c'\n",
223 			    optopt);
224 			return (usage(false));
225 		}
226 	}
227 
228 	argc -= optind;
229 	argv += optind;
230 
231 	if (argc != 1) {
232 		fprintf(stderr, "bectl create: wrong number of arguments\n");
233 		return (usage(false));
234 	}
235 
236 	bootenv = *argv;
237 
238 	err = BE_ERR_SUCCESS;
239 	if ((atpos = strchr(bootenv, '@')) != NULL) {
240 		/*
241 		 * This is the "create a snapshot variant". No new boot
242 		 * environment is to be created here.
243 		 */
244 		*atpos++ = '\0';
245 		err = be_snapshot(be, bootenv, atpos, recursive, NULL);
246 	} else {
247 		if (snapname == NULL)
248 			/* Create from currently booted BE */
249 			err = be_snapshot(be, be_active_path(be), NULL,
250 			    recursive, snapshot);
251 		else if (strchr(snapname, '@') != NULL)
252 			/* Create from given snapshot */
253 			strlcpy(snapshot, snapname, sizeof(snapshot));
254 		else
255 			/* Create from given BE */
256 			err = be_snapshot(be, snapname, NULL, recursive,
257 			    snapshot);
258 
259 		if (err == BE_ERR_SUCCESS)
260 			err = be_create_depth(be, bootenv, snapshot,
261 					      recursive == true ? -1 : 0);
262 	}
263 
264 	switch (err) {
265 	case BE_ERR_SUCCESS:
266 		break;
267 	default:
268 		if (atpos != NULL)
269 			fprintf(stderr,
270 			    "Failed to create a snapshot '%s' of '%s'\n",
271 			    atpos, bootenv);
272 		else if (snapname == NULL)
273 			fprintf(stderr,
274 			    "Failed to create bootenv %s\n", bootenv);
275 		else
276 			fprintf(stderr,
277 			    "Failed to create bootenv %s from snapshot %s\n",
278 			    bootenv, snapname);
279 	}
280 
281 	return (err);
282 }
283 
284 
285 static int
286 bectl_cmd_export(int argc, char *argv[])
287 {
288 	char *bootenv;
289 
290 	if (argc == 1) {
291 		fprintf(stderr, "bectl export: missing boot environment name\n");
292 		return (usage(false));
293 	}
294 
295 	if (argc > 2) {
296 		fprintf(stderr, "bectl export: extra arguments provided\n");
297 		return (usage(false));
298 	}
299 
300 	bootenv = argv[1];
301 
302 	if (isatty(STDOUT_FILENO)) {
303 		fprintf(stderr, "bectl export: must redirect output\n");
304 		return (EX_USAGE);
305 	}
306 
307 	be_export(be, bootenv, STDOUT_FILENO);
308 
309 	return (0);
310 }
311 
312 
313 static int
314 bectl_cmd_import(int argc, char *argv[])
315 {
316 	char *bootenv;
317 	int err;
318 
319 	if (argc == 1) {
320 		fprintf(stderr, "bectl import: missing boot environment name\n");
321 		return (usage(false));
322 	}
323 
324 	if (argc > 2) {
325 		fprintf(stderr, "bectl import: extra arguments provided\n");
326 		return (usage(false));
327 	}
328 
329 	bootenv = argv[1];
330 
331 	if (isatty(STDIN_FILENO)) {
332 		fprintf(stderr, "bectl import: input can not be from terminal\n");
333 		return (EX_USAGE);
334 	}
335 
336 	err = be_import(be, bootenv, STDIN_FILENO);
337 
338 	return (err);
339 }
340 
341 #if SOON
342 static int
343 bectl_cmd_add(int argc, char *argv[])
344 {
345 
346 	if (argc < 2) {
347 		fprintf(stderr, "bectl add: must provide at least one path\n");
348 		return (usage(false));
349 	}
350 
351 	for (int i = 1; i < argc; ++i) {
352 		printf("arg %d: %s\n", i, argv[i]);
353 		/* XXX TODO catch err */
354 		be_add_child(be, argv[i], true);
355 	}
356 
357 	return (0);
358 }
359 #endif
360 
361 static int
362 bectl_cmd_destroy(int argc, char *argv[])
363 {
364 	nvlist_t *props;
365 	char *origin, *target, targetds[BE_MAXPATHLEN];
366 	int err, flags, opt;
367 
368 	flags = 0;
369 	while ((opt = getopt(argc, argv, "Fo")) != -1) {
370 		switch (opt) {
371 		case 'F':
372 			flags |= BE_DESTROY_FORCE;
373 			break;
374 		case 'o':
375 			flags |= BE_DESTROY_ORIGIN;
376 			break;
377 		default:
378 			fprintf(stderr, "bectl destroy: unknown option '-%c'\n",
379 			    optopt);
380 			return (usage(false));
381 		}
382 	}
383 
384 	argc -= optind;
385 	argv += optind;
386 
387 	if (argc != 1) {
388 		fprintf(stderr, "bectl destroy: wrong number of arguments\n");
389 		return (usage(false));
390 	}
391 
392 	target = argv[0];
393 
394 	/* We'll emit a notice if there's an origin to be cleaned up */
395 	if ((flags & BE_DESTROY_ORIGIN) == 0 && strchr(target, '@') == NULL) {
396 		flags |= BE_DESTROY_AUTOORIGIN;
397 		if (be_root_concat(be, target, targetds) != 0)
398 			goto destroy;
399 		if (be_prop_list_alloc(&props) != 0)
400 			goto destroy;
401 		if (be_get_dataset_props(be, targetds, props) != 0) {
402 			be_prop_list_free(props);
403 			goto destroy;
404 		}
405 		if (nvlist_lookup_string(props, "origin", &origin) == 0 &&
406 		    !be_is_auto_snapshot_name(be, origin))
407 			fprintf(stderr, "bectl destroy: leaving origin '%s' intact\n",
408 			    origin);
409 		be_prop_list_free(props);
410 	}
411 
412 destroy:
413 	err = be_destroy(be, target, flags);
414 
415 	return (err);
416 }
417 
418 static int
419 bectl_cmd_mount(int argc, char *argv[])
420 {
421 	char result_loc[BE_MAXPATHLEN];
422 	char *bootenv, *mountpoint;
423 	int err, mntflags;
424 
425 	/* XXX TODO: Allow shallow */
426 	mntflags = BE_MNT_DEEP;
427 	if (argc < 2) {
428 		fprintf(stderr, "bectl mount: missing argument(s)\n");
429 		return (usage(false));
430 	}
431 
432 	if (argc > 3) {
433 		fprintf(stderr, "bectl mount: too many arguments\n");
434 		return (usage(false));
435 	}
436 
437 	bootenv = argv[1];
438 	mountpoint = ((argc == 3) ? argv[2] : NULL);
439 
440 	err = be_mount(be, bootenv, mountpoint, mntflags, result_loc);
441 
442 	switch (err) {
443 	case BE_ERR_SUCCESS:
444 		printf("Successfully mounted %s at %s\n", bootenv, result_loc);
445 		break;
446 	default:
447 		fprintf(stderr,
448 		    (argc == 3) ? "Failed to mount bootenv %s at %s\n" :
449 		    "Failed to mount bootenv %s at temporary path %s\n",
450 		    bootenv, mountpoint);
451 	}
452 
453 	return (err);
454 }
455 
456 
457 static int
458 bectl_cmd_rename(int argc, char *argv[])
459 {
460 	char *dest, *src;
461 	int err;
462 
463 	if (argc < 3) {
464 		fprintf(stderr, "bectl rename: missing argument\n");
465 		return (usage(false));
466 	}
467 
468 	if (argc > 3) {
469 		fprintf(stderr, "bectl rename: too many arguments\n");
470 		return (usage(false));
471 	}
472 
473 	src = argv[1];
474 	dest = argv[2];
475 
476 	err = be_rename(be, src, dest);
477 
478 	switch (err) {
479 	case BE_ERR_SUCCESS:
480 		break;
481 	default:
482 		fprintf(stderr, "Failed to rename bootenv %s to %s\n",
483 		    src, dest);
484 	}
485 
486 	return (0);
487 }
488 
489 static int
490 bectl_cmd_unmount(int argc, char *argv[])
491 {
492 	char *bootenv, *cmd;
493 	int err, flags, opt;
494 
495 	/* Store alias used */
496 	cmd = argv[0];
497 
498 	flags = 0;
499 	while ((opt = getopt(argc, argv, "f")) != -1) {
500 		switch (opt) {
501 		case 'f':
502 			flags |= BE_MNT_FORCE;
503 			break;
504 		default:
505 			fprintf(stderr, "bectl %s: unknown option '-%c'\n",
506 			    cmd, optopt);
507 			return (usage(false));
508 		}
509 	}
510 
511 	argc -= optind;
512 	argv += optind;
513 
514 	if (argc != 1) {
515 		fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
516 		return (usage(false));
517 	}
518 
519 	bootenv = argv[0];
520 
521 	err = be_unmount(be, bootenv, flags);
522 
523 	switch (err) {
524 	case BE_ERR_SUCCESS:
525 		break;
526 	default:
527 		fprintf(stderr, "Failed to unmount bootenv %s\n", bootenv);
528 	}
529 
530 	return (err);
531 }
532 
533 static int
534 bectl_cmd_check(int argc, char *argv[] __unused)
535 {
536 
537 	/* The command is left as argv[0] */
538 	if (argc != 1) {
539 		fprintf(stderr, "bectl check: wrong number of arguments\n");
540 		return (usage(false));
541 	}
542 
543 	return (0);
544 }
545 
546 int
547 main(int argc, char *argv[])
548 {
549 	struct command_map_entry *cmd;
550 	const char *command;
551 	char *root;
552 	int rc;
553 
554 	cmd = NULL;
555 	root = NULL;
556 	if (argc < 2)
557 		return (usage(false));
558 
559 	if (strcmp(argv[1], "-r") == 0) {
560 		if (argc < 4)
561 			return (usage(false));
562 		root = strdup(argv[2]);
563 		command = argv[3];
564 		argc -= 3;
565 		argv += 3;
566 	} else {
567 		command = argv[1];
568 		argc -= 1;
569 		argv += 1;
570 	}
571 
572 	/* Handle command aliases */
573 	if (strcmp(command, "umount") == 0)
574 		command = "unmount";
575 
576 	if (strcmp(command, "ujail") == 0)
577 		command = "unjail";
578 
579 	if ((strcmp(command, "-?") == 0) || (strcmp(command, "-h") == 0))
580 		return (usage(true));
581 
582 	if ((cmd = get_cmd_info(command)) == NULL) {
583 		fprintf(stderr, "Unknown command: %s\n", command);
584 		return (usage(false));
585 	}
586 
587 	if ((be = libbe_init(root)) == NULL)
588 		return (-1);
589 
590 	libbe_print_on_error(be, !cmd->silent);
591 
592 	rc = cmd->fn(argc, argv);
593 
594 	libbe_close(be);
595 	return (rc);
596 }
597