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