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