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