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