]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/bectl/bectl.c
mac: add new mac_ddb(4) policy
[FreeBSD/FreeBSD.git] / sbin / bectl / bectl.c
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
48 static int bectl_cmd_activate(int argc, char *argv[]);
49 static int bectl_cmd_check(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, "%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  */
98 struct 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
105 static 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
124 static struct command_map_entry *
125 get_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 static int
138 bectl_cmd_activate(int argc, char *argv[])
139 {
140         int err, opt;
141         bool temp, reset;
142
143         temp = false;
144         reset = false;
145         while ((opt = getopt(argc, argv, "tT")) != -1) {
146                 switch (opt) {
147                 case 't':
148                         if (reset)
149                                 return (usage(false));
150                         temp = true;
151                         break;
152                 case 'T':
153                         if (temp)
154                                 return (usage(false));
155                         reset = true;
156                         break;
157                 default:
158                         fprintf(stderr, "bectl activate: unknown option '-%c'\n",
159                             optopt);
160                         return (usage(false));
161                 }
162         }
163
164         argc -= optind;
165         argv += optind;
166
167         if (argc != 1 && (!reset || argc != 0)) {
168                 fprintf(stderr, "bectl activate: wrong number of arguments\n");
169                 return (usage(false));
170         }
171
172         if (reset) {
173                 if ((err = be_deactivate(be, NULL, reset)) == 0)
174                         printf("Temporary activation removed\n");
175                 else
176                         printf("Failed to remove temporary activation\n");
177                 return (err);
178         }
179
180         /* activate logic goes here */
181         if ((err = be_activate(be, argv[0], temp)) != 0)
182                 /* XXX TODO: more specific error msg based on err */
183                 printf("Did not successfully activate boot environment %s\n",
184                     argv[0]);
185         else
186                 printf("Successfully activated boot environment %s\n", argv[0]);
187
188         if (temp)
189                 printf("for next boot\n");
190
191         return (err);
192 }
193
194
195 /*
196  * TODO: when only one arg is given, and it contains an "@" the this should
197  * create that snapshot
198  */
199 static int
200 bectl_cmd_create(int argc, char *argv[])
201 {
202         char snapshot[BE_MAXPATHLEN];
203         char *atpos, *bootenv, *snapname;
204         int err, opt;
205         bool recursive;
206
207         snapname = NULL;
208         recursive = false;
209         while ((opt = getopt(argc, argv, "e:r")) != -1) {
210                 switch (opt) {
211                 case 'e':
212                         snapname = optarg;
213                         break;
214                 case 'r':
215                         recursive = true;
216                         break;
217                 default:
218                         fprintf(stderr, "bectl create: unknown option '-%c'\n",
219                             optopt);
220                         return (usage(false));
221                 }
222         }
223
224         argc -= optind;
225         argv += optind;
226
227         if (argc != 1) {
228                 fprintf(stderr, "bectl create: wrong number of arguments\n");
229                 return (usage(false));
230         }
231
232         bootenv = *argv;
233
234         err = BE_ERR_SUCCESS;
235         if ((atpos = strchr(bootenv, '@')) != NULL) {
236                 /*
237                  * This is the "create a snapshot variant". No new boot
238                  * environment is to be created here.
239                  */
240                 *atpos++ = '\0';
241                 err = be_snapshot(be, bootenv, atpos, recursive, NULL);
242         } else {
243                 if (snapname == NULL)
244                         /* Create from currently booted BE */
245                         err = be_snapshot(be, be_active_path(be), NULL,
246                             recursive, snapshot);
247                 else if (strchr(snapname, '@') != NULL)
248                         /* Create from given snapshot */
249                         strlcpy(snapshot, snapname, sizeof(snapshot));
250                 else
251                         /* Create from given BE */
252                         err = be_snapshot(be, snapname, NULL, recursive,
253                             snapshot);
254
255                 if (err == BE_ERR_SUCCESS)
256                         err = be_create_depth(be, bootenv, snapshot,
257                                               recursive == true ? -1 : 0);
258         }
259
260         switch (err) {
261         case BE_ERR_SUCCESS:
262                 break;
263         case BE_ERR_INVALIDNAME:
264                 fprintf(stderr,
265                     "bectl create: boot environment name must not contain spaces\n");
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
285 static int
286 bectl_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
313 static int
314 bectl_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
342 static int
343 bectl_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
361 static int
362 bectl_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
412 destroy:
413         err = be_destroy(be, target, flags);
414
415         return (err);
416 }
417
418 static int
419 bectl_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
457 static int
458 bectl_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
488 static int
489 bectl_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
532 static int
533 bectl_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
545 int
546 main(int argc, char *argv[])
547 {
548         struct command_map_entry *cmd;
549         const char *command;
550         char *root;
551         int rc;
552
553         cmd = NULL;
554         root = NULL;
555         if (argc < 2)
556                 return (usage(false));
557
558         if (strcmp(argv[1], "-r") == 0) {
559                 if (argc < 4)
560                         return (usage(false));
561                 root = strdup(argv[2]);
562                 command = argv[3];
563                 argc -= 3;
564                 argv += 3;
565         } else {
566                 command = argv[1];
567                 argc -= 1;
568                 argv += 1;
569         }
570
571         /* Handle command aliases */
572         if (strcmp(command, "umount") == 0)
573                 command = "unmount";
574
575         if (strcmp(command, "ujail") == 0)
576                 command = "unjail";
577
578         if ((strcmp(command, "-?") == 0) || (strcmp(command, "-h") == 0))
579                 return (usage(true));
580
581         if ((cmd = get_cmd_info(command)) == NULL) {
582                 fprintf(stderr, "Unknown command: %s\n", command);
583                 return (usage(false));
584         }
585
586         if ((be = libbe_init(root)) == NULL) {
587                 if (!cmd->silent)
588                         fprintf(stderr, "libbe_init(\"%s\") failed.\n",
589                             root != NULL ? root : "");
590                 return (-1);
591         }
592
593         libbe_print_on_error(be, !cmd->silent);
594
595         rc = cmd->fn(argc, argv);
596
597         libbe_close(be);
598         return (rc);
599 }