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