]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/bectl/bectl.c
bectl(8): Remove the jail mount point with unjail
[FreeBSD/FreeBSD.git] / sbin / bectl / bectl.c
1 /*
2  * be.c
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/param.h>
30 #include <sys/jail.h>
31 #include <sys/mount.h>
32 #include <errno.h>
33 #include <jail.h>
34 #include <stdbool.h>
35 #include <stdio.h>
36 #include <stdint.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <sysexits.h>
40 #include <unistd.h>
41
42 #include <be.h>
43
44 static int bectl_cmd_activate(int argc, char *argv[]);
45 static int bectl_cmd_create(int argc, char *argv[]);
46 static int bectl_cmd_destroy(int argc, char *argv[]);
47 static int bectl_cmd_export(int argc, char *argv[]);
48 static int bectl_cmd_import(int argc, char *argv[]);
49 static int bectl_cmd_add(int argc, char *argv[]);
50 static int bectl_cmd_jail(int argc, char *argv[]);
51 static int bectl_cmd_list(int argc, char *argv[]);
52 static int bectl_cmd_mount(int argc, char *argv[]);
53 static int bectl_cmd_rename(int argc, char *argv[]);
54 static int bectl_search_jail_paths(const char *mnt);
55 static int bectl_locate_jail(const char *ident);
56 static int bectl_cmd_unjail(int argc, char *argv[]);
57 static int bectl_cmd_unmount(int argc, char *argv[]);
58
59 static libbe_handle_t *be;
60
61 static int
62 usage(bool explicit)
63 {
64         FILE *fp;
65
66         fp =  explicit ? stdout : stderr;
67         fprintf(fp,
68             "usage:\tbectl ( -h | -? | subcommand [args...] )\n"
69             "\tbectl activate [-t] beName\n"
70             "\tbectl create [-e nonActiveBe | -e beName@snapshot] beName\n"
71             "\tbectl create beName@snapshot\n"
72             "\tbectl destroy [-F] beName | beName@snapshot⟩\n"
73             "\tbectl export sourceBe\n"
74             "\tbectl import targetBe\n"
75             "\tbectl add (path)*\n"
76             "\tbectl jail bootenv\n"
77             "\tbectl list [-a] [-D] [-H] [-s]\n"
78             "\tbectl mount beName [mountpoint]\n"
79             "\tbectl rename origBeName newBeName\n"
80             "\tbectl { ujail | unjail } ⟨jailID | jailName | bootenv)\n"
81             "\tbectl { umount | unmount } [-f] beName\n");
82
83         return (explicit ? 0 : EX_USAGE);
84 }
85
86
87 /*
88  * Represents a relationship between the command name and the parser action
89  * that handles it.
90  */
91 struct command_map_entry {
92         const char *command;
93         int (*fn)(int argc, char *argv[]);
94 };
95
96 static struct command_map_entry command_map[] =
97 {
98         { "activate", bectl_cmd_activate },
99         { "create",   bectl_cmd_create   },
100         { "destroy",  bectl_cmd_destroy  },
101         { "export",   bectl_cmd_export   },
102         { "import",   bectl_cmd_import   },
103         { "add",      bectl_cmd_add      },
104         { "jail",     bectl_cmd_jail     },
105         { "list",     bectl_cmd_list     },
106         { "mount",    bectl_cmd_mount    },
107         { "rename",   bectl_cmd_rename   },
108         { "unjail",   bectl_cmd_unjail   },
109         { "unmount",  bectl_cmd_unmount  },
110 };
111
112 static int
113 get_cmd_index(const char *cmd, int *index)
114 {
115         int map_size;
116
117         map_size = nitems(command_map);
118         for (int i = 0; i < map_size; ++i) {
119                 if (strcmp(cmd, command_map[i].command) == 0) {
120                         *index = i;
121                         return (0);
122                 }
123         }
124
125         return (1);
126 }
127
128
129 static int
130 bectl_cmd_activate(int argc, char *argv[])
131 {
132         int err, opt;
133         bool temp;
134
135         temp = false;
136         while ((opt = getopt(argc, argv, "t")) != -1) {
137                 switch (opt) {
138                 case 't':
139                         temp = true;
140                         break;
141                 default:
142                         fprintf(stderr, "bectl activate: unknown option '-%c'\n",
143                             optopt);
144                         return (usage(false));
145                 }
146         }
147
148         argc -= optind;
149         argv += optind;
150
151         if (argc != 1) {
152                 fprintf(stderr, "bectl activate: wrong number of arguments\n");
153                 return (usage(false));
154         }
155
156
157         /* activate logic goes here */
158         if ((err = be_activate(be, argv[0], temp)) != 0)
159                 /* XXX TODO: more specific error msg based on err */
160                 printf("did not successfully activate boot environment %s\n",
161                     argv[0]);
162         else
163                 printf("successfully activated boot environment %s\n", argv[0]);
164
165         if (temp)
166                 printf("for next boot\n");
167
168         return (err);
169 }
170
171
172 /*
173  * TODO: when only one arg is given, and it contains an "@" the this should
174  * create that snapshot
175  */
176 static int
177 bectl_cmd_create(int argc, char *argv[])
178 {
179         char *bootenv, *snapname, *source;
180         int err, opt;
181
182         snapname = NULL;
183         while ((opt = getopt(argc, argv, "e:")) != -1) {
184                 switch (opt) {
185                 case 'e':
186                         snapname = optarg;
187                         break;
188                 default:
189                         fprintf(stderr, "bectl create: unknown option '-%c'\n",
190                             optopt);
191                         return (usage(false));
192                 }
193         }
194
195         argc -= optind;
196         argv += optind;
197
198         if (argc != 1) {
199                 fprintf(stderr, "bectl create: wrong number of arguments\n");
200                 return (usage(false));
201         }
202
203         bootenv = *argv;
204
205         if (snapname != NULL) {
206                 if (strchr(snapname, '@') != NULL)
207                         err = be_create_from_existing_snap(be, bootenv,
208                             snapname);
209                 else
210                         err = be_create_from_existing(be, bootenv, snapname);
211         } else {
212                 if ((snapname = strchr(bootenv, '@')) != NULL) {
213                         *(snapname++) = '\0';
214                         if ((err = be_snapshot(be, be_active_path(be),
215                             snapname, true, NULL)) != BE_ERR_SUCCESS)
216                                 fprintf(stderr, "failed to create snapshot\n");
217                         asprintf(&source, "%s@%s", be_active_path(be), snapname);
218                         err = be_create_from_existing_snap(be, bootenv,
219                             source);
220                         return (err);
221                 } else
222                         err = be_create(be, bootenv);
223         }
224
225         switch (err) {
226         case BE_ERR_SUCCESS:
227                 break;
228         default:
229                 if (snapname == NULL)
230                         fprintf(stderr,
231                             "failed to create bootenv %s\n", bootenv);
232                 else
233                         fprintf(stderr,
234                             "failed to create bootenv %s from snapshot %s\n",
235                             bootenv, snapname);
236         }
237
238         return (err);
239 }
240
241
242 static int
243 bectl_cmd_export(int argc, char *argv[])
244 {
245         char *bootenv;
246
247         if (argc == 1) {
248                 fprintf(stderr, "bectl export: missing boot environment name\n");
249                 return (usage(false));
250         }
251
252         if (argc > 2) {
253                 fprintf(stderr, "bectl export: extra arguments provided\n");
254                 return (usage(false));
255         }
256
257         bootenv = argv[1];
258
259         if (isatty(STDOUT_FILENO)) {
260                 fprintf(stderr, "bectl export: must redirect output\n");
261                 return (EX_USAGE);
262         }
263
264         be_export(be, bootenv, STDOUT_FILENO);
265
266         return (0);
267 }
268
269
270 static int
271 bectl_cmd_import(int argc, char *argv[])
272 {
273         char *bootenv;
274         int err;
275
276         if (argc == 1) {
277                 fprintf(stderr, "bectl import: missing boot environment name\n");
278                 return (usage(false));
279         }
280
281         if (argc > 2) {
282                 fprintf(stderr, "bectl import: extra arguments provided\n");
283                 return (usage(false));
284         }
285
286         bootenv = argv[1];
287
288         if (isatty(STDIN_FILENO)) {
289                 fprintf(stderr, "bectl import: input can not be from terminal\n");
290                 return (EX_USAGE);
291         }
292
293         err = be_import(be, bootenv, STDIN_FILENO);
294
295         return (err);
296 }
297
298
299 static int
300 bectl_cmd_add(int argc, char *argv[])
301 {
302
303         if (argc < 2) {
304                 fprintf(stderr, "bectl add: must provide at least one path\n");
305                 return (usage(false));
306         }
307
308         for (int i = 1; i < argc; ++i) {
309                 printf("arg %d: %s\n", i, argv[i]);
310                 /* XXX TODO catch err */
311                 be_add_child(be, argv[i], true);
312         }
313
314         return (0);
315 }
316
317
318 static int
319 bectl_cmd_destroy(int argc, char *argv[])
320 {
321         char *target;
322         int opt, err;
323         bool force;
324
325         force = false;
326         while ((opt = getopt(argc, argv, "F")) != -1) {
327                 switch (opt) {
328                 case 'F':
329                         force = true;
330                         break;
331                 default:
332                         fprintf(stderr, "bectl destroy: unknown option '-%c'\n",
333                             optopt);
334                         return (usage(false));
335                 }
336         }
337
338         argc -= optind;
339         argv += optind;
340
341         if (argc != 1) {
342                 fprintf(stderr, "bectl destroy: wrong number of arguments\n");
343                 return (usage(false));
344         }
345
346         target = argv[0];
347
348         err = be_destroy(be, target, force);
349
350         return (err);
351 }
352
353
354 static int
355 bectl_cmd_jail(int argc, char *argv[])
356 {
357         char *bootenv;
358         char mnt_loc[BE_MAXPATHLEN];
359         int err, jid;
360
361         /* struct jail be_jail = { 0 }; */
362
363         if (argc == 1) {
364                 fprintf(stderr, "bectl jail: missing boot environment name\n");
365                 return (usage(false));
366         }
367         if (argc > 2) {
368                 fprintf(stderr, "bectl jail: too many arguments\n");
369                 return (usage(false));
370         }
371
372         bootenv = argv[1];
373
374         /*
375          * XXX TODO: if its already mounted, perhaps there should be a flag to
376          * indicate its okay to proceed??
377          */
378         if ((err = be_mount(be, bootenv, NULL, 0, mnt_loc)) != BE_ERR_SUCCESS) {
379                 fprintf(stderr, "could not mount bootenv\n");
380                 return (1);
381         }
382
383         /* XXX TODO: Make the IP/hostname configurable? */
384         jid = jail_setv(JAIL_CREATE | JAIL_ATTACH,
385             "name", bootenv,
386             "path", mnt_loc,
387             "host.hostname", bootenv,
388             "persist", "true",
389             "ip4.addr", "10.20.30.40",
390             "allow.mount", "true",
391             "allow.mount.devfs", "true",
392             "enforce_statfs", "1",
393             NULL);
394         if (jid == -1) {
395                 fprintf(stderr, "unable to create jail.  error: %d\n", errno);
396                 return (1);
397         }
398
399         /* We're attached within the jail... good bye! */
400         chdir("/");
401         execl("/bin/sh", "/bin/sh", NULL);
402         return (0);
403 }
404
405
406 static int
407 bectl_cmd_list(int argc, char *argv[])
408 {
409         nvlist_t *props;
410         int opt;
411         bool show_all_datasets, show_space, hide_headers, show_snaps;
412
413         props = NULL;
414         show_all_datasets = show_space = hide_headers = show_snaps = false;
415         while ((opt = getopt(argc, argv, "aDHs")) != -1) {
416                 switch (opt) {
417                 case 'a':
418                         show_all_datasets = true;
419                         break;
420                 case 'D':
421                         show_space = true;
422                         break;
423                 case 'H':
424                         hide_headers = true;
425                         break;
426                 case 's':
427                         show_space = true;
428                         break;
429                 default:
430                         fprintf(stderr, "bectl list: unknown option '-%c'\n",
431                             optopt);
432                         return (usage(false));
433                 }
434         }
435
436         argc -= optind;
437
438         if (argc != 0) {
439                 fprintf(stderr, "bectl list: extra argument provided\n");
440                 return (usage(false));
441         }
442
443         if (be_prop_list_alloc(&props) != 0) {
444                 fprintf(stderr, "bectl list: failed to allocate prop nvlist\n");
445                 return (1);
446         }
447         if (be_get_bootenv_props(be, props) != 0) {
448                 /* XXX TODO: Real errors */
449                 fprintf(stderr, "bectl list: failed to fetch boot environments\n");
450                 return (1);
451         }
452
453         dump_nvlist(props, 0);
454         be_prop_list_free(props);
455
456         return (0);
457 }
458
459
460 static int
461 bectl_cmd_mount(int argc, char *argv[])
462 {
463         char result_loc[BE_MAXPATHLEN];
464         char *bootenv, *mountpoint;
465         int err;
466
467         if (argc < 2) {
468                 fprintf(stderr, "bectl mount: missing argument(s)\n");
469                 return (usage(false));
470         }
471
472         if (argc > 3) {
473                 fprintf(stderr, "bectl mount: too many arguments\n");
474                 return (usage(false));
475         }
476
477         bootenv = argv[1];
478         mountpoint = ((argc == 3) ? argv[2] : NULL);
479
480         err = be_mount(be, bootenv, mountpoint, 0, result_loc);
481
482         switch (err) {
483         case BE_ERR_SUCCESS:
484                 printf("successfully mounted %s at %s\n", bootenv, result_loc);
485                 break;
486         default:
487                 fprintf(stderr,
488                     (argc == 3) ? "failed to mount bootenv %s at %s\n" :
489                     "failed to mount bootenv %s at temporary path %s\n",
490                     bootenv, mountpoint);
491         }
492
493         return (err);
494 }
495
496
497 static int
498 bectl_cmd_rename(int argc, char *argv[])
499 {
500         char *dest, *src;
501         int err;
502
503         if (argc < 3) {
504                 fprintf(stderr, "bectl rename: missing argument\n");
505                 return (usage(false));
506         }
507
508         if (argc > 3) {
509                 fprintf(stderr, "bectl rename: too many arguments\n");
510                 return (usage(false));
511         }
512
513         src = argv[1];
514         dest = argv[2];
515
516         err = be_rename(be, src, dest);
517
518         switch (err) {
519         case BE_ERR_SUCCESS:
520                 break;
521         default:
522                 fprintf(stderr, "failed to rename bootenv %s to %s\n",
523                     src, dest);
524         }
525
526         return (0);
527 }
528
529 static int
530 bectl_search_jail_paths(const char *mnt)
531 {
532         char jailpath[MAXPATHLEN + 1];
533         int jid;
534
535         jid = 0;
536         (void)mnt;
537         while ((jid = jail_getv(0, "lastjid", &jid, "path", &jailpath,
538             NULL)) != -1) {
539                 if (strcmp(jailpath, mnt) == 0)
540                         return (jid);
541         }
542
543         return (-1);
544 }
545
546 /*
547  * Locate a jail based on an arbitrary identifier.  This may be either a name,
548  * a jid, or a BE name.  Returns the jid or -1 on failure.
549  */
550 static int
551 bectl_locate_jail(const char *ident)
552 {
553         nvlist_t *belist, *props;
554         char *mnt;
555         int jid;
556
557         /* Try the easy-match first */
558         jid = jail_getid(ident);
559         if (jid != -1)
560                 return (jid);
561
562         /* Attempt to try it as a BE name, first */
563         if (be_prop_list_alloc(&belist) != 0)
564                 return (-1);
565
566         if (be_get_bootenv_props(be, belist) != 0)
567                 return (-1);
568
569         if (nvlist_lookup_nvlist(belist, ident, &props) == 0) {
570                 /* We'll attempt to resolve the jid by way of mountpoint */
571                 if (nvlist_lookup_string(props, "mountpoint", &mnt) == 0) {
572                         jid = bectl_search_jail_paths(mnt);
573                         be_prop_list_free(belist);
574                         return (jid);
575                 }
576
577                 be_prop_list_free(belist);
578         }
579
580         return (-1);
581 }
582
583 static int
584 bectl_cmd_unjail(int argc, char *argv[])
585 {
586         char path[MAXPATHLEN + 1];
587         char *cmd, *name, *target;
588         int jid;
589
590         /* Store alias used */
591         cmd = argv[0];
592
593         if (argc != 2) {
594                 fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
595                 return (usage(false));
596         }
597
598         target = argv[1];
599
600         /* Locate the jail */
601         if ((jid = bectl_locate_jail(target)) == -1) {
602                 fprintf(stderr, "bectl %s: failed to locate BE by '%s'\n", cmd, target);
603                 return (1);
604         }
605
606         bzero(&path, MAXPATHLEN + 1);
607         name = jail_getname(jid);
608         if (jail_getv(0, "name", name, "path", path, NULL) != jid) {
609                 free(name);
610                 fprintf(stderr, "bectl %s: failed to get path for jail requested by '%s'\n", cmd, target);
611                 return (1);
612         }
613
614         free(name);
615
616         if (be_mounted_at(be, path, NULL) != 0) {
617                 fprintf(stderr, "bectl %s: jail requested by '%s' not a BE\n", cmd, target);
618                 return (1);
619         }
620
621         jail_remove(jid);
622         unmount(path, 0);
623
624         return (0);
625 }
626
627
628 static int
629 bectl_cmd_unmount(int argc, char *argv[])
630 {
631         char *bootenv, *cmd;
632         int err, flags, opt;
633
634         /* Store alias used */
635         cmd = argv[0];
636
637         flags = 0;
638         while ((opt = getopt(argc, argv, "f")) != -1) {
639                 switch (opt) {
640                 case 'f':
641                         flags |= BE_MNT_FORCE;
642                         break;
643                 default:
644                         fprintf(stderr, "bectl %s: unknown option '-%c'\n",
645                             cmd, optopt);
646                         return (usage(false));
647                 }
648         }
649
650         argc -= optind;
651         argv += optind;
652
653         if (argc != 1) {
654                 fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
655                 return (usage(false));
656         }
657
658         bootenv = argv[0];
659
660         err = be_unmount(be, bootenv, flags);
661
662         switch (err) {
663         case BE_ERR_SUCCESS:
664                 break;
665         default:
666                 fprintf(stderr, "failed to unmount bootenv %s\n", bootenv);
667         }
668
669         return (err);
670 }
671
672
673 int
674 main(int argc, char *argv[])
675 {
676         const char *command;
677         int command_index, rc;
678
679         if (argc < 2) {
680                 fprintf(stderr, "missing command\n");
681                 return (usage(false));
682         }
683
684         command = argv[1];
685
686         /* Handle command aliases */
687         if (strcmp(command, "umount") == 0)
688                 command = "unmount";
689
690         if (strcmp(command, "ujail") == 0)
691                 command = "unjail";
692
693         if ((strcmp(command, "-?") == 0) || (strcmp(command, "-h") == 0))
694                 return (usage(true));
695
696         if (get_cmd_index(command, &command_index)) {
697                 fprintf(stderr, "unknown command: %s\n", command);
698                 return (usage(false));
699         }
700
701
702         if ((be = libbe_init()) == NULL)
703                 return (-1);
704
705         libbe_print_on_error(be, true);
706
707         /* XXX TODO: can be simplified if offset by 2 instead of one */
708         rc = command_map[command_index].fn(argc-1, argv+1);
709
710         libbe_close(be);
711         return (rc);
712 }