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