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