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
64usage(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 activate [-T]\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 [-Fo] {beName | beName@snapshot}\n"
80	    "\tbectl export sourceBe\n"
81	    "\tbectl import targetBe\n"
82	    "\tbectl jail [-bU] [{-o key=value | -u key}]... beName\n"
83	    "\t      [utility [argument ...]]\n"
84	    "\tbectl list [-aDHs] [{-c property | -C property}]\n"
85	    "\tbectl mount beName [mountpoint]\n"
86	    "\tbectl rename origBeName newBeName\n"
87	    "\tbectl {ujail | unjail} {jailID | jailName | beName}\n"
88	    "\tbectl {umount | unmount} [-f] beName\n");
89
90	return (explicit ? 0 : EX_USAGE);
91}
92
93
94/*
95 * Represents a relationship between the command name and the parser action
96 * that handles it.
97 */
98struct command_map_entry {
99	const char *command;
100	int (*fn)(int argc, char *argv[]);
101	/* True if libbe_print_on_error should be disabled */
102	bool silent;
103};
104
105static struct command_map_entry command_map[] =
106{
107	{ "activate", bectl_cmd_activate,false   },
108	{ "create",   bectl_cmd_create,  false   },
109	{ "destroy",  bectl_cmd_destroy, false   },
110	{ "export",   bectl_cmd_export,  false   },
111	{ "import",   bectl_cmd_import,  false   },
112#if SOON
113	{ "add",      bectl_cmd_add,     false   },
114#endif
115	{ "jail",     bectl_cmd_jail,    false   },
116	{ "list",     bectl_cmd_list,    false   },
117	{ "mount",    bectl_cmd_mount,   false   },
118	{ "rename",   bectl_cmd_rename,  false   },
119	{ "unjail",   bectl_cmd_unjail,  false   },
120	{ "unmount",  bectl_cmd_unmount, false   },
121	{ "check",    bectl_cmd_check,   true    },
122};
123
124static struct command_map_entry *
125get_cmd_info(const char *cmd)
126{
127	size_t i;
128
129	for (i = 0; i < nitems(command_map); ++i) {
130		if (strcmp(cmd, command_map[i].command) == 0)
131			return (&command_map[i]);
132	}
133
134	return (NULL);
135}
136
137
138static int
139bectl_cmd_activate(int argc, char *argv[])
140{
141	int err, opt;
142	bool temp, reset;
143
144	temp = false;
145	reset = false;
146	while ((opt = getopt(argc, argv, "tT")) != -1) {
147		switch (opt) {
148		case 't':
149			if (reset)
150				return (usage(false));
151			temp = true;
152			break;
153		case 'T':
154			if (temp)
155				return (usage(false));
156			reset = true;
157			break;
158		default:
159			fprintf(stderr, "bectl activate: unknown option '-%c'\n",
160			    optopt);
161			return (usage(false));
162		}
163	}
164
165	argc -= optind;
166	argv += optind;
167
168	if (argc != 1 && (!reset || argc != 0)) {
169		fprintf(stderr, "bectl activate: wrong number of arguments\n");
170		return (usage(false));
171	}
172
173	if (reset) {
174		if ((err = be_deactivate(be, NULL, reset)) == 0)
175			printf("Temporary activation removed\n");
176		else
177			printf("Failed to remove temporary activation\n");
178		return (err);
179	}
180
181	/* activate logic goes here */
182	if ((err = be_activate(be, argv[0], temp)) != 0)
183		/* XXX TODO: more specific error msg based on err */
184		printf("Did not successfully activate boot environment %s\n",
185		    argv[0]);
186	else
187		printf("Successfully activated boot environment %s\n", argv[0]);
188
189	if (temp)
190		printf("for next boot\n");
191
192	return (err);
193}
194
195
196/*
197 * TODO: when only one arg is given, and it contains an "@" the this should
198 * create that snapshot
199 */
200static int
201bectl_cmd_create(int argc, char *argv[])
202{
203	char snapshot[BE_MAXPATHLEN];
204	char *atpos, *bootenv, *snapname;
205	int err, opt;
206	bool recursive;
207
208	snapname = NULL;
209	recursive = false;
210	while ((opt = getopt(argc, argv, "e:r")) != -1) {
211		switch (opt) {
212		case 'e':
213			snapname = optarg;
214			break;
215		case 'r':
216			recursive = true;
217			break;
218		default:
219			fprintf(stderr, "bectl create: unknown option '-%c'\n",
220			    optopt);
221			return (usage(false));
222		}
223	}
224
225	argc -= optind;
226	argv += optind;
227
228	if (argc != 1) {
229		fprintf(stderr, "bectl create: wrong number of arguments\n");
230		return (usage(false));
231	}
232
233	bootenv = *argv;
234
235	err = BE_ERR_SUCCESS;
236	if (strchr(bootenv, ' ') != NULL)
237		/* BE datasets with spaces are not bootable */
238		err = BE_ERR_INVALIDNAME;
239	else 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	case BE_ERR_INVALIDNAME:
268		fprintf(stderr,
269		    "bectl create: boot environment name must not contain spaces\n");
270		break;
271	default:
272		if (atpos != NULL)
273			fprintf(stderr,
274			    "Failed to create a snapshot '%s' of '%s'\n",
275			    atpos, bootenv);
276		else if (snapname == NULL)
277			fprintf(stderr,
278			    "Failed to create bootenv %s\n", bootenv);
279		else
280			fprintf(stderr,
281			    "Failed to create bootenv %s from snapshot %s\n",
282			    bootenv, snapname);
283	}
284
285	return (err);
286}
287
288
289static int
290bectl_cmd_export(int argc, char *argv[])
291{
292	char *bootenv;
293
294	if (argc == 1) {
295		fprintf(stderr, "bectl export: missing boot environment name\n");
296		return (usage(false));
297	}
298
299	if (argc > 2) {
300		fprintf(stderr, "bectl export: extra arguments provided\n");
301		return (usage(false));
302	}
303
304	bootenv = argv[1];
305
306	if (isatty(STDOUT_FILENO)) {
307		fprintf(stderr, "bectl export: must redirect output\n");
308		return (EX_USAGE);
309	}
310
311	be_export(be, bootenv, STDOUT_FILENO);
312
313	return (0);
314}
315
316
317static int
318bectl_cmd_import(int argc, char *argv[])
319{
320	char *bootenv;
321	int err;
322
323	if (argc == 1) {
324		fprintf(stderr, "bectl import: missing boot environment name\n");
325		return (usage(false));
326	}
327
328	if (argc > 2) {
329		fprintf(stderr, "bectl import: extra arguments provided\n");
330		return (usage(false));
331	}
332
333	bootenv = argv[1];
334
335	if (isatty(STDIN_FILENO)) {
336		fprintf(stderr, "bectl import: input can not be from terminal\n");
337		return (EX_USAGE);
338	}
339
340	err = be_import(be, bootenv, STDIN_FILENO);
341
342	return (err);
343}
344
345#if SOON
346static int
347bectl_cmd_add(int argc, char *argv[])
348{
349
350	if (argc < 2) {
351		fprintf(stderr, "bectl add: must provide at least one path\n");
352		return (usage(false));
353	}
354
355	for (int i = 1; i < argc; ++i) {
356		printf("arg %d: %s\n", i, argv[i]);
357		/* XXX TODO catch err */
358		be_add_child(be, argv[i], true);
359	}
360
361	return (0);
362}
363#endif
364
365static int
366bectl_cmd_destroy(int argc, char *argv[])
367{
368	nvlist_t *props;
369	char *origin, *target, targetds[BE_MAXPATHLEN];
370	int err, flags, opt;
371
372	flags = 0;
373	while ((opt = getopt(argc, argv, "Fo")) != -1) {
374		switch (opt) {
375		case 'F':
376			flags |= BE_DESTROY_FORCE;
377			break;
378		case 'o':
379			flags |= BE_DESTROY_ORIGIN;
380			break;
381		default:
382			fprintf(stderr, "bectl destroy: unknown option '-%c'\n",
383			    optopt);
384			return (usage(false));
385		}
386	}
387
388	argc -= optind;
389	argv += optind;
390
391	if (argc != 1) {
392		fprintf(stderr, "bectl destroy: wrong number of arguments\n");
393		return (usage(false));
394	}
395
396	target = argv[0];
397
398	/* We'll emit a notice if there's an origin to be cleaned up */
399	if ((flags & BE_DESTROY_ORIGIN) == 0 && strchr(target, '@') == NULL) {
400		flags |= BE_DESTROY_AUTOORIGIN;
401		if (be_root_concat(be, target, targetds) != 0)
402			goto destroy;
403		if (be_prop_list_alloc(&props) != 0)
404			goto destroy;
405		if (be_get_dataset_props(be, targetds, props) != 0) {
406			be_prop_list_free(props);
407			goto destroy;
408		}
409		if (nvlist_lookup_string(props, "origin", &origin) == 0 &&
410		    !be_is_auto_snapshot_name(be, origin))
411			fprintf(stderr, "bectl destroy: leaving origin '%s' intact\n",
412			    origin);
413		be_prop_list_free(props);
414	}
415
416destroy:
417	err = be_destroy(be, target, flags);
418
419	return (err);
420}
421
422static int
423bectl_cmd_mount(int argc, char *argv[])
424{
425	char result_loc[BE_MAXPATHLEN];
426	char *bootenv, *mountpoint;
427	int err, mntflags;
428
429	/* XXX TODO: Allow shallow */
430	mntflags = BE_MNT_DEEP;
431	if (argc < 2) {
432		fprintf(stderr, "bectl mount: missing argument(s)\n");
433		return (usage(false));
434	}
435
436	if (argc > 3) {
437		fprintf(stderr, "bectl mount: too many arguments\n");
438		return (usage(false));
439	}
440
441	bootenv = argv[1];
442	mountpoint = ((argc == 3) ? argv[2] : NULL);
443
444	err = be_mount(be, bootenv, mountpoint, mntflags, result_loc);
445
446	switch (err) {
447	case BE_ERR_SUCCESS:
448		printf("Successfully mounted %s at %s\n", bootenv, result_loc);
449		break;
450	default:
451		fprintf(stderr,
452		    (argc == 3) ? "Failed to mount bootenv %s at %s\n" :
453		    "Failed to mount bootenv %s at temporary path %s\n",
454		    bootenv, mountpoint);
455	}
456
457	return (err);
458}
459
460
461static int
462bectl_cmd_rename(int argc, char *argv[])
463{
464	char *dest, *src;
465	int err;
466
467	if (argc < 3) {
468		fprintf(stderr, "bectl rename: missing argument\n");
469		return (usage(false));
470	}
471
472	if (argc > 3) {
473		fprintf(stderr, "bectl rename: too many arguments\n");
474		return (usage(false));
475	}
476
477	src = argv[1];
478	dest = argv[2];
479
480	err = be_rename(be, src, dest);
481
482	switch (err) {
483	case BE_ERR_SUCCESS:
484		break;
485	default:
486		fprintf(stderr, "Failed to rename bootenv %s to %s\n",
487		    src, dest);
488	}
489
490	return (0);
491}
492
493static int
494bectl_cmd_unmount(int argc, char *argv[])
495{
496	char *bootenv, *cmd;
497	int err, flags, opt;
498
499	/* Store alias used */
500	cmd = argv[0];
501
502	flags = 0;
503	while ((opt = getopt(argc, argv, "f")) != -1) {
504		switch (opt) {
505		case 'f':
506			flags |= BE_MNT_FORCE;
507			break;
508		default:
509			fprintf(stderr, "bectl %s: unknown option '-%c'\n",
510			    cmd, optopt);
511			return (usage(false));
512		}
513	}
514
515	argc -= optind;
516	argv += optind;
517
518	if (argc != 1) {
519		fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
520		return (usage(false));
521	}
522
523	bootenv = argv[0];
524
525	err = be_unmount(be, bootenv, flags);
526
527	switch (err) {
528	case BE_ERR_SUCCESS:
529		break;
530	default:
531		fprintf(stderr, "Failed to unmount bootenv %s\n", bootenv);
532	}
533
534	return (err);
535}
536
537static int
538bectl_cmd_check(int argc, char *argv[] __unused)
539{
540
541	/* The command is left as argv[0] */
542	if (argc != 1) {
543		fprintf(stderr, "bectl check: wrong number of arguments\n");
544		return (usage(false));
545	}
546
547	return (0);
548}
549
550int
551main(int argc, char *argv[])
552{
553	struct command_map_entry *cmd;
554	const char *command;
555	char *root;
556	int rc;
557
558	cmd = NULL;
559	root = NULL;
560	if (argc < 2)
561		return (usage(false));
562
563	if (strcmp(argv[1], "-r") == 0) {
564		if (argc < 4)
565			return (usage(false));
566		root = strdup(argv[2]);
567		command = argv[3];
568		argc -= 3;
569		argv += 3;
570	} else {
571		command = argv[1];
572		argc -= 1;
573		argv += 1;
574	}
575
576	/* Handle command aliases */
577	if (strcmp(command, "umount") == 0)
578		command = "unmount";
579
580	if (strcmp(command, "ujail") == 0)
581		command = "unjail";
582
583	if ((strcmp(command, "-?") == 0) || (strcmp(command, "-h") == 0))
584		return (usage(true));
585
586	if ((cmd = get_cmd_info(command)) == NULL) {
587		fprintf(stderr, "Unknown command: %s\n", command);
588		return (usage(false));
589	}
590
591	if ((be = libbe_init(root)) == NULL) {
592		if (!cmd->silent)
593			fprintf(stderr, "libbe_init(\"%s\") failed.\n",
594			    root != NULL ? root : "");
595		return (-1);
596	}
597
598	libbe_print_on_error(be, !cmd->silent);
599
600	rc = cmd->fn(argc, argv);
601
602	libbe_close(be);
603	return (rc);
604}
605