xref: /freebsd/sbin/bectl/bectl.c (revision 0957b409)
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_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 create [-r] [-e {nonActiveBe | beName@snapshot}] beName\n"
76 	    "\tbectl create [-r] beName@snapshot\n"
77 	    "\tbectl destroy [-F] {beName | beName@snapshot}\n"
78 	    "\tbectl export sourceBe\n"
79 	    "\tbectl import targetBe\n"
80 	    "\tbectl jail {-b | -U} [{-o key=value | -u key}]... "
81 	    "{jailID | jailName}\n"
82 	    "\t      bootenv [utility [argument ...]]\n"
83 	    "\tbectl list [-DHas]\n"
84 	    "\tbectl mount beName [mountpoint]\n"
85 	    "\tbectl rename origBeName newBeName\n"
86 	    "\tbectl {ujail | unjail} {jailID | jailName} bootenv\n"
87 	    "\tbectl {umount | unmount} [-f] beName\n");
88 
89 	return (explicit ? 0 : EX_USAGE);
90 }
91 
92 
93 /*
94  * Represents a relationship between the command name and the parser action
95  * that handles it.
96  */
97 struct command_map_entry {
98 	const char *command;
99 	int (*fn)(int argc, char *argv[]);
100 };
101 
102 static struct command_map_entry command_map[] =
103 {
104 	{ "activate", bectl_cmd_activate },
105 	{ "create",   bectl_cmd_create   },
106 	{ "destroy",  bectl_cmd_destroy  },
107 	{ "export",   bectl_cmd_export   },
108 	{ "import",   bectl_cmd_import   },
109 #if SOON
110 	{ "add",      bectl_cmd_add      },
111 #endif
112 	{ "jail",     bectl_cmd_jail     },
113 	{ "list",     bectl_cmd_list     },
114 	{ "mount",    bectl_cmd_mount    },
115 	{ "rename",   bectl_cmd_rename   },
116 	{ "unjail",   bectl_cmd_unjail   },
117 	{ "unmount",  bectl_cmd_unmount  },
118 };
119 
120 static int
121 get_cmd_index(const char *cmd, int *idx)
122 {
123 	int map_size;
124 
125 	map_size = nitems(command_map);
126 	for (int i = 0; i < map_size; ++i) {
127 		if (strcmp(cmd, command_map[i].command) == 0) {
128 			*idx = i;
129 			return (0);
130 		}
131 	}
132 
133 	return (1);
134 }
135 
136 
137 static int
138 bectl_cmd_activate(int argc, char *argv[])
139 {
140 	int err, opt;
141 	bool temp;
142 
143 	temp = false;
144 	while ((opt = getopt(argc, argv, "t")) != -1) {
145 		switch (opt) {
146 		case 't':
147 			temp = true;
148 			break;
149 		default:
150 			fprintf(stderr, "bectl activate: unknown option '-%c'\n",
151 			    optopt);
152 			return (usage(false));
153 		}
154 	}
155 
156 	argc -= optind;
157 	argv += optind;
158 
159 	if (argc != 1) {
160 		fprintf(stderr, "bectl activate: wrong number of arguments\n");
161 		return (usage(false));
162 	}
163 
164 
165 	/* activate logic goes here */
166 	if ((err = be_activate(be, argv[0], temp)) != 0)
167 		/* XXX TODO: more specific error msg based on err */
168 		printf("did not successfully activate boot environment %s\n",
169 		    argv[0]);
170 	else
171 		printf("successfully activated boot environment %s\n", argv[0]);
172 
173 	if (temp)
174 		printf("for next boot\n");
175 
176 	return (err);
177 }
178 
179 
180 /*
181  * TODO: when only one arg is given, and it contains an "@" the this should
182  * create that snapshot
183  */
184 static int
185 bectl_cmd_create(int argc, char *argv[])
186 {
187 	char *atpos, *bootenv, *snapname, *source;
188 	int err, opt;
189 	bool recursive;
190 
191 	snapname = NULL;
192 	recursive = false;
193 	while ((opt = getopt(argc, argv, "e:r")) != -1) {
194 		switch (opt) {
195 		case 'e':
196 			snapname = optarg;
197 			break;
198 		case 'r':
199 			recursive = true;
200 			break;
201 		default:
202 			fprintf(stderr, "bectl create: unknown option '-%c'\n",
203 			    optopt);
204 			return (usage(false));
205 		}
206 	}
207 
208 	argc -= optind;
209 	argv += optind;
210 
211 	if (argc != 1) {
212 		fprintf(stderr, "bectl create: wrong number of arguments\n");
213 		return (usage(false));
214 	}
215 
216 	bootenv = *argv;
217 	if ((atpos = strchr(bootenv, '@')) != NULL) {
218 		/*
219 		 * This is the "create a snapshot variant". No new boot
220 		 * environment is to be created here.
221 		 */
222 		*atpos++ = '\0';
223 		err = be_snapshot(be, bootenv, atpos, recursive, NULL);
224 	} else if (snapname != NULL) {
225 		if (strchr(snapname, '@') != NULL)
226 			err = be_create_from_existing_snap(be, bootenv,
227 			    snapname);
228 		else
229 			err = be_create_from_existing(be, bootenv, snapname);
230 	} else {
231 		if ((snapname = strchr(bootenv, '@')) != NULL) {
232 			*(snapname++) = '\0';
233 			if ((err = be_snapshot(be, be_active_path(be),
234 			    snapname, true, NULL)) != BE_ERR_SUCCESS)
235 				fprintf(stderr, "failed to create snapshot\n");
236 			asprintf(&source, "%s@%s", be_active_path(be), snapname);
237 			err = be_create_from_existing_snap(be, bootenv,
238 			    source);
239 			return (err);
240 		} else
241 			err = be_create(be, bootenv);
242 	}
243 
244 	switch (err) {
245 	case BE_ERR_SUCCESS:
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
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
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
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
342 bectl_cmd_destroy(int argc, char *argv[])
343 {
344 	nvlist_t *props;
345 	char *origin, *target, targetds[BE_MAXPATHLEN];
346 	int err, flags, opt;
347 
348 	flags = 0;
349 	while ((opt = getopt(argc, argv, "Fo")) != -1) {
350 		switch (opt) {
351 		case 'F':
352 			flags |= BE_DESTROY_FORCE;
353 			break;
354 		case 'o':
355 			flags |= BE_DESTROY_ORIGIN;
356 			break;
357 		default:
358 			fprintf(stderr, "bectl destroy: unknown option '-%c'\n",
359 			    optopt);
360 			return (usage(false));
361 		}
362 	}
363 
364 	argc -= optind;
365 	argv += optind;
366 
367 	if (argc != 1) {
368 		fprintf(stderr, "bectl destroy: wrong number of arguments\n");
369 		return (usage(false));
370 	}
371 
372 	target = argv[0];
373 
374 	/* We'll emit a notice if there's an origin to be cleaned up */
375 	if ((flags & BE_DESTROY_ORIGIN) == 0 && strchr(target, '@') == NULL) {
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 			fprintf(stderr, "bectl destroy: leaving origin '%s' intact\n",
386 			    origin);
387 		be_prop_list_free(props);
388 	}
389 
390 destroy:
391 	err = be_destroy(be, target, flags);
392 
393 	return (err);
394 }
395 
396 static int
397 bectl_cmd_mount(int argc, char *argv[])
398 {
399 	char result_loc[BE_MAXPATHLEN];
400 	char *bootenv, *mountpoint;
401 	int err, mntflags;
402 
403 	/* XXX TODO: Allow shallow */
404 	mntflags = BE_MNT_DEEP;
405 	if (argc < 2) {
406 		fprintf(stderr, "bectl mount: missing argument(s)\n");
407 		return (usage(false));
408 	}
409 
410 	if (argc > 3) {
411 		fprintf(stderr, "bectl mount: too many arguments\n");
412 		return (usage(false));
413 	}
414 
415 	bootenv = argv[1];
416 	mountpoint = ((argc == 3) ? argv[2] : NULL);
417 
418 	err = be_mount(be, bootenv, mountpoint, mntflags, result_loc);
419 
420 	switch (err) {
421 	case BE_ERR_SUCCESS:
422 		printf("successfully mounted %s at %s\n", bootenv, result_loc);
423 		break;
424 	default:
425 		fprintf(stderr,
426 		    (argc == 3) ? "failed to mount bootenv %s at %s\n" :
427 		    "failed to mount bootenv %s at temporary path %s\n",
428 		    bootenv, mountpoint);
429 	}
430 
431 	return (err);
432 }
433 
434 
435 static int
436 bectl_cmd_rename(int argc, char *argv[])
437 {
438 	char *dest, *src;
439 	int err;
440 
441 	if (argc < 3) {
442 		fprintf(stderr, "bectl rename: missing argument\n");
443 		return (usage(false));
444 	}
445 
446 	if (argc > 3) {
447 		fprintf(stderr, "bectl rename: too many arguments\n");
448 		return (usage(false));
449 	}
450 
451 	src = argv[1];
452 	dest = argv[2];
453 
454 	err = be_rename(be, src, dest);
455 
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 (0);
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 
512 int
513 main(int argc, char *argv[])
514 {
515 	const char *command;
516 	char *root;
517 	int command_index, rc;
518 
519 	root = NULL;
520 	if (argc < 2)
521 		return (usage(false));
522 
523 	if (strcmp(argv[1], "-r") == 0) {
524 		if (argc < 4)
525 			return (usage(false));
526 		root = strdup(argv[2]);
527 		command = argv[3];
528 		argc -= 3;
529 		argv += 3;
530 	} else {
531 		command = argv[1];
532 		argc -= 1;
533 		argv += 1;
534 	}
535 
536 	/* Handle command aliases */
537 	if (strcmp(command, "umount") == 0)
538 		command = "unmount";
539 
540 	if (strcmp(command, "ujail") == 0)
541 		command = "unjail";
542 
543 	if ((strcmp(command, "-?") == 0) || (strcmp(command, "-h") == 0))
544 		return (usage(true));
545 
546 	if (get_cmd_index(command, &command_index)) {
547 		fprintf(stderr, "unknown command: %s\n", command);
548 		return (usage(false));
549 	}
550 
551 
552 	if ((be = libbe_init(root)) == NULL)
553 		return (-1);
554 
555 	libbe_print_on_error(be, true);
556 
557 	rc = command_map[command_index].fn(argc, argv);
558 
559 	libbe_close(be);
560 	return (rc);
561 }
562