]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/bectl/bectl.c
geom_uzip(4), mkuzip(8): Add Zstd image mode
[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  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
31
32 #include <sys/param.h>
33 #include <sys/mount.h>
34 #include <errno.h>
35 #include <libutil.h>
36 #include <stdbool.h>
37 #include <stdio.h>
38 #include <stdint.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <sysexits.h>
42 #include <time.h>
43 #include <unistd.h>
44
45 #include <be.h>
46
47 #include "bectl.h"
48
49 static int bectl_cmd_activate(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 create [-r] [-e {nonActiveBe | beName@snapshot}] beName\n"
76             "\tbectl create [-r] beName@snapshot\n"
77             "\tbectl destroy [-F] {beName | beName@snapshot}\n"
78             "\tbectl export sourceBe\n"
79             "\tbectl import targetBe\n"
80             "\tbectl jail {-b | -U} [{-o key=value | -u key}]... "
81             "{jailID | jailName}\n"
82             "\t      bootenv [utility [argument ...]]\n"
83             "\tbectl list [-DHas]\n"
84             "\tbectl mount beName [mountpoint]\n"
85             "\tbectl rename origBeName newBeName\n"
86             "\tbectl {ujail | unjail} {jailID | jailName} bootenv\n"
87             "\tbectl {umount | unmount} [-f] beName\n");
88
89         return (explicit ? 0 : EX_USAGE);
90 }
91
92
93 /*
94  * Represents a relationship between the command name and the parser action
95  * that handles it.
96  */
97 struct command_map_entry {
98         const char *command;
99         int (*fn)(int argc, char *argv[]);
100 };
101
102 static struct command_map_entry command_map[] =
103 {
104         { "activate", bectl_cmd_activate },
105         { "create",   bectl_cmd_create   },
106         { "destroy",  bectl_cmd_destroy  },
107         { "export",   bectl_cmd_export   },
108         { "import",   bectl_cmd_import   },
109 #if SOON
110         { "add",      bectl_cmd_add      },
111 #endif
112         { "jail",     bectl_cmd_jail     },
113         { "list",     bectl_cmd_list     },
114         { "mount",    bectl_cmd_mount    },
115         { "rename",   bectl_cmd_rename   },
116         { "unjail",   bectl_cmd_unjail   },
117         { "unmount",  bectl_cmd_unmount  },
118 };
119
120 static int
121 get_cmd_index(const char *cmd, int *idx)
122 {
123         int map_size;
124
125         map_size = nitems(command_map);
126         for (int i = 0; i < map_size; ++i) {
127                 if (strcmp(cmd, command_map[i].command) == 0) {
128                         *idx = i;
129                         return (0);
130                 }
131         }
132
133         return (1);
134 }
135
136
137 static int
138 bectl_cmd_activate(int argc, char *argv[])
139 {
140         int err, opt;
141         bool temp;
142
143         temp = false;
144         while ((opt = getopt(argc, argv, "t")) != -1) {
145                 switch (opt) {
146                 case 't':
147                         temp = true;
148                         break;
149                 default:
150                         fprintf(stderr, "bectl activate: unknown option '-%c'\n",
151                             optopt);
152                         return (usage(false));
153                 }
154         }
155
156         argc -= optind;
157         argv += optind;
158
159         if (argc != 1) {
160                 fprintf(stderr, "bectl activate: wrong number of arguments\n");
161                 return (usage(false));
162         }
163
164
165         /* activate logic goes here */
166         if ((err = be_activate(be, argv[0], temp)) != 0)
167                 /* XXX TODO: more specific error msg based on err */
168                 printf("did not successfully activate boot environment %s\n",
169                     argv[0]);
170         else
171                 printf("successfully activated boot environment %s\n", argv[0]);
172
173         if (temp)
174                 printf("for next boot\n");
175
176         return (err);
177 }
178
179
180 /*
181  * TODO: when only one arg is given, and it contains an "@" the this should
182  * create that snapshot
183  */
184 static int
185 bectl_cmd_create(int argc, char *argv[])
186 {
187         char snapshot[BE_MAXPATHLEN];
188         char *atpos, *bootenv, *snapname;
189         int err, opt;
190         bool recursive;
191
192         snapname = NULL;
193         recursive = false;
194         while ((opt = getopt(argc, argv, "e:r")) != -1) {
195                 switch (opt) {
196                 case 'e':
197                         snapname = optarg;
198                         break;
199                 case 'r':
200                         recursive = true;
201                         break;
202                 default:
203                         fprintf(stderr, "bectl create: unknown option '-%c'\n",
204                             optopt);
205                         return (usage(false));
206                 }
207         }
208
209         argc -= optind;
210         argv += optind;
211
212         if (argc != 1) {
213                 fprintf(stderr, "bectl create: wrong number of arguments\n");
214                 return (usage(false));
215         }
216
217         bootenv = *argv;
218
219         err = BE_ERR_SUCCESS;
220         if ((atpos = strchr(bootenv, '@')) != NULL) {
221                 /*
222                  * This is the "create a snapshot variant". No new boot
223                  * environment is to be created here.
224                  */
225                 *atpos++ = '\0';
226                 err = be_snapshot(be, bootenv, atpos, recursive, NULL);
227         } else {
228                 if (snapname == NULL)
229                         /* Create from currently booted BE */
230                         err = be_snapshot(be, be_active_path(be), NULL,
231                             recursive, snapshot);
232                 else if (strchr(snapname, '@') != NULL)
233                         /* Create from given snapshot */
234                         strlcpy(snapshot, snapname, sizeof(snapshot));
235                 else
236                         /* Create from given BE */
237                         err = be_snapshot(be, snapname, NULL, recursive,
238                             snapshot);
239
240                 if (err == BE_ERR_SUCCESS)
241                         err = be_create_depth(be, bootenv, snapshot,
242                                               recursive == true ? -1 : 0);
243         }
244
245         switch (err) {
246         case BE_ERR_SUCCESS:
247                 break;
248         default:
249                 if (atpos != NULL)
250                         fprintf(stderr,
251                             "failed to create a snapshot '%s' of '%s'\n",
252                             atpos, bootenv);
253                 else if (snapname == NULL)
254                         fprintf(stderr,
255                             "failed to create bootenv %s\n", bootenv);
256                 else
257                         fprintf(stderr,
258                             "failed to create bootenv %s from snapshot %s\n",
259                             bootenv, snapname);
260         }
261
262         return (err);
263 }
264
265
266 static int
267 bectl_cmd_export(int argc, char *argv[])
268 {
269         char *bootenv;
270
271         if (argc == 1) {
272                 fprintf(stderr, "bectl export: missing boot environment name\n");
273                 return (usage(false));
274         }
275
276         if (argc > 2) {
277                 fprintf(stderr, "bectl export: extra arguments provided\n");
278                 return (usage(false));
279         }
280
281         bootenv = argv[1];
282
283         if (isatty(STDOUT_FILENO)) {
284                 fprintf(stderr, "bectl export: must redirect output\n");
285                 return (EX_USAGE);
286         }
287
288         be_export(be, bootenv, STDOUT_FILENO);
289
290         return (0);
291 }
292
293
294 static int
295 bectl_cmd_import(int argc, char *argv[])
296 {
297         char *bootenv;
298         int err;
299
300         if (argc == 1) {
301                 fprintf(stderr, "bectl import: missing boot environment name\n");
302                 return (usage(false));
303         }
304
305         if (argc > 2) {
306                 fprintf(stderr, "bectl import: extra arguments provided\n");
307                 return (usage(false));
308         }
309
310         bootenv = argv[1];
311
312         if (isatty(STDIN_FILENO)) {
313                 fprintf(stderr, "bectl import: input can not be from terminal\n");
314                 return (EX_USAGE);
315         }
316
317         err = be_import(be, bootenv, STDIN_FILENO);
318
319         return (err);
320 }
321
322 #if SOON
323 static int
324 bectl_cmd_add(int argc, char *argv[])
325 {
326
327         if (argc < 2) {
328                 fprintf(stderr, "bectl add: must provide at least one path\n");
329                 return (usage(false));
330         }
331
332         for (int i = 1; i < argc; ++i) {
333                 printf("arg %d: %s\n", i, argv[i]);
334                 /* XXX TODO catch err */
335                 be_add_child(be, argv[i], true);
336         }
337
338         return (0);
339 }
340 #endif
341
342 static int
343 bectl_cmd_destroy(int argc, char *argv[])
344 {
345         nvlist_t *props;
346         char *origin, *target, targetds[BE_MAXPATHLEN];
347         int err, flags, opt;
348
349         flags = 0;
350         while ((opt = getopt(argc, argv, "Fo")) != -1) {
351                 switch (opt) {
352                 case 'F':
353                         flags |= BE_DESTROY_FORCE;
354                         break;
355                 case 'o':
356                         flags |= BE_DESTROY_ORIGIN;
357                         break;
358                 default:
359                         fprintf(stderr, "bectl destroy: unknown option '-%c'\n",
360                             optopt);
361                         return (usage(false));
362                 }
363         }
364
365         argc -= optind;
366         argv += optind;
367
368         if (argc != 1) {
369                 fprintf(stderr, "bectl destroy: wrong number of arguments\n");
370                 return (usage(false));
371         }
372
373         target = argv[0];
374
375         /* We'll emit a notice if there's an origin to be cleaned up */
376         if ((flags & BE_DESTROY_ORIGIN) == 0 && strchr(target, '@') == NULL) {
377                 if (be_root_concat(be, target, targetds) != 0)
378                         goto destroy;
379                 if (be_prop_list_alloc(&props) != 0)
380                         goto destroy;
381                 if (be_get_dataset_props(be, targetds, props) != 0) {
382                         be_prop_list_free(props);
383                         goto destroy;
384                 }
385                 if (nvlist_lookup_string(props, "origin", &origin) == 0)
386                         fprintf(stderr, "bectl destroy: leaving origin '%s' intact\n",
387                             origin);
388                 be_prop_list_free(props);
389         }
390
391 destroy:
392         err = be_destroy(be, target, flags);
393
394         return (err);
395 }
396
397 static int
398 bectl_cmd_mount(int argc, char *argv[])
399 {
400         char result_loc[BE_MAXPATHLEN];
401         char *bootenv, *mountpoint;
402         int err, mntflags;
403
404         /* XXX TODO: Allow shallow */
405         mntflags = BE_MNT_DEEP;
406         if (argc < 2) {
407                 fprintf(stderr, "bectl mount: missing argument(s)\n");
408                 return (usage(false));
409         }
410
411         if (argc > 3) {
412                 fprintf(stderr, "bectl mount: too many arguments\n");
413                 return (usage(false));
414         }
415
416         bootenv = argv[1];
417         mountpoint = ((argc == 3) ? argv[2] : NULL);
418
419         err = be_mount(be, bootenv, mountpoint, mntflags, result_loc);
420
421         switch (err) {
422         case BE_ERR_SUCCESS:
423                 printf("successfully mounted %s at %s\n", bootenv, result_loc);
424                 break;
425         default:
426                 fprintf(stderr,
427                     (argc == 3) ? "failed to mount bootenv %s at %s\n" :
428                     "failed to mount bootenv %s at temporary path %s\n",
429                     bootenv, mountpoint);
430         }
431
432         return (err);
433 }
434
435
436 static int
437 bectl_cmd_rename(int argc, char *argv[])
438 {
439         char *dest, *src;
440         int err;
441
442         if (argc < 3) {
443                 fprintf(stderr, "bectl rename: missing argument\n");
444                 return (usage(false));
445         }
446
447         if (argc > 3) {
448                 fprintf(stderr, "bectl rename: too many arguments\n");
449                 return (usage(false));
450         }
451
452         src = argv[1];
453         dest = argv[2];
454
455         err = be_rename(be, src, dest);
456
457         switch (err) {
458         case BE_ERR_SUCCESS:
459                 break;
460         default:
461                 fprintf(stderr, "failed to rename bootenv %s to %s\n",
462                     src, dest);
463         }
464
465         return (0);
466 }
467
468 static int
469 bectl_cmd_unmount(int argc, char *argv[])
470 {
471         char *bootenv, *cmd;
472         int err, flags, opt;
473
474         /* Store alias used */
475         cmd = argv[0];
476
477         flags = 0;
478         while ((opt = getopt(argc, argv, "f")) != -1) {
479                 switch (opt) {
480                 case 'f':
481                         flags |= BE_MNT_FORCE;
482                         break;
483                 default:
484                         fprintf(stderr, "bectl %s: unknown option '-%c'\n",
485                             cmd, optopt);
486                         return (usage(false));
487                 }
488         }
489
490         argc -= optind;
491         argv += optind;
492
493         if (argc != 1) {
494                 fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
495                 return (usage(false));
496         }
497
498         bootenv = argv[0];
499
500         err = be_unmount(be, bootenv, flags);
501
502         switch (err) {
503         case BE_ERR_SUCCESS:
504                 break;
505         default:
506                 fprintf(stderr, "failed to unmount bootenv %s\n", bootenv);
507         }
508
509         return (err);
510 }
511
512
513 int
514 main(int argc, char *argv[])
515 {
516         const char *command;
517         char *root;
518         int command_index, rc;
519
520         root = NULL;
521         if (argc < 2)
522                 return (usage(false));
523
524         if (strcmp(argv[1], "-r") == 0) {
525                 if (argc < 4)
526                         return (usage(false));
527                 root = strdup(argv[2]);
528                 command = argv[3];
529                 argc -= 3;
530                 argv += 3;
531         } else {
532                 command = argv[1];
533                 argc -= 1;
534                 argv += 1;
535         }
536
537         /* Handle command aliases */
538         if (strcmp(command, "umount") == 0)
539                 command = "unmount";
540
541         if (strcmp(command, "ujail") == 0)
542                 command = "unjail";
543
544         if ((strcmp(command, "-?") == 0) || (strcmp(command, "-h") == 0))
545                 return (usage(true));
546
547         if (get_cmd_index(command, &command_index)) {
548                 fprintf(stderr, "unknown command: %s\n", command);
549                 return (usage(false));
550         }
551
552
553         if ((be = libbe_init(root)) == NULL)
554                 return (-1);
555
556         libbe_print_on_error(be, true);
557
558         rc = command_map[command_index].fn(argc, argv);
559
560         libbe_close(be);
561         return (rc);
562 }