]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/bectl/bectl.c
Add UPDATING entries and bump version.
[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 [-bU] [{-o key=value | -u key}]... beName\n"
82             "\t      [utility [argument ...]]\n"
83             "\tbectl list [-aDHs] [{-c property | -C property}]\n"
84             "\tbectl mount beName [mountpoint]\n"
85             "\tbectl rename origBeName newBeName\n"
86             "\tbectl {ujail | unjail} {jailID | jailName | beName}\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         /* True if libbe_print_on_error should be disabled */
101         bool silent;
102 };
103
104 static struct command_map_entry command_map[] =
105 {
106         { "activate", bectl_cmd_activate,false   },
107         { "create",   bectl_cmd_create,  false   },
108         { "destroy",  bectl_cmd_destroy, false   },
109         { "export",   bectl_cmd_export,  false   },
110         { "import",   bectl_cmd_import,  false   },
111 #if SOON
112         { "add",      bectl_cmd_add,     false   },
113 #endif
114         { "jail",     bectl_cmd_jail,    false   },
115         { "list",     bectl_cmd_list,    false   },
116         { "mount",    bectl_cmd_mount,   false   },
117         { "rename",   bectl_cmd_rename,  false   },
118         { "unjail",   bectl_cmd_unjail,  false   },
119         { "unmount",  bectl_cmd_unmount, false   },
120         { "check",    bectl_cmd_check,   true    },
121 };
122
123 static struct command_map_entry *
124 get_cmd_info(const char *cmd)
125 {
126         size_t i;
127
128         for (i = 0; i < nitems(command_map); ++i) {
129                 if (strcmp(cmd, command_map[i].command) == 0)
130                         return (&command_map[i]);
131         }
132
133         return (NULL);
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 (strchr(bootenv, ' ') != NULL)
221                 /* BE datasets with spaces are not bootable */
222                 err = BE_ERR_INVALIDNAME;
223         else 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         case BE_ERR_INVALIDNAME:
252                 fprintf(stderr,
253                     "bectl create: boot environment name must not contain spaces\n");
254                 break;
255         default:
256                 if (atpos != NULL)
257                         fprintf(stderr,
258                             "Failed to create a snapshot '%s' of '%s'\n",
259                             atpos, bootenv);
260                 else if (snapname == NULL)
261                         fprintf(stderr,
262                             "Failed to create bootenv %s\n", bootenv);
263                 else
264                         fprintf(stderr,
265                             "Failed to create bootenv %s from snapshot %s\n",
266                             bootenv, snapname);
267         }
268
269         return (err);
270 }
271
272
273 static int
274 bectl_cmd_export(int argc, char *argv[])
275 {
276         char *bootenv;
277
278         if (argc == 1) {
279                 fprintf(stderr, "bectl export: missing boot environment name\n");
280                 return (usage(false));
281         }
282
283         if (argc > 2) {
284                 fprintf(stderr, "bectl export: extra arguments provided\n");
285                 return (usage(false));
286         }
287
288         bootenv = argv[1];
289
290         if (isatty(STDOUT_FILENO)) {
291                 fprintf(stderr, "bectl export: must redirect output\n");
292                 return (EX_USAGE);
293         }
294
295         be_export(be, bootenv, STDOUT_FILENO);
296
297         return (0);
298 }
299
300
301 static int
302 bectl_cmd_import(int argc, char *argv[])
303 {
304         char *bootenv;
305         int err;
306
307         if (argc == 1) {
308                 fprintf(stderr, "bectl import: missing boot environment name\n");
309                 return (usage(false));
310         }
311
312         if (argc > 2) {
313                 fprintf(stderr, "bectl import: extra arguments provided\n");
314                 return (usage(false));
315         }
316
317         bootenv = argv[1];
318
319         if (isatty(STDIN_FILENO)) {
320                 fprintf(stderr, "bectl import: input can not be from terminal\n");
321                 return (EX_USAGE);
322         }
323
324         err = be_import(be, bootenv, STDIN_FILENO);
325
326         return (err);
327 }
328
329 #if SOON
330 static int
331 bectl_cmd_add(int argc, char *argv[])
332 {
333
334         if (argc < 2) {
335                 fprintf(stderr, "bectl add: must provide at least one path\n");
336                 return (usage(false));
337         }
338
339         for (int i = 1; i < argc; ++i) {
340                 printf("arg %d: %s\n", i, argv[i]);
341                 /* XXX TODO catch err */
342                 be_add_child(be, argv[i], true);
343         }
344
345         return (0);
346 }
347 #endif
348
349 static int
350 bectl_cmd_destroy(int argc, char *argv[])
351 {
352         nvlist_t *props;
353         char *origin, *target, targetds[BE_MAXPATHLEN];
354         int err, flags, opt;
355
356         flags = 0;
357         while ((opt = getopt(argc, argv, "Fo")) != -1) {
358                 switch (opt) {
359                 case 'F':
360                         flags |= BE_DESTROY_FORCE;
361                         break;
362                 case 'o':
363                         flags |= BE_DESTROY_ORIGIN;
364                         break;
365                 default:
366                         fprintf(stderr, "bectl destroy: unknown option '-%c'\n",
367                             optopt);
368                         return (usage(false));
369                 }
370         }
371
372         argc -= optind;
373         argv += optind;
374
375         if (argc != 1) {
376                 fprintf(stderr, "bectl destroy: wrong number of arguments\n");
377                 return (usage(false));
378         }
379
380         target = argv[0];
381
382         /* We'll emit a notice if there's an origin to be cleaned up */
383         if ((flags & BE_DESTROY_ORIGIN) == 0 && strchr(target, '@') == NULL) {
384                 flags |= BE_DESTROY_AUTOORIGIN;
385                 if (be_root_concat(be, target, targetds) != 0)
386                         goto destroy;
387                 if (be_prop_list_alloc(&props) != 0)
388                         goto destroy;
389                 if (be_get_dataset_props(be, targetds, props) != 0) {
390                         be_prop_list_free(props);
391                         goto destroy;
392                 }
393                 if (nvlist_lookup_string(props, "origin", &origin) == 0 &&
394                     !be_is_auto_snapshot_name(be, origin))
395                         fprintf(stderr, "bectl destroy: leaving origin '%s' intact\n",
396                             origin);
397                 be_prop_list_free(props);
398         }
399
400 destroy:
401         err = be_destroy(be, target, flags);
402
403         return (err);
404 }
405
406 static int
407 bectl_cmd_mount(int argc, char *argv[])
408 {
409         char result_loc[BE_MAXPATHLEN];
410         char *bootenv, *mountpoint;
411         int err, mntflags;
412
413         /* XXX TODO: Allow shallow */
414         mntflags = BE_MNT_DEEP;
415         if (argc < 2) {
416                 fprintf(stderr, "bectl mount: missing argument(s)\n");
417                 return (usage(false));
418         }
419
420         if (argc > 3) {
421                 fprintf(stderr, "bectl mount: too many arguments\n");
422                 return (usage(false));
423         }
424
425         bootenv = argv[1];
426         mountpoint = ((argc == 3) ? argv[2] : NULL);
427
428         err = be_mount(be, bootenv, mountpoint, mntflags, result_loc);
429
430         switch (err) {
431         case BE_ERR_SUCCESS:
432                 printf("Successfully mounted %s at %s\n", bootenv, result_loc);
433                 break;
434         default:
435                 fprintf(stderr,
436                     (argc == 3) ? "Failed to mount bootenv %s at %s\n" :
437                     "Failed to mount bootenv %s at temporary path %s\n",
438                     bootenv, mountpoint);
439         }
440
441         return (err);
442 }
443
444
445 static int
446 bectl_cmd_rename(int argc, char *argv[])
447 {
448         char *dest, *src;
449         int err;
450
451         if (argc < 3) {
452                 fprintf(stderr, "bectl rename: missing argument\n");
453                 return (usage(false));
454         }
455
456         if (argc > 3) {
457                 fprintf(stderr, "bectl rename: too many arguments\n");
458                 return (usage(false));
459         }
460
461         src = argv[1];
462         dest = argv[2];
463
464         err = be_rename(be, src, dest);
465
466         switch (err) {
467         case BE_ERR_SUCCESS:
468                 break;
469         default:
470                 fprintf(stderr, "Failed to rename bootenv %s to %s\n",
471                     src, dest);
472         }
473
474         return (0);
475 }
476
477 static int
478 bectl_cmd_unmount(int argc, char *argv[])
479 {
480         char *bootenv, *cmd;
481         int err, flags, opt;
482
483         /* Store alias used */
484         cmd = argv[0];
485
486         flags = 0;
487         while ((opt = getopt(argc, argv, "f")) != -1) {
488                 switch (opt) {
489                 case 'f':
490                         flags |= BE_MNT_FORCE;
491                         break;
492                 default:
493                         fprintf(stderr, "bectl %s: unknown option '-%c'\n",
494                             cmd, optopt);
495                         return (usage(false));
496                 }
497         }
498
499         argc -= optind;
500         argv += optind;
501
502         if (argc != 1) {
503                 fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
504                 return (usage(false));
505         }
506
507         bootenv = argv[0];
508
509         err = be_unmount(be, bootenv, flags);
510
511         switch (err) {
512         case BE_ERR_SUCCESS:
513                 break;
514         default:
515                 fprintf(stderr, "Failed to unmount bootenv %s\n", bootenv);
516         }
517
518         return (err);
519 }
520
521 static int
522 bectl_cmd_check(int argc, char *argv[] __unused)
523 {
524
525         /* The command is left as argv[0] */
526         if (argc != 1) {
527                 fprintf(stderr, "bectl check: wrong number of arguments\n");
528                 return (usage(false));
529         }
530
531         return (0);
532 }
533
534 int
535 main(int argc, char *argv[])
536 {
537         struct command_map_entry *cmd;
538         const char *command;
539         char *root;
540         int rc;
541
542         cmd = NULL;
543         root = NULL;
544         if (argc < 2)
545                 return (usage(false));
546
547         if (strcmp(argv[1], "-r") == 0) {
548                 if (argc < 4)
549                         return (usage(false));
550                 root = strdup(argv[2]);
551                 command = argv[3];
552                 argc -= 3;
553                 argv += 3;
554         } else {
555                 command = argv[1];
556                 argc -= 1;
557                 argv += 1;
558         }
559
560         /* Handle command aliases */
561         if (strcmp(command, "umount") == 0)
562                 command = "unmount";
563
564         if (strcmp(command, "ujail") == 0)
565                 command = "unjail";
566
567         if ((strcmp(command, "-?") == 0) || (strcmp(command, "-h") == 0))
568                 return (usage(true));
569
570         if ((cmd = get_cmd_info(command)) == NULL) {
571                 fprintf(stderr, "Unknown command: %s\n", command);
572                 return (usage(false));
573         }
574
575         if ((be = libbe_init(root)) == NULL)
576                 return (-1);
577
578         libbe_print_on_error(be, !cmd->silent);
579
580         rc = cmd->fn(argc, argv);
581
582         libbe_close(be);
583         return (rc);
584 }