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