]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/bectl/bectl.c
bectl(8): Some light cleanup and commenting
[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_BEPLUS   "BE/Dataset/Snapshot"
48 #define HEADER_ACTIVE   "Active"
49 #define HEADER_MOUNT    "Mountpoint"
50 #define HEADER_SPACE    "Space"
51 #define HEADER_CREATED  "Created"
52
53 /* Spaces */
54 #define INDENT_INCREMENT        2
55
56 struct printc {
57         int     active_colsz_def;
58         int     be_colsz;
59         int     current_indent;
60         int     mount_colsz;
61         int     space_colsz;
62         bool    script_fmt;
63         bool    show_all_datasets;
64         bool    show_snaps;
65         bool    show_space;
66 };
67
68 static int bectl_cmd_activate(int argc, char *argv[]);
69 static int bectl_cmd_create(int argc, char *argv[]);
70 static int bectl_cmd_destroy(int argc, char *argv[]);
71 static int bectl_cmd_export(int argc, char *argv[]);
72 static int bectl_cmd_import(int argc, char *argv[]);
73 static int bectl_cmd_add(int argc, char *argv[]);
74 static int bectl_cmd_jail(int argc, char *argv[]);
75 static const char *get_origin_props(nvlist_t *dsprops, nvlist_t **originprops);
76 static void print_padding(const char *fval, int colsz, struct printc *pc);
77 static int print_snapshots(const char *dsname, struct printc *pc);
78 static void print_info(const char *name, nvlist_t *dsprops, struct printc *pc);
79 static void print_headers(nvlist_t *props, struct printc *pc);
80 static int bectl_cmd_list(int argc, char *argv[]);
81 static int bectl_cmd_mount(int argc, char *argv[]);
82 static int bectl_cmd_rename(int argc, char *argv[]);
83 static int bectl_search_jail_paths(const char *mnt);
84 static int bectl_locate_jail(const char *ident);
85 static int bectl_cmd_unjail(int argc, char *argv[]);
86 static int bectl_cmd_unmount(int argc, char *argv[]);
87
88 static libbe_handle_t *be;
89
90 static int
91 usage(bool explicit)
92 {
93         FILE *fp;
94
95         fp =  explicit ? stdout : stderr;
96         fprintf(fp,
97             "usage:\tbectl ( -h | -? | subcommand [args...] )\n"
98             "\tbectl activate [-t] beName\n"
99             "\tbectl create [-e nonActiveBe | -e beName@snapshot] beName\n"
100             "\tbectl create beName@snapshot\n"
101             "\tbectl destroy [-F] beName | beName@snapshot⟩\n"
102             "\tbectl export sourceBe\n"
103             "\tbectl import targetBe\n"
104             "\tbectl add (path)*\n"
105             "\tbectl jail bootenv\n"
106             "\tbectl list [-a] [-D] [-H] [-s]\n"
107             "\tbectl mount beName [mountpoint]\n"
108             "\tbectl rename origBeName newBeName\n"
109             "\tbectl { ujail | unjail } ⟨jailID | jailName | bootenv)\n"
110             "\tbectl { umount | unmount } [-f] beName\n");
111
112         return (explicit ? 0 : EX_USAGE);
113 }
114
115
116 /*
117  * Represents a relationship between the command name and the parser action
118  * that handles it.
119  */
120 struct command_map_entry {
121         const char *command;
122         int (*fn)(int argc, char *argv[]);
123 };
124
125 static struct command_map_entry command_map[] =
126 {
127         { "activate", bectl_cmd_activate },
128         { "create",   bectl_cmd_create   },
129         { "destroy",  bectl_cmd_destroy  },
130         { "export",   bectl_cmd_export   },
131         { "import",   bectl_cmd_import   },
132         { "add",      bectl_cmd_add      },
133         { "jail",     bectl_cmd_jail     },
134         { "list",     bectl_cmd_list     },
135         { "mount",    bectl_cmd_mount    },
136         { "rename",   bectl_cmd_rename   },
137         { "unjail",   bectl_cmd_unjail   },
138         { "unmount",  bectl_cmd_unmount  },
139 };
140
141 static int
142 get_cmd_index(const char *cmd, int *index)
143 {
144         int map_size;
145
146         map_size = nitems(command_map);
147         for (int i = 0; i < map_size; ++i) {
148                 if (strcmp(cmd, command_map[i].command) == 0) {
149                         *index = i;
150                         return (0);
151                 }
152         }
153
154         return (1);
155 }
156
157
158 static int
159 bectl_cmd_activate(int argc, char *argv[])
160 {
161         int err, opt;
162         bool temp;
163
164         temp = false;
165         while ((opt = getopt(argc, argv, "t")) != -1) {
166                 switch (opt) {
167                 case 't':
168                         temp = true;
169                         break;
170                 default:
171                         fprintf(stderr, "bectl activate: unknown option '-%c'\n",
172                             optopt);
173                         return (usage(false));
174                 }
175         }
176
177         argc -= optind;
178         argv += optind;
179
180         if (argc != 1) {
181                 fprintf(stderr, "bectl activate: wrong number of arguments\n");
182                 return (usage(false));
183         }
184
185
186         /* activate logic goes here */
187         if ((err = be_activate(be, argv[0], temp)) != 0)
188                 /* XXX TODO: more specific error msg based on err */
189                 printf("did not successfully activate boot environment %s\n",
190                     argv[0]);
191         else
192                 printf("successfully activated boot environment %s\n", argv[0]);
193
194         if (temp)
195                 printf("for next boot\n");
196
197         return (err);
198 }
199
200
201 /*
202  * TODO: when only one arg is given, and it contains an "@" the this should
203  * create that snapshot
204  */
205 static int
206 bectl_cmd_create(int argc, char *argv[])
207 {
208         char *bootenv, *snapname, *source;
209         int err, opt;
210
211         snapname = NULL;
212         while ((opt = getopt(argc, argv, "e:")) != -1) {
213                 switch (opt) {
214                 case 'e':
215                         snapname = optarg;
216                         break;
217                 default:
218                         fprintf(stderr, "bectl create: unknown option '-%c'\n",
219                             optopt);
220                         return (usage(false));
221                 }
222         }
223
224         argc -= optind;
225         argv += optind;
226
227         if (argc != 1) {
228                 fprintf(stderr, "bectl create: wrong number of arguments\n");
229                 return (usage(false));
230         }
231
232         bootenv = *argv;
233
234         if (snapname != NULL) {
235                 if (strchr(snapname, '@') != NULL)
236                         err = be_create_from_existing_snap(be, bootenv,
237                             snapname);
238                 else
239                         err = be_create_from_existing(be, bootenv, snapname);
240         } else {
241                 if ((snapname = strchr(bootenv, '@')) != NULL) {
242                         *(snapname++) = '\0';
243                         if ((err = be_snapshot(be, be_active_path(be),
244                             snapname, true, NULL)) != BE_ERR_SUCCESS)
245                                 fprintf(stderr, "failed to create snapshot\n");
246                         asprintf(&source, "%s@%s", be_active_path(be), snapname);
247                         err = be_create_from_existing_snap(be, bootenv,
248                             source);
249                         return (err);
250                 } else
251                         err = be_create(be, bootenv);
252         }
253
254         switch (err) {
255         case BE_ERR_SUCCESS:
256                 break;
257         default:
258                 if (snapname == NULL)
259                         fprintf(stderr,
260                             "failed to create bootenv %s\n", bootenv);
261                 else
262                         fprintf(stderr,
263                             "failed to create bootenv %s from snapshot %s\n",
264                             bootenv, snapname);
265         }
266
267         return (err);
268 }
269
270
271 static int
272 bectl_cmd_export(int argc, char *argv[])
273 {
274         char *bootenv;
275
276         if (argc == 1) {
277                 fprintf(stderr, "bectl export: missing boot environment name\n");
278                 return (usage(false));
279         }
280
281         if (argc > 2) {
282                 fprintf(stderr, "bectl export: extra arguments provided\n");
283                 return (usage(false));
284         }
285
286         bootenv = argv[1];
287
288         if (isatty(STDOUT_FILENO)) {
289                 fprintf(stderr, "bectl export: must redirect output\n");
290                 return (EX_USAGE);
291         }
292
293         be_export(be, bootenv, STDOUT_FILENO);
294
295         return (0);
296 }
297
298
299 static int
300 bectl_cmd_import(int argc, char *argv[])
301 {
302         char *bootenv;
303         int err;
304
305         if (argc == 1) {
306                 fprintf(stderr, "bectl import: missing boot environment name\n");
307                 return (usage(false));
308         }
309
310         if (argc > 2) {
311                 fprintf(stderr, "bectl import: extra arguments provided\n");
312                 return (usage(false));
313         }
314
315         bootenv = argv[1];
316
317         if (isatty(STDIN_FILENO)) {
318                 fprintf(stderr, "bectl import: input can not be from terminal\n");
319                 return (EX_USAGE);
320         }
321
322         err = be_import(be, bootenv, STDIN_FILENO);
323
324         return (err);
325 }
326
327
328 static int
329 bectl_cmd_add(int argc, char *argv[])
330 {
331
332         if (argc < 2) {
333                 fprintf(stderr, "bectl add: must provide at least one path\n");
334                 return (usage(false));
335         }
336
337         for (int i = 1; i < argc; ++i) {
338                 printf("arg %d: %s\n", i, argv[i]);
339                 /* XXX TODO catch err */
340                 be_add_child(be, argv[i], true);
341         }
342
343         return (0);
344 }
345
346
347 static int
348 bectl_cmd_destroy(int argc, char *argv[])
349 {
350         char *target;
351         int opt, err;
352         bool force;
353
354         force = false;
355         while ((opt = getopt(argc, argv, "F")) != -1) {
356                 switch (opt) {
357                 case 'F':
358                         force = true;
359                         break;
360                 default:
361                         fprintf(stderr, "bectl destroy: unknown option '-%c'\n",
362                             optopt);
363                         return (usage(false));
364                 }
365         }
366
367         argc -= optind;
368         argv += optind;
369
370         if (argc != 1) {
371                 fprintf(stderr, "bectl destroy: wrong number of arguments\n");
372                 return (usage(false));
373         }
374
375         target = argv[0];
376
377         err = be_destroy(be, target, force);
378
379         return (err);
380 }
381
382
383 static int
384 bectl_cmd_jail(int argc, char *argv[])
385 {
386         char *bootenv;
387         char mnt_loc[BE_MAXPATHLEN];
388         int err, jid;
389
390         /* struct jail be_jail = { 0 }; */
391
392         if (argc == 1) {
393                 fprintf(stderr, "bectl jail: missing boot environment name\n");
394                 return (usage(false));
395         }
396         if (argc > 2) {
397                 fprintf(stderr, "bectl jail: too many arguments\n");
398                 return (usage(false));
399         }
400
401         bootenv = argv[1];
402
403         /*
404          * XXX TODO: if its already mounted, perhaps there should be a flag to
405          * indicate its okay to proceed??
406          */
407         if ((err = be_mount(be, bootenv, NULL, 0, mnt_loc)) != BE_ERR_SUCCESS) {
408                 fprintf(stderr, "could not mount bootenv\n");
409                 return (1);
410         }
411
412         /* XXX TODO: Make the IP/hostname configurable? */
413         jid = jail_setv(JAIL_CREATE | JAIL_ATTACH,
414             "name", bootenv,
415             "path", mnt_loc,
416             "host.hostname", bootenv,
417             "persist", "true",
418             "ip4.addr", "10.20.30.40",
419             "allow.mount", "true",
420             "allow.mount.devfs", "true",
421             "enforce_statfs", "1",
422             NULL);
423         if (jid == -1) {
424                 fprintf(stderr, "unable to create jail.  error: %d\n", errno);
425                 return (1);
426         }
427
428         /* We're attached within the jail... good bye! */
429         chdir("/");
430         execl("/bin/sh", "/bin/sh", NULL);
431         return (0);
432 }
433
434 /*
435  * Given a set of dataset properties (for a BE dataset), populate originprops
436  * with the origin's properties.
437  */
438 static const char *
439 get_origin_props(nvlist_t *dsprops, nvlist_t **originprops)
440 {
441         char *propstr;
442
443         if (nvlist_lookup_string(dsprops, "origin", &propstr) == 0) {
444                 if (be_prop_list_alloc(originprops) != 0) {
445                         fprintf(stderr,
446                             "bectl list: failed to allocate origin prop nvlist\n");
447                         return (NULL);
448                 }
449                 if (be_get_dataset_props(be, propstr, *originprops) != 0) {
450                         /* XXX TODO: Real errors */
451                         fprintf(stderr,
452                             "bectl list: failed to fetch origin properties\n");
453                         return (NULL);
454                 }
455
456                 return (propstr);
457         }
458         return (NULL);
459 }
460
461 static void
462 print_padding(const char *fval, int colsz, struct printc *pc)
463 {
464
465         /* -H flag handling; all delimiters/padding are a single tab */
466         if (pc->script_fmt) {
467                 printf("\t");
468                 return;
469         }
470
471         if (fval != NULL)
472                 colsz -= strlen(fval);
473         printf("%*s ", colsz, "");
474 }
475
476 static unsigned long long
477 dataset_space(const char *oname)
478 {
479         unsigned long long space;
480         char *dsname, *propstr, *sep;
481         nvlist_t *dsprops;
482
483         space = 0;
484         dsname = strdup(oname);
485         if (dsname == NULL)
486                 return (0);
487
488         /* Truncate snapshot to dataset name, as needed */
489         if ((sep = strchr(dsname, '@')) != NULL)
490                 *sep = '\0';
491
492         if (be_prop_list_alloc(&dsprops) != 0) {
493                 free(dsname);
494                 return (0);
495         }
496
497         if (be_get_dataset_props(be, dsname, dsprops) != 0) {
498                 nvlist_free(dsprops);
499                 free(dsname);
500                 return (0);
501         }
502
503         if (nvlist_lookup_string(dsprops, "used", &propstr) == 0)
504                 space = strtoull(propstr, NULL, 10);
505
506         nvlist_free(dsprops);
507         free(dsname);
508         return (space);
509 }
510
511 static int
512 print_snapshots(const char *dsname, struct printc *pc)
513 {
514         nvpair_t *cur;
515         nvlist_t *props, *sprops;
516
517         if (be_prop_list_alloc(&props) != 0) {
518                 fprintf(stderr, "bectl list: failed to allocate snapshot nvlist\n");
519                 return (1);
520         }
521         if (be_get_dataset_snapshots(be, dsname, props) != 0) {
522                 fprintf(stderr, "bectl list: failed to fetch boot ds snapshots\n");
523                 return (1);
524         }
525         for (cur = nvlist_next_nvpair(props, NULL); cur != NULL;
526             cur = nvlist_next_nvpair(props, cur)) {
527                 nvpair_value_nvlist(cur, &sprops);
528                 print_info(nvpair_name(cur), sprops, pc);
529         }
530         return (0);
531 }
532
533 static void
534 print_info(const char *name, nvlist_t *dsprops, struct printc *pc)
535 {
536 #define BUFSZ   64
537         char buf[BUFSZ];
538         unsigned long long ctimenum, space;
539         nvlist_t *originprops;
540         const char *oname;
541         char *dsname, *propstr;
542         int active_colsz;
543         boolean_t active_now, active_reboot;
544
545         dsname = NULL;
546         originprops = NULL;
547         printf("%*s%s", pc->current_indent, "", name);
548         nvlist_lookup_string(dsprops, "dataset", &dsname);
549
550         /* Recurse at the base level if we're breaking info down */
551         if (pc->current_indent == 0 && (pc->show_all_datasets ||
552             pc->show_snaps)) {
553                 printf("\n");
554                 if (dsname == NULL)
555                         /* XXX TODO: Error? */
556                         return;
557                 /*
558                  * Whether we're dealing with -a or -s, we'll always print the
559                  * dataset name/information followed by its origin. For -s, we
560                  * additionally iterate through all snapshots of this boot
561                  * environment and also print their information.
562                  */
563                 pc->current_indent += INDENT_INCREMENT;
564                 print_info(dsname, dsprops, pc);
565                 pc->current_indent += INDENT_INCREMENT;
566                 if ((oname = get_origin_props(dsprops, &originprops)) != NULL) {
567                         print_info(oname, originprops, pc);
568                         nvlist_free(originprops);
569                 }
570
571                 /* Back up a level; snapshots at the same level as dataset */
572                 pc->current_indent -= INDENT_INCREMENT;
573                 if (pc->show_snaps)
574                         print_snapshots(dsname, pc);
575                 pc->current_indent = 0;
576                 return;
577         } else
578                 print_padding(name, pc->be_colsz - pc->current_indent, pc);
579
580         active_colsz = pc->active_colsz_def;
581         if (nvlist_lookup_boolean_value(dsprops, "active",
582             &active_now) == 0 && active_now) {
583                 printf("N");
584                 active_colsz--;
585         }
586         if (nvlist_lookup_boolean_value(dsprops, "nextboot",
587             &active_reboot) == 0 && active_reboot) {
588                 printf("R");
589                 active_colsz--;
590         }
591         if (active_colsz == pc->active_colsz_def) {
592                 printf("-");
593                 active_colsz--;
594         }
595         print_padding(NULL, active_colsz, pc);
596         if (nvlist_lookup_string(dsprops, "mountpoint", &propstr) == 0){
597                 printf("%s", propstr);
598                 print_padding(propstr, pc->mount_colsz, pc);
599         } else {
600                 printf("%s", "-");
601                 print_padding("-", pc->mount_colsz, pc);
602         }
603
604         oname = get_origin_props(dsprops, &originprops);
605         if (nvlist_lookup_string(dsprops, "used", &propstr) == 0) {
606                 /*
607                  * The space used column is some composition of:
608                  * - The "used" property of the dataset
609                  * - The "used" property of the origin snapshot (not -a or -s)
610                  * - The "used" property of the origin dataset (-D flag only)
611                  *
612                  * The -D flag is ignored if -a or -s are specified.
613                  */
614                 space = strtoull(propstr, NULL, 10);
615
616                 if (!pc->show_all_datasets && !pc->show_snaps &&
617                     originprops != NULL &&
618                     nvlist_lookup_string(originprops, "used", &propstr) == 0)
619                         space += strtoull(propstr, NULL, 10);
620
621                 if (pc->show_space && oname != NULL)
622                         space += dataset_space(oname);
623
624                 /* Alas, there's more to it,. */
625                 be_nicenum(space, buf, 6);
626                 printf("%s", buf);
627                 print_padding(buf, pc->space_colsz, pc);
628         } else {
629                 printf("-");
630                 print_padding("-", pc->space_colsz, pc);
631         }
632
633         if (nvlist_lookup_string(dsprops, "creation", &propstr) == 0) {
634                 ctimenum = strtoull(propstr, NULL, 10);
635                 strftime(buf, BUFSZ, "%Y-%m-%d %H:%M",
636                     localtime((time_t *)&ctimenum));
637                 printf("%s", buf);
638         }
639
640         printf("\n");
641         if (originprops != NULL)
642                 be_prop_list_free(originprops);
643 #undef BUFSZ
644 }
645
646 static void
647 print_headers(nvlist_t *props, struct printc *pc)
648 {
649         const char *chosen_be_header;
650         nvpair_t *cur;
651         nvlist_t *dsprops;
652         char *propstr;
653         size_t be_maxcol;
654
655         if (pc->show_all_datasets || pc->show_snaps)
656                 chosen_be_header = HEADER_BEPLUS;
657         else
658                 chosen_be_header = HEADER_BE;
659         be_maxcol = strlen(chosen_be_header);
660         for (cur = nvlist_next_nvpair(props, NULL); cur != NULL;
661             cur = nvlist_next_nvpair(props, cur)) {
662                 be_maxcol = MAX(be_maxcol, strlen(nvpair_name(cur)));
663                 if (!pc->show_all_datasets && !pc->show_snaps)
664                         continue;
665                 nvpair_value_nvlist(cur, &dsprops);
666                 if (nvlist_lookup_string(dsprops, "dataset", &propstr) != 0)
667                         continue;
668                 be_maxcol = MAX(be_maxcol, strlen(propstr) + INDENT_INCREMENT);
669                 if (nvlist_lookup_string(dsprops, "origin", &propstr) != 0)
670                         continue;
671                 be_maxcol = MAX(be_maxcol,
672                     strlen(propstr) + INDENT_INCREMENT * 2);
673         }
674
675         pc->be_colsz = be_maxcol;
676         pc->active_colsz_def = strlen(HEADER_ACTIVE);
677         pc->mount_colsz = strlen(HEADER_MOUNT);
678         pc->space_colsz = strlen(HEADER_SPACE);
679         printf("%*s %s %s %s %s\n", -pc->be_colsz, chosen_be_header,
680             HEADER_ACTIVE, HEADER_MOUNT, HEADER_SPACE, HEADER_CREATED);
681
682         /*
683          * All other invocations in which we aren't using the default header
684          * will produce quite a bit of input.  Throw an extra blank line after
685          * the header to make it look nicer.
686          */
687         if (chosen_be_header != HEADER_BE)
688                 printf("\n");
689 }
690
691 static int
692 bectl_cmd_list(int argc, char *argv[])
693 {
694         struct printc pc;
695         nvpair_t *cur;
696         nvlist_t *dsprops, *props;
697         int opt, printed;
698         boolean_t active_now, active_reboot;
699
700         props = NULL;
701         printed = 0;
702         bzero(&pc, sizeof(pc));
703         while ((opt = getopt(argc, argv, "aDHs")) != -1) {
704                 switch (opt) {
705                 case 'a':
706                         pc.show_all_datasets = true;
707                         break;
708                 case 'D':
709                         pc.show_space = true;
710                         break;
711                 case 'H':
712                         pc.script_fmt = true;
713                         break;
714                 case 's':
715                         pc.show_snaps = true;
716                         break;
717                 default:
718                         fprintf(stderr, "bectl list: unknown option '-%c'\n",
719                             optopt);
720                         return (usage(false));
721                 }
722         }
723
724         argc -= optind;
725
726         if (argc != 0) {
727                 fprintf(stderr, "bectl list: extra argument provided\n");
728                 return (usage(false));
729         }
730
731         if (be_prop_list_alloc(&props) != 0) {
732                 fprintf(stderr, "bectl list: failed to allocate prop nvlist\n");
733                 return (1);
734         }
735         if (be_get_bootenv_props(be, props) != 0) {
736                 /* XXX TODO: Real errors */
737                 fprintf(stderr, "bectl list: failed to fetch boot environments\n");
738                 return (1);
739         }
740
741         /* Force -D off if either -a or -s are specified */
742         if (pc.show_all_datasets || pc.show_snaps)
743                 pc.show_space = false;
744         if (!pc.script_fmt)
745                 print_headers(props, &pc);
746         /* Do a first pass to print active and next active first */
747         for (cur = nvlist_next_nvpair(props, NULL); cur != NULL;
748             cur = nvlist_next_nvpair(props, cur)) {
749                 nvpair_value_nvlist(cur, &dsprops);
750                 active_now = active_reboot = false;
751
752                 nvlist_lookup_boolean_value(dsprops, "active", &active_now);
753                 nvlist_lookup_boolean_value(dsprops, "nextboot",
754                     &active_reboot);
755                 if (!active_now && !active_reboot)
756                         continue;
757                 if (printed > 0 && (pc.show_all_datasets || pc.show_snaps))
758                         printf("\n");
759                 print_info(nvpair_name(cur), dsprops, &pc);
760                 printed++;
761         }
762
763         /* Now pull everything else */
764         for (cur = nvlist_next_nvpair(props, NULL); cur != NULL;
765             cur = nvlist_next_nvpair(props, cur)) {
766                 nvpair_value_nvlist(cur, &dsprops);
767                 active_now = active_reboot = false;
768
769                 nvlist_lookup_boolean_value(dsprops, "active", &active_now);
770                 nvlist_lookup_boolean_value(dsprops, "nextboot",
771                     &active_reboot);
772                 if (active_now || active_reboot)
773                         continue;
774                 if (printed > 0 && (pc.show_all_datasets || pc.show_snaps))
775                         printf("\n");
776                 print_info(nvpair_name(cur), dsprops, &pc);
777                 printed++;
778         }
779         be_prop_list_free(props);
780
781         return (0);
782 }
783
784
785 static int
786 bectl_cmd_mount(int argc, char *argv[])
787 {
788         char result_loc[BE_MAXPATHLEN];
789         char *bootenv, *mountpoint;
790         int err;
791
792         if (argc < 2) {
793                 fprintf(stderr, "bectl mount: missing argument(s)\n");
794                 return (usage(false));
795         }
796
797         if (argc > 3) {
798                 fprintf(stderr, "bectl mount: too many arguments\n");
799                 return (usage(false));
800         }
801
802         bootenv = argv[1];
803         mountpoint = ((argc == 3) ? argv[2] : NULL);
804
805         err = be_mount(be, bootenv, mountpoint, 0, result_loc);
806
807         switch (err) {
808         case BE_ERR_SUCCESS:
809                 printf("successfully mounted %s at %s\n", bootenv, result_loc);
810                 break;
811         default:
812                 fprintf(stderr,
813                     (argc == 3) ? "failed to mount bootenv %s at %s\n" :
814                     "failed to mount bootenv %s at temporary path %s\n",
815                     bootenv, mountpoint);
816         }
817
818         return (err);
819 }
820
821
822 static int
823 bectl_cmd_rename(int argc, char *argv[])
824 {
825         char *dest, *src;
826         int err;
827
828         if (argc < 3) {
829                 fprintf(stderr, "bectl rename: missing argument\n");
830                 return (usage(false));
831         }
832
833         if (argc > 3) {
834                 fprintf(stderr, "bectl rename: too many arguments\n");
835                 return (usage(false));
836         }
837
838         src = argv[1];
839         dest = argv[2];
840
841         err = be_rename(be, src, dest);
842
843         switch (err) {
844         case BE_ERR_SUCCESS:
845                 break;
846         default:
847                 fprintf(stderr, "failed to rename bootenv %s to %s\n",
848                     src, dest);
849         }
850
851         return (0);
852 }
853
854 static int
855 bectl_search_jail_paths(const char *mnt)
856 {
857         char jailpath[MAXPATHLEN + 1];
858         int jid;
859
860         jid = 0;
861         (void)mnt;
862         while ((jid = jail_getv(0, "lastjid", &jid, "path", &jailpath,
863             NULL)) != -1) {
864                 if (strcmp(jailpath, mnt) == 0)
865                         return (jid);
866         }
867
868         return (-1);
869 }
870
871 /*
872  * Locate a jail based on an arbitrary identifier.  This may be either a name,
873  * a jid, or a BE name.  Returns the jid or -1 on failure.
874  */
875 static int
876 bectl_locate_jail(const char *ident)
877 {
878         nvlist_t *belist, *props;
879         char *mnt;
880         int jid;
881
882         /* Try the easy-match first */
883         jid = jail_getid(ident);
884         if (jid != -1)
885                 return (jid);
886
887         /* Attempt to try it as a BE name, first */
888         if (be_prop_list_alloc(&belist) != 0)
889                 return (-1);
890
891         if (be_get_bootenv_props(be, belist) != 0)
892                 return (-1);
893
894         if (nvlist_lookup_nvlist(belist, ident, &props) == 0) {
895                 /* We'll attempt to resolve the jid by way of mountpoint */
896                 if (nvlist_lookup_string(props, "mountpoint", &mnt) == 0) {
897                         jid = bectl_search_jail_paths(mnt);
898                         be_prop_list_free(belist);
899                         return (jid);
900                 }
901
902                 be_prop_list_free(belist);
903         }
904
905         return (-1);
906 }
907
908 static int
909 bectl_cmd_unjail(int argc, char *argv[])
910 {
911         char path[MAXPATHLEN + 1];
912         char *cmd, *name, *target;
913         int jid;
914
915         /* Store alias used */
916         cmd = argv[0];
917
918         if (argc != 2) {
919                 fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
920                 return (usage(false));
921         }
922
923         target = argv[1];
924
925         /* Locate the jail */
926         if ((jid = bectl_locate_jail(target)) == -1) {
927                 fprintf(stderr, "bectl %s: failed to locate BE by '%s'\n", cmd, target);
928                 return (1);
929         }
930
931         bzero(&path, MAXPATHLEN + 1);
932         name = jail_getname(jid);
933         if (jail_getv(0, "name", name, "path", path, NULL) != jid) {
934                 free(name);
935                 fprintf(stderr, "bectl %s: failed to get path for jail requested by '%s'\n", cmd, target);
936                 return (1);
937         }
938
939         free(name);
940
941         if (be_mounted_at(be, path, NULL) != 0) {
942                 fprintf(stderr, "bectl %s: jail requested by '%s' not a BE\n", cmd, target);
943                 return (1);
944         }
945
946         jail_remove(jid);
947         unmount(path, 0);
948
949         return (0);
950 }
951
952
953 static int
954 bectl_cmd_unmount(int argc, char *argv[])
955 {
956         char *bootenv, *cmd;
957         int err, flags, opt;
958
959         /* Store alias used */
960         cmd = argv[0];
961
962         flags = 0;
963         while ((opt = getopt(argc, argv, "f")) != -1) {
964                 switch (opt) {
965                 case 'f':
966                         flags |= BE_MNT_FORCE;
967                         break;
968                 default:
969                         fprintf(stderr, "bectl %s: unknown option '-%c'\n",
970                             cmd, optopt);
971                         return (usage(false));
972                 }
973         }
974
975         argc -= optind;
976         argv += optind;
977
978         if (argc != 1) {
979                 fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
980                 return (usage(false));
981         }
982
983         bootenv = argv[0];
984
985         err = be_unmount(be, bootenv, flags);
986
987         switch (err) {
988         case BE_ERR_SUCCESS:
989                 break;
990         default:
991                 fprintf(stderr, "failed to unmount bootenv %s\n", bootenv);
992         }
993
994         return (err);
995 }
996
997
998 int
999 main(int argc, char *argv[])
1000 {
1001         const char *command;
1002         int command_index, rc;
1003
1004         if (argc < 2) {
1005                 fprintf(stderr, "missing command\n");
1006                 return (usage(false));
1007         }
1008
1009         command = argv[1];
1010
1011         /* Handle command aliases */
1012         if (strcmp(command, "umount") == 0)
1013                 command = "unmount";
1014
1015         if (strcmp(command, "ujail") == 0)
1016                 command = "unjail";
1017
1018         if ((strcmp(command, "-?") == 0) || (strcmp(command, "-h") == 0))
1019                 return (usage(true));
1020
1021         if (get_cmd_index(command, &command_index)) {
1022                 fprintf(stderr, "unknown command: %s\n", command);
1023                 return (usage(false));
1024         }
1025
1026
1027         if ((be = libbe_init()) == NULL)
1028                 return (-1);
1029
1030         libbe_print_on_error(be, true);
1031
1032         /* XXX TODO: can be simplified if offset by 2 instead of one */
1033         rc = command_map[command_index].fn(argc-1, argv+1);
1034
1035         libbe_close(be);
1036         return (rc);
1037 }