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