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