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