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