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