]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/bectl/bectl.c
bsddialog: import snapshot 2021-12-05
[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
138 static int
139 bectl_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  */
200 static int
201 bectl_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
289 static int
290 bectl_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
317 static int
318 bectl_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
346 static int
347 bectl_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
365 static int
366 bectl_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
416 destroy:
417         err = be_destroy(be, target, flags);
418
419         return (err);
420 }
421
422 static int
423 bectl_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
461 static int
462 bectl_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
493 static int
494 bectl_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
537 static int
538 bectl_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
550 int
551 main(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 }