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