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