]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/bectl/bectl.c
MFV r341618:
[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 *atpos, *bootenv, *snapname, *source;
188         int err, opt;
189         bool recursive;
190
191         snapname = NULL;
192         recursive = false;
193         while ((opt = getopt(argc, argv, "e:r")) != -1) {
194                 switch (opt) {
195                 case 'e':
196                         snapname = optarg;
197                         break;
198                 case 'r':
199                         recursive = true;
200                         break;
201                 default:
202                         fprintf(stderr, "bectl create: unknown option '-%c'\n",
203                             optopt);
204                         return (usage(false));
205                 }
206         }
207
208         argc -= optind;
209         argv += optind;
210
211         if (argc != 1) {
212                 fprintf(stderr, "bectl create: wrong number of arguments\n");
213                 return (usage(false));
214         }
215
216         bootenv = *argv;
217         if ((atpos = strchr(bootenv, '@')) != NULL) {
218                 /*
219                  * This is the "create a snapshot variant". No new boot
220                  * environment is to be created here.
221                  */
222                 *atpos++ = '\0';
223                 err = be_snapshot(be, bootenv, atpos, recursive, NULL);
224         } else if (snapname != NULL) {
225                 if (strchr(snapname, '@') != NULL)
226                         err = be_create_from_existing_snap(be, bootenv,
227                             snapname);
228                 else
229                         err = be_create_from_existing(be, bootenv, snapname);
230         } else {
231                 if ((snapname = strchr(bootenv, '@')) != NULL) {
232                         *(snapname++) = '\0';
233                         if ((err = be_snapshot(be, be_active_path(be),
234                             snapname, true, NULL)) != BE_ERR_SUCCESS)
235                                 fprintf(stderr, "failed to create snapshot\n");
236                         asprintf(&source, "%s@%s", be_active_path(be), snapname);
237                         err = be_create_from_existing_snap(be, bootenv,
238                             source);
239                         return (err);
240                 } else
241                         err = be_create(be, bootenv);
242         }
243
244         switch (err) {
245         case BE_ERR_SUCCESS:
246                 break;
247         default:
248                 if (atpos != NULL)
249                         fprintf(stderr,
250                             "failed to create a snapshot '%s' of '%s'\n",
251                             atpos, bootenv);
252                 else if (snapname == NULL)
253                         fprintf(stderr,
254                             "failed to create bootenv %s\n", bootenv);
255                 else
256                         fprintf(stderr,
257                             "failed to create bootenv %s from snapshot %s\n",
258                             bootenv, snapname);
259         }
260
261         return (err);
262 }
263
264
265 static int
266 bectl_cmd_export(int argc, char *argv[])
267 {
268         char *bootenv;
269
270         if (argc == 1) {
271                 fprintf(stderr, "bectl export: missing boot environment name\n");
272                 return (usage(false));
273         }
274
275         if (argc > 2) {
276                 fprintf(stderr, "bectl export: extra arguments provided\n");
277                 return (usage(false));
278         }
279
280         bootenv = argv[1];
281
282         if (isatty(STDOUT_FILENO)) {
283                 fprintf(stderr, "bectl export: must redirect output\n");
284                 return (EX_USAGE);
285         }
286
287         be_export(be, bootenv, STDOUT_FILENO);
288
289         return (0);
290 }
291
292
293 static int
294 bectl_cmd_import(int argc, char *argv[])
295 {
296         char *bootenv;
297         int err;
298
299         if (argc == 1) {
300                 fprintf(stderr, "bectl import: missing boot environment name\n");
301                 return (usage(false));
302         }
303
304         if (argc > 2) {
305                 fprintf(stderr, "bectl import: extra arguments provided\n");
306                 return (usage(false));
307         }
308
309         bootenv = argv[1];
310
311         if (isatty(STDIN_FILENO)) {
312                 fprintf(stderr, "bectl import: input can not be from terminal\n");
313                 return (EX_USAGE);
314         }
315
316         err = be_import(be, bootenv, STDIN_FILENO);
317
318         return (err);
319 }
320
321 #if SOON
322 static int
323 bectl_cmd_add(int argc, char *argv[])
324 {
325
326         if (argc < 2) {
327                 fprintf(stderr, "bectl add: must provide at least one path\n");
328                 return (usage(false));
329         }
330
331         for (int i = 1; i < argc; ++i) {
332                 printf("arg %d: %s\n", i, argv[i]);
333                 /* XXX TODO catch err */
334                 be_add_child(be, argv[i], true);
335         }
336
337         return (0);
338 }
339 #endif
340
341 static int
342 bectl_cmd_destroy(int argc, char *argv[])
343 {
344         char *target;
345         int opt, err;
346         bool force;
347
348         force = false;
349         while ((opt = getopt(argc, argv, "F")) != -1) {
350                 switch (opt) {
351                 case 'F':
352                         force = true;
353                         break;
354                 default:
355                         fprintf(stderr, "bectl destroy: unknown option '-%c'\n",
356                             optopt);
357                         return (usage(false));
358                 }
359         }
360
361         argc -= optind;
362         argv += optind;
363
364         if (argc != 1) {
365                 fprintf(stderr, "bectl destroy: wrong number of arguments\n");
366                 return (usage(false));
367         }
368
369         target = argv[0];
370
371         err = be_destroy(be, target, force);
372
373         return (err);
374 }
375
376 static int
377 bectl_cmd_mount(int argc, char *argv[])
378 {
379         char result_loc[BE_MAXPATHLEN];
380         char *bootenv, *mountpoint;
381         int err;
382
383         if (argc < 2) {
384                 fprintf(stderr, "bectl mount: missing argument(s)\n");
385                 return (usage(false));
386         }
387
388         if (argc > 3) {
389                 fprintf(stderr, "bectl mount: too many arguments\n");
390                 return (usage(false));
391         }
392
393         bootenv = argv[1];
394         mountpoint = ((argc == 3) ? argv[2] : NULL);
395
396         err = be_mount(be, bootenv, mountpoint, 0, result_loc);
397
398         switch (err) {
399         case BE_ERR_SUCCESS:
400                 printf("successfully mounted %s at %s\n", bootenv, result_loc);
401                 break;
402         default:
403                 fprintf(stderr,
404                     (argc == 3) ? "failed to mount bootenv %s at %s\n" :
405                     "failed to mount bootenv %s at temporary path %s\n",
406                     bootenv, mountpoint);
407         }
408
409         return (err);
410 }
411
412
413 static int
414 bectl_cmd_rename(int argc, char *argv[])
415 {
416         char *dest, *src;
417         int err;
418
419         if (argc < 3) {
420                 fprintf(stderr, "bectl rename: missing argument\n");
421                 return (usage(false));
422         }
423
424         if (argc > 3) {
425                 fprintf(stderr, "bectl rename: too many arguments\n");
426                 return (usage(false));
427         }
428
429         src = argv[1];
430         dest = argv[2];
431
432         err = be_rename(be, src, dest);
433
434         switch (err) {
435         case BE_ERR_SUCCESS:
436                 break;
437         default:
438                 fprintf(stderr, "failed to rename bootenv %s to %s\n",
439                     src, dest);
440         }
441
442         return (0);
443 }
444
445 static int
446 bectl_cmd_unmount(int argc, char *argv[])
447 {
448         char *bootenv, *cmd;
449         int err, flags, opt;
450
451         /* Store alias used */
452         cmd = argv[0];
453
454         flags = 0;
455         while ((opt = getopt(argc, argv, "f")) != -1) {
456                 switch (opt) {
457                 case 'f':
458                         flags |= BE_MNT_FORCE;
459                         break;
460                 default:
461                         fprintf(stderr, "bectl %s: unknown option '-%c'\n",
462                             cmd, optopt);
463                         return (usage(false));
464                 }
465         }
466
467         argc -= optind;
468         argv += optind;
469
470         if (argc != 1) {
471                 fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
472                 return (usage(false));
473         }
474
475         bootenv = argv[0];
476
477         err = be_unmount(be, bootenv, flags);
478
479         switch (err) {
480         case BE_ERR_SUCCESS:
481                 break;
482         default:
483                 fprintf(stderr, "failed to unmount bootenv %s\n", bootenv);
484         }
485
486         return (err);
487 }
488
489
490 int
491 main(int argc, char *argv[])
492 {
493         const char *command;
494         char *root;
495         int command_index, rc;
496
497         root = NULL;
498         if (argc < 2)
499                 return (usage(false));
500
501         if (strcmp(argv[1], "-r") == 0) {
502                 if (argc < 4)
503                         return (usage(false));
504                 root = strdup(argv[2]);
505                 command = argv[3];
506                 argc -= 3;
507                 argv += 3;
508         } else {
509                 command = argv[1];
510                 argc -= 1;
511                 argv += 1;
512         }
513
514         /* Handle command aliases */
515         if (strcmp(command, "umount") == 0)
516                 command = "unmount";
517
518         if (strcmp(command, "ujail") == 0)
519                 command = "unjail";
520
521         if ((strcmp(command, "-?") == 0) || (strcmp(command, "-h") == 0))
522                 return (usage(true));
523
524         if (get_cmd_index(command, &command_index)) {
525                 fprintf(stderr, "unknown command: %s\n", command);
526                 return (usage(false));
527         }
528
529
530         if ((be = libbe_init(root)) == NULL)
531                 return (-1);
532
533         libbe_print_on_error(be, true);
534
535         rc = command_map[command_index].fn(argc, argv);
536
537         libbe_close(be);
538         return (rc);
539 }