]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/bectl/bectl.c
MFV: r361597
[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 check\n"
76             "\tbectl create [-r] [-e {nonActiveBe | beName@snapshot}] beName\n"
77             "\tbectl create [-r] beName@snapshot\n"
78             "\tbectl destroy [-F] {beName | beName@snapshot}\n"
79             "\tbectl export sourceBe\n"
80             "\tbectl import targetBe\n"
81             "\tbectl jail {-b | -U} [{-o key=value | -u key}]... "
82             "{jailID | jailName}\n"
83             "\t      bootenv [utility [argument ...]]\n"
84             "\tbectl list [-DHas] [{-c property | -C property}]\n"
85             "\tbectl mount beName [mountpoint]\n"
86             "\tbectl rename origBeName newBeName\n"
87             "\tbectl {ujail | unjail} {jailID | jailName} bootenv\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;
143
144         temp = false;
145         while ((opt = getopt(argc, argv, "t")) != -1) {
146                 switch (opt) {
147                 case 't':
148                         temp = true;
149                         break;
150                 default:
151                         fprintf(stderr, "bectl activate: unknown option '-%c'\n",
152                             optopt);
153                         return (usage(false));
154                 }
155         }
156
157         argc -= optind;
158         argv += optind;
159
160         if (argc != 1) {
161                 fprintf(stderr, "bectl activate: wrong number of arguments\n");
162                 return (usage(false));
163         }
164
165
166         /* activate logic goes here */
167         if ((err = be_activate(be, argv[0], temp)) != 0)
168                 /* XXX TODO: more specific error msg based on err */
169                 printf("Did not successfully activate boot environment %s\n",
170                     argv[0]);
171         else
172                 printf("Successfully activated boot environment %s\n", argv[0]);
173
174         if (temp)
175                 printf("for next boot\n");
176
177         return (err);
178 }
179
180
181 /*
182  * TODO: when only one arg is given, and it contains an "@" the this should
183  * create that snapshot
184  */
185 static int
186 bectl_cmd_create(int argc, char *argv[])
187 {
188         char snapshot[BE_MAXPATHLEN];
189         char *atpos, *bootenv, *snapname;
190         int err, opt;
191         bool recursive;
192
193         snapname = NULL;
194         recursive = false;
195         while ((opt = getopt(argc, argv, "e:r")) != -1) {
196                 switch (opt) {
197                 case 'e':
198                         snapname = optarg;
199                         break;
200                 case 'r':
201                         recursive = true;
202                         break;
203                 default:
204                         fprintf(stderr, "bectl create: unknown option '-%c'\n",
205                             optopt);
206                         return (usage(false));
207                 }
208         }
209
210         argc -= optind;
211         argv += optind;
212
213         if (argc != 1) {
214                 fprintf(stderr, "bectl create: wrong number of arguments\n");
215                 return (usage(false));
216         }
217
218         bootenv = *argv;
219
220         err = BE_ERR_SUCCESS;
221         if ((atpos = strchr(bootenv, '@')) != NULL) {
222                 /*
223                  * This is the "create a snapshot variant". No new boot
224                  * environment is to be created here.
225                  */
226                 *atpos++ = '\0';
227                 err = be_snapshot(be, bootenv, atpos, recursive, NULL);
228         } else {
229                 if (snapname == NULL)
230                         /* Create from currently booted BE */
231                         err = be_snapshot(be, be_active_path(be), NULL,
232                             recursive, snapshot);
233                 else if (strchr(snapname, '@') != NULL)
234                         /* Create from given snapshot */
235                         strlcpy(snapshot, snapname, sizeof(snapshot));
236                 else
237                         /* Create from given BE */
238                         err = be_snapshot(be, snapname, NULL, recursive,
239                             snapshot);
240
241                 if (err == BE_ERR_SUCCESS)
242                         err = be_create_depth(be, bootenv, snapshot,
243                                               recursive == true ? -1 : 0);
244         }
245
246         switch (err) {
247         case BE_ERR_SUCCESS:
248                 break;
249         default:
250                 if (atpos != NULL)
251                         fprintf(stderr,
252                             "Failed to create a snapshot '%s' of '%s'\n",
253                             atpos, bootenv);
254                 else if (snapname == NULL)
255                         fprintf(stderr,
256                             "Failed to create bootenv %s\n", bootenv);
257                 else
258                         fprintf(stderr,
259                             "Failed to create bootenv %s from snapshot %s\n",
260                             bootenv, snapname);
261         }
262
263         return (err);
264 }
265
266
267 static int
268 bectl_cmd_export(int argc, char *argv[])
269 {
270         char *bootenv;
271
272         if (argc == 1) {
273                 fprintf(stderr, "bectl export: missing boot environment name\n");
274                 return (usage(false));
275         }
276
277         if (argc > 2) {
278                 fprintf(stderr, "bectl export: extra arguments provided\n");
279                 return (usage(false));
280         }
281
282         bootenv = argv[1];
283
284         if (isatty(STDOUT_FILENO)) {
285                 fprintf(stderr, "bectl export: must redirect output\n");
286                 return (EX_USAGE);
287         }
288
289         be_export(be, bootenv, STDOUT_FILENO);
290
291         return (0);
292 }
293
294
295 static int
296 bectl_cmd_import(int argc, char *argv[])
297 {
298         char *bootenv;
299         int err;
300
301         if (argc == 1) {
302                 fprintf(stderr, "bectl import: missing boot environment name\n");
303                 return (usage(false));
304         }
305
306         if (argc > 2) {
307                 fprintf(stderr, "bectl import: extra arguments provided\n");
308                 return (usage(false));
309         }
310
311         bootenv = argv[1];
312
313         if (isatty(STDIN_FILENO)) {
314                 fprintf(stderr, "bectl import: input can not be from terminal\n");
315                 return (EX_USAGE);
316         }
317
318         err = be_import(be, bootenv, STDIN_FILENO);
319
320         return (err);
321 }
322
323 #if SOON
324 static int
325 bectl_cmd_add(int argc, char *argv[])
326 {
327
328         if (argc < 2) {
329                 fprintf(stderr, "bectl add: must provide at least one path\n");
330                 return (usage(false));
331         }
332
333         for (int i = 1; i < argc; ++i) {
334                 printf("arg %d: %s\n", i, argv[i]);
335                 /* XXX TODO catch err */
336                 be_add_child(be, argv[i], true);
337         }
338
339         return (0);
340 }
341 #endif
342
343 static int
344 bectl_cmd_destroy(int argc, char *argv[])
345 {
346         nvlist_t *props;
347         char *origin, *target, targetds[BE_MAXPATHLEN];
348         int err, flags, opt;
349
350         flags = 0;
351         while ((opt = getopt(argc, argv, "Fo")) != -1) {
352                 switch (opt) {
353                 case 'F':
354                         flags |= BE_DESTROY_FORCE;
355                         break;
356                 case 'o':
357                         flags |= BE_DESTROY_ORIGIN;
358                         break;
359                 default:
360                         fprintf(stderr, "bectl destroy: unknown option '-%c'\n",
361                             optopt);
362                         return (usage(false));
363                 }
364         }
365
366         argc -= optind;
367         argv += optind;
368
369         if (argc != 1) {
370                 fprintf(stderr, "bectl destroy: wrong number of arguments\n");
371                 return (usage(false));
372         }
373
374         target = argv[0];
375
376         /* We'll emit a notice if there's an origin to be cleaned up */
377         if ((flags & BE_DESTROY_ORIGIN) == 0 && strchr(target, '@') == NULL) {
378                 flags |= BE_DESTROY_AUTOORIGIN;
379                 if (be_root_concat(be, target, targetds) != 0)
380                         goto destroy;
381                 if (be_prop_list_alloc(&props) != 0)
382                         goto destroy;
383                 if (be_get_dataset_props(be, targetds, props) != 0) {
384                         be_prop_list_free(props);
385                         goto destroy;
386                 }
387                 if (nvlist_lookup_string(props, "origin", &origin) == 0 &&
388                     !be_is_auto_snapshot_name(be, origin))
389                         fprintf(stderr, "bectl destroy: leaving origin '%s' intact\n",
390                             origin);
391                 be_prop_list_free(props);
392         }
393
394 destroy:
395         err = be_destroy(be, target, flags);
396
397         return (err);
398 }
399
400 static int
401 bectl_cmd_mount(int argc, char *argv[])
402 {
403         char result_loc[BE_MAXPATHLEN];
404         char *bootenv, *mountpoint;
405         int err, mntflags;
406
407         /* XXX TODO: Allow shallow */
408         mntflags = BE_MNT_DEEP;
409         if (argc < 2) {
410                 fprintf(stderr, "bectl mount: missing argument(s)\n");
411                 return (usage(false));
412         }
413
414         if (argc > 3) {
415                 fprintf(stderr, "bectl mount: too many arguments\n");
416                 return (usage(false));
417         }
418
419         bootenv = argv[1];
420         mountpoint = ((argc == 3) ? argv[2] : NULL);
421
422         err = be_mount(be, bootenv, mountpoint, mntflags, result_loc);
423
424         switch (err) {
425         case BE_ERR_SUCCESS:
426                 printf("Successfully mounted %s at %s\n", bootenv, result_loc);
427                 break;
428         default:
429                 fprintf(stderr,
430                     (argc == 3) ? "Failed to mount bootenv %s at %s\n" :
431                     "Failed to mount bootenv %s at temporary path %s\n",
432                     bootenv, mountpoint);
433         }
434
435         return (err);
436 }
437
438
439 static int
440 bectl_cmd_rename(int argc, char *argv[])
441 {
442         char *dest, *src;
443         int err;
444
445         if (argc < 3) {
446                 fprintf(stderr, "bectl rename: missing argument\n");
447                 return (usage(false));
448         }
449
450         if (argc > 3) {
451                 fprintf(stderr, "bectl rename: too many arguments\n");
452                 return (usage(false));
453         }
454
455         src = argv[1];
456         dest = argv[2];
457
458         err = be_rename(be, src, dest);
459
460         switch (err) {
461         case BE_ERR_SUCCESS:
462                 break;
463         default:
464                 fprintf(stderr, "Failed to rename bootenv %s to %s\n",
465                     src, dest);
466         }
467
468         return (0);
469 }
470
471 static int
472 bectl_cmd_unmount(int argc, char *argv[])
473 {
474         char *bootenv, *cmd;
475         int err, flags, opt;
476
477         /* Store alias used */
478         cmd = argv[0];
479
480         flags = 0;
481         while ((opt = getopt(argc, argv, "f")) != -1) {
482                 switch (opt) {
483                 case 'f':
484                         flags |= BE_MNT_FORCE;
485                         break;
486                 default:
487                         fprintf(stderr, "bectl %s: unknown option '-%c'\n",
488                             cmd, optopt);
489                         return (usage(false));
490                 }
491         }
492
493         argc -= optind;
494         argv += optind;
495
496         if (argc != 1) {
497                 fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
498                 return (usage(false));
499         }
500
501         bootenv = argv[0];
502
503         err = be_unmount(be, bootenv, flags);
504
505         switch (err) {
506         case BE_ERR_SUCCESS:
507                 break;
508         default:
509                 fprintf(stderr, "Failed to unmount bootenv %s\n", bootenv);
510         }
511
512         return (err);
513 }
514
515 static int
516 bectl_cmd_check(int argc, char *argv[] __unused)
517 {
518
519         /* The command is left as argv[0] */
520         if (argc != 1) {
521                 fprintf(stderr, "bectl check: wrong number of arguments\n");
522                 return (usage(false));
523         }
524
525         return (0);
526 }
527
528 int
529 main(int argc, char *argv[])
530 {
531         struct command_map_entry *cmd;
532         const char *command;
533         char *root;
534         int rc;
535
536         cmd = NULL;
537         root = NULL;
538         if (argc < 2)
539                 return (usage(false));
540
541         if (strcmp(argv[1], "-r") == 0) {
542                 if (argc < 4)
543                         return (usage(false));
544                 root = strdup(argv[2]);
545                 command = argv[3];
546                 argc -= 3;
547                 argv += 3;
548         } else {
549                 command = argv[1];
550                 argc -= 1;
551                 argv += 1;
552         }
553
554         /* Handle command aliases */
555         if (strcmp(command, "umount") == 0)
556                 command = "unmount";
557
558         if (strcmp(command, "ujail") == 0)
559                 command = "unjail";
560
561         if ((strcmp(command, "-?") == 0) || (strcmp(command, "-h") == 0))
562                 return (usage(true));
563
564         if ((cmd = get_cmd_info(command)) == NULL) {
565                 fprintf(stderr, "Unknown command: %s\n", command);
566                 return (usage(false));
567         }
568
569         if ((be = libbe_init(root)) == NULL)
570                 return (-1);
571
572         libbe_print_on_error(be, !cmd->silent);
573
574         rc = cmd->fn(argc, argv);
575
576         libbe_close(be);
577         return (rc);
578 }