Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/sbin/bectl/bectl.c
104425 views
1
/*
2
* Copyright (c) 2017 Kyle J. Kneitinger <[email protected]>
3
*
4
* SPDX-License-Identifier: BSD-2-Clause
5
*/
6
7
#include <sys/param.h>
8
#include <sys/mount.h>
9
#include <err.h>
10
#include <errno.h>
11
#include <libutil.h>
12
#include <stdbool.h>
13
#include <stdio.h>
14
#include <stdint.h>
15
#include <stdlib.h>
16
#include <string.h>
17
#include <sysexits.h>
18
#include <time.h>
19
#include <unistd.h>
20
21
#include <be.h>
22
23
#include "bectl.h"
24
25
static int bectl_cmd_activate(int argc, char *argv[]);
26
static int bectl_cmd_check(int argc, char *argv[]);
27
static int bectl_cmd_create(int argc, char *argv[]);
28
static int bectl_cmd_destroy(int argc, char *argv[]);
29
static int bectl_cmd_export(int argc, char *argv[]);
30
static int bectl_cmd_import(int argc, char *argv[]);
31
#if SOON
32
static int bectl_cmd_add(int argc, char *argv[]);
33
#endif
34
static int bectl_cmd_mount(int argc, char *argv[]);
35
static int bectl_cmd_rename(int argc, char *argv[]);
36
static int bectl_cmd_unmount(int argc, char *argv[]);
37
38
libbe_handle_t *be;
39
40
int
41
usage(bool explicit)
42
{
43
FILE *fp;
44
45
fp = explicit ? stdout : stderr;
46
fprintf(fp, "%s",
47
"Usage:\tbectl {-h | subcommand [args...]}\n"
48
#if SOON
49
"\tbectl [-r beroot] add (path)*\n"
50
#endif
51
"\tbectl [-r beroot] activate [-t] beName\n"
52
"\tbectl [-r beroot] activate [-T]\n"
53
"\tbectl [-r beroot] check\n"
54
"\tbectl [-r beroot] create [-r] [-e {nonActiveBe | beName@snapshot}] beName\n"
55
"\tbectl [-r beroot] create [-r] beName@snapshot\n"
56
"\tbectl [-r beroot] create -E beName\n"
57
"\tbectl [-r beroot] destroy [-Fo] {beName | beName@snapshot}\n"
58
"\tbectl [-r beroot] export sourceBe\n"
59
"\tbectl [-r beroot] import targetBe\n"
60
"\tbectl [-r beroot] jail [-bU] [{-o key=value | -u key}]... beName\n"
61
"\t [utility [argument ...]]\n"
62
"\tbectl [-r beroot] list [-aDHs] [{-c property | -C property}]\n"
63
"\tbectl [-r beroot] mount beName [mountpoint]\n"
64
"\tbectl [-r beroot] rename origBeName newBeName\n"
65
"\tbectl [-r beroot] {ujail | unjail} {jailID | jailName | beName}\n"
66
"\tbectl [-r beroot] {umount | unmount} [-f] beName\n");
67
68
return (explicit ? 0 : EX_USAGE);
69
}
70
71
72
/*
73
* Represents a relationship between the command name and the parser action
74
* that handles it.
75
*/
76
struct command_map_entry {
77
const char *command;
78
int (*fn)(int argc, char *argv[]);
79
/* True if libbe_print_on_error should be disabled */
80
bool silent;
81
bool save_history;
82
};
83
84
static struct command_map_entry command_map[] =
85
{
86
{ "activate", bectl_cmd_activate,false, true },
87
{ "create", bectl_cmd_create, false, true },
88
{ "destroy", bectl_cmd_destroy, false, true },
89
{ "export", bectl_cmd_export, false, true },
90
{ "import", bectl_cmd_import, false, true },
91
#if SOON
92
{ "add", bectl_cmd_add, false, true },
93
#endif
94
{ "jail", bectl_cmd_jail, false, false },
95
{ "list", bectl_cmd_list, false, false },
96
{ "mount", bectl_cmd_mount, false, false },
97
{ "rename", bectl_cmd_rename, false, true },
98
{ "unjail", bectl_cmd_unjail, false, false },
99
{ "ujail", bectl_cmd_unjail, false, false },
100
{ "unmount", bectl_cmd_unmount, false, false },
101
{ "umount", bectl_cmd_unmount, false, false },
102
{ "check", bectl_cmd_check, true, false },
103
};
104
105
static struct command_map_entry *
106
get_cmd_info(const char *cmd)
107
{
108
size_t i;
109
110
for (i = 0; i < nitems(command_map); ++i) {
111
if (strcmp(cmd, command_map[i].command) == 0)
112
return (&command_map[i]);
113
}
114
115
return (NULL);
116
}
117
118
static int
119
bectl_cmd_activate(int argc, char *argv[])
120
{
121
int err, opt;
122
bool temp, reset;
123
124
temp = false;
125
reset = false;
126
while ((opt = getopt(argc, argv, "tT")) != -1) {
127
switch (opt) {
128
case 't':
129
if (reset)
130
return (usage(false));
131
temp = true;
132
break;
133
case 'T':
134
if (temp)
135
return (usage(false));
136
reset = 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 && (!reset || argc != 0)) {
149
fprintf(stderr, "bectl activate: wrong number of arguments\n");
150
return (usage(false));
151
}
152
153
if (reset) {
154
if ((err = be_deactivate(be, NULL, reset)) == 0)
155
printf("Temporary activation removed\n");
156
else
157
printf("Failed to remove temporary activation\n");
158
return (err);
159
}
160
161
/* activate logic goes here */
162
if ((err = be_activate(be, argv[0], temp)) != 0)
163
/* XXX TODO: more specific error msg based on err */
164
printf("Did not successfully activate boot environment %s",
165
argv[0]);
166
else
167
printf("Successfully activated boot environment %s", argv[0]);
168
169
if (temp)
170
printf(" for next boot");
171
172
printf("\n");
173
174
return (err);
175
}
176
177
178
/*
179
* TODO: when only one arg is given, and it contains an "@" the this should
180
* create that snapshot
181
*/
182
static int
183
bectl_cmd_create(int argc, char *argv[])
184
{
185
char snapshot[BE_MAXPATHLEN];
186
char *atpos, *bootenv, *snapname;
187
int err, opt;
188
bool empty, recursive;
189
190
snapname = NULL;
191
empty = false;
192
recursive = false;
193
while ((opt = getopt(argc, argv, "e:Er")) != -1) {
194
switch (opt) {
195
case 'e':
196
snapname = optarg;
197
break;
198
case 'E':
199
empty = true;
200
break;
201
case 'r':
202
recursive = true;
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
err = BE_ERR_SUCCESS;
222
if ((atpos = strchr(bootenv, '@')) != NULL) {
223
/*
224
* This is the "create a snapshot variant". No new boot
225
* environment is to be created here.
226
*/
227
*atpos++ = '\0';
228
err = be_snapshot(be, bootenv, atpos, recursive, NULL);
229
} else if (empty) {
230
if (snapname || recursive) {
231
fprintf(stderr,
232
"bectl create: -E cannot be combined with -e or -r\n");
233
return (usage(false));
234
}
235
err = be_create_empty(be, bootenv);
236
} else {
237
if (snapname == NULL)
238
/* Create from currently booted BE */
239
err = be_snapshot(be, be_active_path(be), NULL,
240
recursive, snapshot);
241
else if (strchr(snapname, '@') != NULL)
242
/* Create from given snapshot */
243
strlcpy(snapshot, snapname, sizeof(snapshot));
244
else
245
/* Create from given BE */
246
err = be_snapshot(be, snapname, NULL, recursive,
247
snapshot);
248
249
if (err == BE_ERR_SUCCESS)
250
err = be_create_depth(be, bootenv, snapshot,
251
recursive == true ? -1 : 0);
252
}
253
254
switch (err) {
255
case BE_ERR_SUCCESS:
256
break;
257
case BE_ERR_INVALIDNAME:
258
fprintf(stderr,
259
"bectl create: boot environment name must not contain spaces\n");
260
break;
261
default:
262
if (atpos != NULL)
263
fprintf(stderr,
264
"Failed to create a snapshot '%s' of '%s'\n",
265
atpos, bootenv);
266
else if (snapname == NULL)
267
fprintf(stderr,
268
"Failed to create bootenv %s\n", bootenv);
269
else
270
fprintf(stderr,
271
"Failed to create bootenv %s from snapshot %s\n",
272
bootenv, snapname);
273
}
274
275
return (err);
276
}
277
278
279
static int
280
bectl_cmd_export(int argc, char *argv[])
281
{
282
char *bootenv;
283
284
if (argc == 1) {
285
fprintf(stderr, "bectl export: missing boot environment name\n");
286
return (usage(false));
287
}
288
289
if (argc > 2) {
290
fprintf(stderr, "bectl export: extra arguments provided\n");
291
return (usage(false));
292
}
293
294
bootenv = argv[1];
295
296
if (isatty(STDOUT_FILENO)) {
297
fprintf(stderr, "bectl export: must redirect output\n");
298
return (EX_USAGE);
299
}
300
301
be_export(be, bootenv, STDOUT_FILENO);
302
303
return (0);
304
}
305
306
307
static int
308
bectl_cmd_import(int argc, char *argv[])
309
{
310
char *bootenv;
311
int err;
312
313
if (argc == 1) {
314
fprintf(stderr, "bectl import: missing boot environment name\n");
315
return (usage(false));
316
}
317
318
if (argc > 2) {
319
fprintf(stderr, "bectl import: extra arguments provided\n");
320
return (usage(false));
321
}
322
323
bootenv = argv[1];
324
325
if (isatty(STDIN_FILENO)) {
326
fprintf(stderr, "bectl import: input can not be from terminal\n");
327
return (EX_USAGE);
328
}
329
330
err = be_import(be, bootenv, STDIN_FILENO);
331
332
return (err);
333
}
334
335
#if SOON
336
static int
337
bectl_cmd_add(int argc, char *argv[])
338
{
339
340
if (argc < 2) {
341
fprintf(stderr, "bectl add: must provide at least one path\n");
342
return (usage(false));
343
}
344
345
for (int i = 1; i < argc; ++i) {
346
printf("arg %d: %s\n", i, argv[i]);
347
/* XXX TODO catch err */
348
be_add_child(be, argv[i], true);
349
}
350
351
return (0);
352
}
353
#endif
354
355
static int
356
bectl_cmd_destroy(int argc, char *argv[])
357
{
358
nvlist_t *props;
359
char *target, targetds[BE_MAXPATHLEN];
360
const char *origin;
361
int err, flags, opt;
362
363
flags = 0;
364
while ((opt = getopt(argc, argv, "Fo")) != -1) {
365
switch (opt) {
366
case 'F':
367
flags |= BE_DESTROY_FORCE;
368
break;
369
case 'o':
370
flags |= BE_DESTROY_ORIGIN;
371
break;
372
default:
373
fprintf(stderr, "bectl destroy: unknown option '-%c'\n",
374
optopt);
375
return (usage(false));
376
}
377
}
378
379
argc -= optind;
380
argv += optind;
381
382
if (argc != 1) {
383
fprintf(stderr, "bectl destroy: wrong number of arguments\n");
384
return (usage(false));
385
}
386
387
target = argv[0];
388
389
/* We'll emit a notice if there's an origin to be cleaned up */
390
if ((flags & BE_DESTROY_ORIGIN) == 0 && strchr(target, '@') == NULL) {
391
flags |= BE_DESTROY_AUTOORIGIN;
392
if (be_root_concat(be, target, targetds) != 0)
393
goto destroy;
394
if (be_prop_list_alloc(&props) != 0)
395
goto destroy;
396
if (be_get_dataset_props(be, targetds, props) != 0) {
397
be_prop_list_free(props);
398
goto destroy;
399
}
400
if (nvlist_lookup_string(props, "origin", &origin) == 0 &&
401
!be_is_auto_snapshot_name(be, origin))
402
fprintf(stderr, "bectl destroy: leaving origin '%s' intact\n",
403
origin);
404
be_prop_list_free(props);
405
}
406
407
destroy:
408
err = be_destroy(be, target, flags);
409
410
return (err);
411
}
412
413
static int
414
bectl_cmd_mount(int argc, char *argv[])
415
{
416
char result_loc[BE_MAXPATHLEN];
417
char *bootenv, *mountpoint;
418
int err, mntflags;
419
420
/* XXX TODO: Allow shallow */
421
mntflags = BE_MNT_DEEP;
422
if (argc < 2) {
423
fprintf(stderr, "bectl mount: missing argument(s)\n");
424
return (usage(false));
425
}
426
427
if (argc > 3) {
428
fprintf(stderr, "bectl mount: too many arguments\n");
429
return (usage(false));
430
}
431
432
bootenv = argv[1];
433
mountpoint = ((argc == 3) ? argv[2] : NULL);
434
435
err = be_mount(be, bootenv, mountpoint, mntflags, result_loc);
436
437
switch (err) {
438
case BE_ERR_SUCCESS:
439
printf("%s\n", result_loc);
440
break;
441
default:
442
fprintf(stderr,
443
(argc == 3) ? "Failed to mount bootenv %s at %s\n" :
444
"Failed to mount bootenv %s at temporary path %s\n",
445
bootenv, mountpoint);
446
}
447
448
return (err);
449
}
450
451
452
static int
453
bectl_cmd_rename(int argc, char *argv[])
454
{
455
char *dest, *src;
456
int err;
457
458
if (argc < 3) {
459
fprintf(stderr, "bectl rename: missing argument\n");
460
return (usage(false));
461
}
462
463
if (argc > 3) {
464
fprintf(stderr, "bectl rename: too many arguments\n");
465
return (usage(false));
466
}
467
468
src = argv[1];
469
dest = argv[2];
470
471
err = be_rename(be, src, dest);
472
switch (err) {
473
case BE_ERR_SUCCESS:
474
break;
475
default:
476
fprintf(stderr, "Failed to rename bootenv %s to %s\n",
477
src, dest);
478
}
479
480
return (err);
481
}
482
483
static int
484
bectl_cmd_unmount(int argc, char *argv[])
485
{
486
char *bootenv, *cmd;
487
int err, flags, opt;
488
489
/* Store alias used */
490
cmd = argv[0];
491
492
flags = 0;
493
while ((opt = getopt(argc, argv, "f")) != -1) {
494
switch (opt) {
495
case 'f':
496
flags |= BE_MNT_FORCE;
497
break;
498
default:
499
fprintf(stderr, "bectl %s: unknown option '-%c'\n",
500
cmd, optopt);
501
return (usage(false));
502
}
503
}
504
505
argc -= optind;
506
argv += optind;
507
508
if (argc != 1) {
509
fprintf(stderr, "bectl %s: wrong number of arguments\n", cmd);
510
return (usage(false));
511
}
512
513
bootenv = argv[0];
514
515
err = be_unmount(be, bootenv, flags);
516
517
switch (err) {
518
case BE_ERR_SUCCESS:
519
break;
520
default:
521
fprintf(stderr, "Failed to unmount bootenv %s\n", bootenv);
522
}
523
524
return (err);
525
}
526
527
static int
528
bectl_cmd_check(int argc, char *argv[] __unused)
529
{
530
531
/* The command is left as argv[0] */
532
if (argc != 1) {
533
fprintf(stderr, "bectl check: wrong number of arguments\n");
534
return (usage(false));
535
}
536
537
return (0);
538
}
539
540
static char *
541
save_cmdline(int argc, char *argv[])
542
{
543
char *cmdline, *basename, *p;
544
int len, n, i;
545
546
len = MAXPATHLEN * 2 + 1; /* HIS_MAX_RECORD_LEN from zfs.h */
547
cmdline = p = malloc(len);
548
if (cmdline == NULL)
549
err(2, "malloc");
550
551
basename = strrchr(argv[0], '/');
552
if (basename == NULL)
553
basename = argv[0];
554
else
555
basename++;
556
557
n = strlcpy(p, basename, len);
558
for (i = 1; i < argc; i++) {
559
if (n >= len)
560
break;
561
p += n;
562
*p++ = ' ';
563
len -= (n + 1);
564
n = strlcpy(p, argv[i], len);
565
}
566
567
return (cmdline);
568
}
569
570
int
571
main(int argc, char *argv[])
572
{
573
struct command_map_entry *cmd;
574
const char *command;
575
char *root = NULL, *cmdline = NULL;
576
int opt, rc;
577
578
while ((opt = getopt(argc, argv, "hr:")) != -1) {
579
switch (opt) {
580
case 'h':
581
exit(usage(true));
582
case 'r':
583
root = strdup(optarg);
584
break;
585
default:
586
exit(usage(false));
587
}
588
}
589
590
argc -= optind;
591
argv += optind;
592
593
if (argc == 0)
594
exit(usage(false));
595
596
command = *argv;
597
optreset = 1;
598
optind = 1;
599
600
if ((cmd = get_cmd_info(command)) == NULL) {
601
fprintf(stderr, "Unknown command: %s\n", command);
602
return (usage(false));
603
}
604
605
if ((be = libbe_init(root)) == NULL) {
606
if (!cmd->silent)
607
fprintf(stderr, "libbe_init(\"%s\") failed.\n",
608
root != NULL ? root : "");
609
return (-1);
610
}
611
612
if (cmd->save_history)
613
cmdline = save_cmdline(argc+optind, argv-optind);
614
615
libbe_print_on_error(be, !cmd->silent);
616
617
rc = cmd->fn(argc, argv);
618
619
if (cmd->save_history) {
620
if (rc == 0)
621
be_log_history(be, cmdline);
622
free(cmdline);
623
}
624
625
libbe_close(be);
626
return (rc);
627
}
628
629