xref: /freebsd/sbin/bectl/bectl.c (revision 63cbe8d1d95f97e93929ec66f1138693d08dd9f6)
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,
70 	    "usage:\tbectl {-h | -? | subcommand [args...]}\n"
71 	    "\tbectl activate [-t] beName\n"
72 	    "\tbectl create [-e {nonActiveBe | -e beName@snapshot}] beName\n"
73 	    "\tbectl create beName@snapshot\n"
74 	    "\tbectl destroy [-F] {beName | beName@snapshot}\n"
75 	    "\tbectl export sourceBe\n"
76 	    "\tbectl import targetBe\n"
77 #if SOON
78 	    "\tbectl add (path)*\n"
79 #endif
80 	    "\tbectl jail [{-b | -U}] [{-o key=value | -u key}]... bootenv [utility [argument ...]]\n"
81 	    "\tbectl list [-a] [-D] [-H] [-s]\n"
82 	    "\tbectl mount beName [mountpoint]\n"
83 	    "\tbectl rename origBeName newBeName\n"
84 	    "\tbectl {ujail | unjail} ⟨jailID | jailName | bootenv)\n"
85 	    "\tbectl {umount | unmount} [-f] beName\n");
86 
87 	return (explicit ? 0 : EX_USAGE);
88 }
89 
90 
91 /*
92  * Represents a relationship between the command name and the parser action
93  * that handles it.
94  */
95 struct command_map_entry {
96 	const char *command;
97 	int (*fn)(int argc, char *argv[]);
98 };
99 
100 static struct command_map_entry command_map[] =
101 {
102 	{ "activate", bectl_cmd_activate },
103 	{ "create",   bectl_cmd_create   },
104 	{ "destroy",  bectl_cmd_destroy  },
105 	{ "export",   bectl_cmd_export   },
106 	{ "import",   bectl_cmd_import   },
107 #if SOON
108 	{ "add",      bectl_cmd_add      },
109 #endif
110 	{ "jail",     bectl_cmd_jail     },
111 	{ "list",     bectl_cmd_list     },
112 	{ "mount",    bectl_cmd_mount    },
113 	{ "rename",   bectl_cmd_rename   },
114 	{ "unjail",   bectl_cmd_unjail   },
115 	{ "unmount",  bectl_cmd_unmount  },
116 };
117 
118 static int
119 get_cmd_index(const char *cmd, int *idx)
120 {
121 	int map_size;
122 
123 	map_size = nitems(command_map);
124 	for (int i = 0; i < map_size; ++i) {
125 		if (strcmp(cmd, command_map[i].command) == 0) {
126 			*idx = i;
127 			return (0);
128 		}
129 	}
130 
131 	return (1);
132 }
133 
134 
135 static int
136 bectl_cmd_activate(int argc, char *argv[])
137 {
138 	int err, opt;
139 	bool temp;
140 
141 	temp = false;
142 	while ((opt = getopt(argc, argv, "t")) != -1) {
143 		switch (opt) {
144 		case 't':
145 			temp = true;
146 			break;
147 		default:
148 			fprintf(stderr, "bectl activate: unknown option '-%c'\n",
149 			    optopt);
150 			return (usage(false));
151 		}
152 	}
153 
154 	argc -= optind;
155 	argv += optind;
156 
157 	if (argc != 1) {
158 		fprintf(stderr, "bectl activate: wrong number of arguments\n");
159 		return (usage(false));
160 	}
161 
162 
163 	/* activate logic goes here */
164 	if ((err = be_activate(be, argv[0], temp)) != 0)
165 		/* XXX TODO: more specific error msg based on err */
166 		printf("did not successfully activate boot environment %s\n",
167 		    argv[0]);
168 	else
169 		printf("successfully activated boot environment %s\n", argv[0]);
170 
171 	if (temp)
172 		printf("for next boot\n");
173 
174 	return (err);
175 }
176 
177 
178 /*
179  * TODO: when only one arg is given, and it contains an "@" the this should
180  * create that snapshot
181  */
182 static int
183 bectl_cmd_create(int argc, char *argv[])
184 {
185 	char *atpos, *bootenv, *snapname, *source;
186 	int err, opt;
187 	bool recursive;
188 
189 	snapname = NULL;
190 	recursive = false;
191 	while ((opt = getopt(argc, argv, "e:r")) != -1) {
192 		switch (opt) {
193 		case 'e':
194 			snapname = optarg;
195 			break;
196 		case 'r':
197 			recursive = true;
198 			break;
199 		default:
200 			fprintf(stderr, "bectl create: unknown option '-%c'\n",
201 			    optopt);
202 			return (usage(false));
203 		}
204 	}
205 
206 	argc -= optind;
207 	argv += optind;
208 
209 	if (argc != 1) {
210 		fprintf(stderr, "bectl create: wrong number of arguments\n");
211 		return (usage(false));
212 	}
213 
214 	bootenv = *argv;
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 if (snapname != NULL) {
223 		if (strchr(snapname, '@') != NULL)
224 			err = be_create_from_existing_snap(be, bootenv,
225 			    snapname);
226 		else
227 			err = be_create_from_existing(be, bootenv, snapname);
228 	} else {
229 		if ((snapname = strchr(bootenv, '@')) != NULL) {
230 			*(snapname++) = '\0';
231 			if ((err = be_snapshot(be, be_active_path(be),
232 			    snapname, true, NULL)) != BE_ERR_SUCCESS)
233 				fprintf(stderr, "failed to create snapshot\n");
234 			asprintf(&source, "%s@%s", be_active_path(be), snapname);
235 			err = be_create_from_existing_snap(be, bootenv,
236 			    source);
237 			return (err);
238 		} else
239 			err = be_create(be, bootenv);
240 	}
241 
242 	switch (err) {
243 	case BE_ERR_SUCCESS:
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 	char *target;
343 	int opt, err;
344 	bool force;
345 
346 	force = false;
347 	while ((opt = getopt(argc, argv, "F")) != -1) {
348 		switch (opt) {
349 		case 'F':
350 			force = true;
351 			break;
352 		default:
353 			fprintf(stderr, "bectl destroy: unknown option '-%c'\n",
354 			    optopt);
355 			return (usage(false));
356 		}
357 	}
358 
359 	argc -= optind;
360 	argv += optind;
361 
362 	if (argc != 1) {
363 		fprintf(stderr, "bectl destroy: wrong number of arguments\n");
364 		return (usage(false));
365 	}
366 
367 	target = argv[0];
368 
369 	err = be_destroy(be, target, force);
370 
371 	return (err);
372 }
373 
374 static int
375 bectl_cmd_mount(int argc, char *argv[])
376 {
377 	char result_loc[BE_MAXPATHLEN];
378 	char *bootenv, *mountpoint;
379 	int err;
380 
381 	if (argc < 2) {
382 		fprintf(stderr, "bectl mount: missing argument(s)\n");
383 		return (usage(false));
384 	}
385 
386 	if (argc > 3) {
387 		fprintf(stderr, "bectl mount: too many arguments\n");
388 		return (usage(false));
389 	}
390 
391 	bootenv = argv[1];
392 	mountpoint = ((argc == 3) ? argv[2] : NULL);
393 
394 	err = be_mount(be, bootenv, mountpoint, 0, result_loc);
395 
396 	switch (err) {
397 	case BE_ERR_SUCCESS:
398 		printf("successfully mounted %s at %s\n", bootenv, result_loc);
399 		break;
400 	default:
401 		fprintf(stderr,
402 		    (argc == 3) ? "failed to mount bootenv %s at %s\n" :
403 		    "failed to mount bootenv %s at temporary path %s\n",
404 		    bootenv, mountpoint);
405 	}
406 
407 	return (err);
408 }
409 
410 
411 static int
412 bectl_cmd_rename(int argc, char *argv[])
413 {
414 	char *dest, *src;
415 	int err;
416 
417 	if (argc < 3) {
418 		fprintf(stderr, "bectl rename: missing argument\n");
419 		return (usage(false));
420 	}
421 
422 	if (argc > 3) {
423 		fprintf(stderr, "bectl rename: too many arguments\n");
424 		return (usage(false));
425 	}
426 
427 	src = argv[1];
428 	dest = argv[2];
429 
430 	err = be_rename(be, src, dest);
431 
432 	switch (err) {
433 	case BE_ERR_SUCCESS:
434 		break;
435 	default:
436 		fprintf(stderr, "failed to rename bootenv %s to %s\n",
437 		    src, dest);
438 	}
439 
440 	return (0);
441 }
442 
443 static int
444 bectl_cmd_unmount(int argc, char *argv[])
445 {
446 	char *bootenv, *cmd;
447 	int err, flags, opt;
448 
449 	/* Store alias used */
450 	cmd = argv[0];
451 
452 	flags = 0;
453 	while ((opt = getopt(argc, argv, "f")) != -1) {
454 		switch (opt) {
455 		case 'f':
456 			flags |= BE_MNT_FORCE;
457 			break;
458 		default:
459 			fprintf(stderr, "bectl %s: unknown option '-%c'\n",
460 			    cmd, optopt);
461 			return (usage(false));
462 		}
463 	}
464 
465 	argc -= optind;
466 	argv += optind;
467 
468 	if (argc != 1) {
469 		fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
470 		return (usage(false));
471 	}
472 
473 	bootenv = argv[0];
474 
475 	err = be_unmount(be, bootenv, flags);
476 
477 	switch (err) {
478 	case BE_ERR_SUCCESS:
479 		break;
480 	default:
481 		fprintf(stderr, "failed to unmount bootenv %s\n", bootenv);
482 	}
483 
484 	return (err);
485 }
486 
487 
488 int
489 main(int argc, char *argv[])
490 {
491 	const char *command;
492 	char *root;
493 	int command_index, rc;
494 
495 	root = NULL;
496 	if (argc < 2)
497 		return (usage(false));
498 
499 	if (strcmp(argv[1], "-r") == 0) {
500 		if (argc < 4)
501 			return (usage(false));
502 		root = strdup(argv[2]);
503 		command = argv[3];
504 		argc -= 3;
505 		argv += 3;
506 	} else {
507 		command = argv[1];
508 		argc -= 1;
509 		argv += 1;
510 	}
511 
512 	/* Handle command aliases */
513 	if (strcmp(command, "umount") == 0)
514 		command = "unmount";
515 
516 	if (strcmp(command, "ujail") == 0)
517 		command = "unjail";
518 
519 	if ((strcmp(command, "-?") == 0) || (strcmp(command, "-h") == 0))
520 		return (usage(true));
521 
522 	if (get_cmd_index(command, &command_index)) {
523 		fprintf(stderr, "unknown command: %s\n", command);
524 		return (usage(false));
525 	}
526 
527 
528 	if ((be = libbe_init(root)) == NULL)
529 		return (-1);
530 
531 	libbe_print_on_error(be, true);
532 
533 	rc = command_map[command_index].fn(argc, argv);
534 
535 	libbe_close(be);
536 	return (rc);
537 }
538