xref: /freebsd/sbin/bectl/bectl.c (revision 8aac90f18aef7c9eea906c3ff9a001ca7b94f375)
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 	{ "ujail",    bectl_cmd_unjail,  false   },
118 	{ "unmount",  bectl_cmd_unmount, false   },
119 	{ "umount",   bectl_cmd_unmount, false   },
120 	{ "check",    bectl_cmd_check,   true    },
121 };
122 
123 static struct command_map_entry *
124 get_cmd_info(const char *cmd)
125 {
126 	size_t i;
127 
128 	for (i = 0; i < nitems(command_map); ++i) {
129 		if (strcmp(cmd, command_map[i].command) == 0)
130 			return (&command_map[i]);
131 	}
132 
133 	return (NULL);
134 }
135 
136 static int
137 bectl_cmd_activate(int argc, char *argv[])
138 {
139 	int err, opt;
140 	bool temp, reset;
141 
142 	temp = false;
143 	reset = false;
144 	while ((opt = getopt(argc, argv, "tT")) != -1) {
145 		switch (opt) {
146 		case 't':
147 			if (reset)
148 				return (usage(false));
149 			temp = true;
150 			break;
151 		case 'T':
152 			if (temp)
153 				return (usage(false));
154 			reset = true;
155 			break;
156 		default:
157 			fprintf(stderr, "bectl activate: unknown option '-%c'\n",
158 			    optopt);
159 			return (usage(false));
160 		}
161 	}
162 
163 	argc -= optind;
164 	argv += optind;
165 
166 	if (argc != 1 && (!reset || argc != 0)) {
167 		fprintf(stderr, "bectl activate: wrong number of arguments\n");
168 		return (usage(false));
169 	}
170 
171 	if (reset) {
172 		if ((err = be_deactivate(be, NULL, reset)) == 0)
173 			printf("Temporary activation removed\n");
174 		else
175 			printf("Failed to remove temporary activation\n");
176 		return (err);
177 	}
178 
179 	/* activate logic goes here */
180 	if ((err = be_activate(be, argv[0], temp)) != 0)
181 		/* XXX TODO: more specific error msg based on err */
182 		printf("Did not successfully activate boot environment %s\n",
183 		    argv[0]);
184 	else
185 		printf("Successfully activated boot environment %s\n", argv[0]);
186 
187 	if (temp)
188 		printf("for next boot\n");
189 
190 	return (err);
191 }
192 
193 
194 /*
195  * TODO: when only one arg is given, and it contains an "@" the this should
196  * create that snapshot
197  */
198 static int
199 bectl_cmd_create(int argc, char *argv[])
200 {
201 	char snapshot[BE_MAXPATHLEN];
202 	char *atpos, *bootenv, *snapname;
203 	int err, opt;
204 	bool recursive;
205 
206 	snapname = NULL;
207 	recursive = false;
208 	while ((opt = getopt(argc, argv, "e:r")) != -1) {
209 		switch (opt) {
210 		case 'e':
211 			snapname = optarg;
212 			break;
213 		case 'r':
214 			recursive = true;
215 			break;
216 		default:
217 			fprintf(stderr, "bectl create: unknown option '-%c'\n",
218 			    optopt);
219 			return (usage(false));
220 		}
221 	}
222 
223 	argc -= optind;
224 	argv += optind;
225 
226 	if (argc != 1) {
227 		fprintf(stderr, "bectl create: wrong number of arguments\n");
228 		return (usage(false));
229 	}
230 
231 	bootenv = *argv;
232 
233 	err = BE_ERR_SUCCESS;
234 	if ((atpos = strchr(bootenv, '@')) != NULL) {
235 		/*
236 		 * This is the "create a snapshot variant". No new boot
237 		 * environment is to be created here.
238 		 */
239 		*atpos++ = '\0';
240 		err = be_snapshot(be, bootenv, atpos, recursive, NULL);
241 	} else {
242 		if (snapname == NULL)
243 			/* Create from currently booted BE */
244 			err = be_snapshot(be, be_active_path(be), NULL,
245 			    recursive, snapshot);
246 		else if (strchr(snapname, '@') != NULL)
247 			/* Create from given snapshot */
248 			strlcpy(snapshot, snapname, sizeof(snapshot));
249 		else
250 			/* Create from given BE */
251 			err = be_snapshot(be, snapname, NULL, recursive,
252 			    snapshot);
253 
254 		if (err == BE_ERR_SUCCESS)
255 			err = be_create_depth(be, bootenv, snapshot,
256 					      recursive == true ? -1 : 0);
257 	}
258 
259 	switch (err) {
260 	case BE_ERR_SUCCESS:
261 		break;
262 	case BE_ERR_INVALIDNAME:
263 		fprintf(stderr,
264 		    "bectl create: boot environment name must not contain spaces\n");
265 		break;
266 	default:
267 		if (atpos != NULL)
268 			fprintf(stderr,
269 			    "Failed to create a snapshot '%s' of '%s'\n",
270 			    atpos, bootenv);
271 		else if (snapname == NULL)
272 			fprintf(stderr,
273 			    "Failed to create bootenv %s\n", bootenv);
274 		else
275 			fprintf(stderr,
276 			    "Failed to create bootenv %s from snapshot %s\n",
277 			    bootenv, snapname);
278 	}
279 
280 	return (err);
281 }
282 
283 
284 static int
285 bectl_cmd_export(int argc, char *argv[])
286 {
287 	char *bootenv;
288 
289 	if (argc == 1) {
290 		fprintf(stderr, "bectl export: missing boot environment name\n");
291 		return (usage(false));
292 	}
293 
294 	if (argc > 2) {
295 		fprintf(stderr, "bectl export: extra arguments provided\n");
296 		return (usage(false));
297 	}
298 
299 	bootenv = argv[1];
300 
301 	if (isatty(STDOUT_FILENO)) {
302 		fprintf(stderr, "bectl export: must redirect output\n");
303 		return (EX_USAGE);
304 	}
305 
306 	be_export(be, bootenv, STDOUT_FILENO);
307 
308 	return (0);
309 }
310 
311 
312 static int
313 bectl_cmd_import(int argc, char *argv[])
314 {
315 	char *bootenv;
316 	int err;
317 
318 	if (argc == 1) {
319 		fprintf(stderr, "bectl import: missing boot environment name\n");
320 		return (usage(false));
321 	}
322 
323 	if (argc > 2) {
324 		fprintf(stderr, "bectl import: extra arguments provided\n");
325 		return (usage(false));
326 	}
327 
328 	bootenv = argv[1];
329 
330 	if (isatty(STDIN_FILENO)) {
331 		fprintf(stderr, "bectl import: input can not be from terminal\n");
332 		return (EX_USAGE);
333 	}
334 
335 	err = be_import(be, bootenv, STDIN_FILENO);
336 
337 	return (err);
338 }
339 
340 #if SOON
341 static int
342 bectl_cmd_add(int argc, char *argv[])
343 {
344 
345 	if (argc < 2) {
346 		fprintf(stderr, "bectl add: must provide at least one path\n");
347 		return (usage(false));
348 	}
349 
350 	for (int i = 1; i < argc; ++i) {
351 		printf("arg %d: %s\n", i, argv[i]);
352 		/* XXX TODO catch err */
353 		be_add_child(be, argv[i], true);
354 	}
355 
356 	return (0);
357 }
358 #endif
359 
360 static int
361 bectl_cmd_destroy(int argc, char *argv[])
362 {
363 	nvlist_t *props;
364 	char *target, targetds[BE_MAXPATHLEN];
365 	const char *origin;
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("%s\n", 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 	switch (err) {
478 	case BE_ERR_SUCCESS:
479 		break;
480 	default:
481 		fprintf(stderr, "Failed to rename bootenv %s to %s\n",
482 		    src, dest);
483 	}
484 
485 	return (err);
486 }
487 
488 static int
489 bectl_cmd_unmount(int argc, char *argv[])
490 {
491 	char *bootenv, *cmd;
492 	int err, flags, opt;
493 
494 	/* Store alias used */
495 	cmd = argv[0];
496 
497 	flags = 0;
498 	while ((opt = getopt(argc, argv, "f")) != -1) {
499 		switch (opt) {
500 		case 'f':
501 			flags |= BE_MNT_FORCE;
502 			break;
503 		default:
504 			fprintf(stderr, "bectl %s: unknown option '-%c'\n",
505 			    cmd, optopt);
506 			return (usage(false));
507 		}
508 	}
509 
510 	argc -= optind;
511 	argv += optind;
512 
513 	if (argc != 1) {
514 		fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
515 		return (usage(false));
516 	}
517 
518 	bootenv = argv[0];
519 
520 	err = be_unmount(be, bootenv, flags);
521 
522 	switch (err) {
523 	case BE_ERR_SUCCESS:
524 		break;
525 	default:
526 		fprintf(stderr, "Failed to unmount bootenv %s\n", bootenv);
527 	}
528 
529 	return (err);
530 }
531 
532 static int
533 bectl_cmd_check(int argc, char *argv[] __unused)
534 {
535 
536 	/* The command is left as argv[0] */
537 	if (argc != 1) {
538 		fprintf(stderr, "bectl check: wrong number of arguments\n");
539 		return (usage(false));
540 	}
541 
542 	return (0);
543 }
544 
545 int
546 main(int argc, char *argv[])
547 {
548 	struct command_map_entry *cmd;
549 	const char *command;
550 	char *root = NULL;
551 	int opt, rc;
552 
553 	while ((opt = getopt(argc, argv, "hr:")) != -1) {
554 		switch (opt) {
555 		case 'h':
556 			exit(usage(true));
557 		case 'r':
558 			root = strdup(optarg);
559 			break;
560 		default:
561 			exit(usage(false));
562 		}
563 	}
564 
565 	argc -= optind;
566 	argv += optind;
567 
568 	if (argc == 0)
569 		exit(usage(false));
570 
571 	command = *argv;
572 	optreset = 1;
573 	optind = 1;
574 
575 	if ((cmd = get_cmd_info(command)) == NULL) {
576 		fprintf(stderr, "Unknown command: %s\n", command);
577 		return (usage(false));
578 	}
579 
580 	if ((be = libbe_init(root)) == NULL) {
581 		if (!cmd->silent)
582 			fprintf(stderr, "libbe_init(\"%s\") failed.\n",
583 			    root != NULL ? root : "");
584 		return (-1);
585 	}
586 
587 	libbe_print_on_error(be, !cmd->silent);
588 
589 	rc = cmd->fn(argc, argv);
590 
591 	libbe_close(be);
592 	return (rc);
593 }
594