Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/sbin/bectl/bectl_list.c
39481 views
1
/*
2
* Copyright (c) 2018 Kyle Evans <[email protected]>
3
*
4
* SPDX-License-Identifier: BSD-2-Clause
5
*/
6
7
#include <sys/param.h>
8
#include <stdbool.h>
9
#include <stdio.h>
10
#include <string.h>
11
#include <unistd.h>
12
13
#include <be.h>
14
15
#include "bectl.h"
16
17
struct sort_column {
18
const char *name;
19
const char *val;
20
nvlist_t *nvl;
21
};
22
23
struct printc {
24
int active_colsz_def;
25
int be_colsz;
26
int current_indent;
27
int mount_colsz;
28
int space_colsz;
29
bool script_fmt;
30
bool show_all_datasets;
31
bool show_snaps;
32
bool show_space;
33
};
34
35
static const char *get_origin_props(nvlist_t *dsprops, nvlist_t **originprops);
36
static void print_padding(const char *fval, int colsz, struct printc *pc);
37
static int print_snapshots(const char *dsname, struct printc *pc);
38
static void print_info(const char *name, nvlist_t *dsprops, struct printc *pc);
39
static void print_headers(nvlist_t *props, struct printc *pc);
40
static unsigned long long dataset_space(const char *oname);
41
42
#define HEADER_BE "BE"
43
#define HEADER_BEPLUS "BE/Dataset/Snapshot"
44
#define HEADER_ACTIVE "Active"
45
#define HEADER_MOUNT "Mountpoint"
46
#define HEADER_SPACE "Space"
47
#define HEADER_CREATED "Created"
48
49
/* Spaces */
50
#define INDENT_INCREMENT 2
51
52
/*
53
* Given a set of dataset properties (for a BE dataset), populate originprops
54
* with the origin's properties.
55
*/
56
static const char *
57
get_origin_props(nvlist_t *dsprops, nvlist_t **originprops)
58
{
59
const char *propstr;
60
61
if (nvlist_lookup_string(dsprops, "origin", &propstr) == 0) {
62
if (be_prop_list_alloc(originprops) != 0) {
63
fprintf(stderr,
64
"bectl list: failed to allocate origin prop nvlist\n");
65
return (NULL);
66
}
67
if (be_get_dataset_props(be, propstr, *originprops) != 0) {
68
/* XXX TODO: Real errors */
69
fprintf(stderr,
70
"bectl list: failed to fetch origin properties\n");
71
return (NULL);
72
}
73
74
return (propstr);
75
}
76
return (NULL);
77
}
78
79
static void
80
print_padding(const char *fval, int colsz, struct printc *pc)
81
{
82
83
/* -H flag handling; all delimiters/padding are a single tab */
84
if (pc->script_fmt) {
85
printf("\t");
86
return;
87
}
88
89
if (fval != NULL)
90
colsz -= strlen(fval);
91
printf("%*s ", colsz, "");
92
}
93
94
static unsigned long long
95
dataset_space(const char *oname)
96
{
97
unsigned long long space;
98
char *dsname, *sep;
99
const char *propstr;
100
nvlist_t *dsprops;
101
102
space = 0;
103
dsname = strdup(oname);
104
if (dsname == NULL)
105
return (0);
106
107
/* Truncate snapshot to dataset name, as needed */
108
if ((sep = strchr(dsname, '@')) != NULL)
109
*sep = '\0';
110
111
if (be_prop_list_alloc(&dsprops) != 0) {
112
free(dsname);
113
return (0);
114
}
115
116
if (be_get_dataset_props(be, dsname, dsprops) != 0) {
117
nvlist_free(dsprops);
118
free(dsname);
119
return (0);
120
}
121
122
if (nvlist_lookup_string(dsprops, "used", &propstr) == 0)
123
space = strtoull(propstr, NULL, 10);
124
125
nvlist_free(dsprops);
126
free(dsname);
127
return (space);
128
}
129
130
static int
131
print_snapshots(const char *dsname, struct printc *pc)
132
{
133
nvpair_t *cur;
134
nvlist_t *props, *sprops;
135
136
if (be_prop_list_alloc(&props) != 0) {
137
fprintf(stderr, "bectl list: failed to allocate snapshot nvlist\n");
138
return (1);
139
}
140
if (be_get_dataset_snapshots(be, dsname, props) != 0) {
141
fprintf(stderr, "bectl list: failed to fetch boot ds snapshots\n");
142
return (1);
143
}
144
for (cur = nvlist_next_nvpair(props, NULL); cur != NULL;
145
cur = nvlist_next_nvpair(props, cur)) {
146
nvpair_value_nvlist(cur, &sprops);
147
print_info(nvpair_name(cur), sprops, pc);
148
}
149
return (0);
150
}
151
152
static void
153
print_info(const char *name, nvlist_t *dsprops, struct printc *pc)
154
{
155
#define BUFSZ 64
156
char buf[BUFSZ];
157
unsigned long long ctimenum, space;
158
nvlist_t *originprops;
159
const char *oname, *dsname, *propstr;
160
int active_colsz;
161
boolean_t active_now, active_reboot, bootonce;
162
163
dsname = NULL;
164
originprops = NULL;
165
printf("%*s%s", pc->current_indent, "", name);
166
nvlist_lookup_string(dsprops, "dataset", &dsname);
167
168
/* Recurse at the base level if we're breaking info down */
169
if (pc->current_indent == 0 && (pc->show_all_datasets ||
170
pc->show_snaps)) {
171
printf("\n");
172
if (dsname == NULL)
173
/* XXX TODO: Error? */
174
return;
175
/*
176
* Whether we're dealing with -a or -s, we'll always print the
177
* dataset name/information followed by its origin. For -s, we
178
* additionally iterate through all snapshots of this boot
179
* environment and also print their information.
180
*/
181
pc->current_indent += INDENT_INCREMENT;
182
print_info(dsname, dsprops, pc);
183
pc->current_indent += INDENT_INCREMENT;
184
if ((oname = get_origin_props(dsprops, &originprops)) != NULL) {
185
print_info(oname, originprops, pc);
186
nvlist_free(originprops);
187
}
188
189
/* Back up a level; snapshots at the same level as dataset */
190
pc->current_indent -= INDENT_INCREMENT;
191
if (pc->show_snaps)
192
print_snapshots(dsname, pc);
193
pc->current_indent = 0;
194
return;
195
} else
196
print_padding(name, pc->be_colsz - pc->current_indent, pc);
197
198
active_colsz = pc->active_colsz_def;
199
if (nvlist_lookup_boolean_value(dsprops, "active",
200
&active_now) == 0 && active_now) {
201
printf("N");
202
active_colsz--;
203
}
204
if (nvlist_lookup_boolean_value(dsprops, "nextboot",
205
&active_reboot) == 0 && active_reboot) {
206
printf("R");
207
active_colsz--;
208
}
209
if (nvlist_lookup_boolean_value(dsprops, "bootonce",
210
&bootonce) == 0 && bootonce) {
211
printf("T");
212
active_colsz--;
213
}
214
if (active_colsz == pc->active_colsz_def) {
215
printf("-");
216
active_colsz--;
217
}
218
print_padding(NULL, active_colsz, pc);
219
if (nvlist_lookup_string(dsprops, "mounted", &propstr) == 0) {
220
printf("%s", propstr);
221
print_padding(propstr, pc->mount_colsz, pc);
222
} else {
223
printf("%s", "-");
224
print_padding("-", pc->mount_colsz, pc);
225
}
226
227
oname = get_origin_props(dsprops, &originprops);
228
if (nvlist_lookup_string(dsprops, "used", &propstr) == 0) {
229
/*
230
* The space used column is some composition of:
231
* - The "used" property of the dataset
232
* - The "used" property of the origin snapshot (not -a or -s)
233
* - The "used" property of the origin dataset (-D flag only)
234
*
235
* The -D flag is ignored if -a or -s are specified.
236
*/
237
space = strtoull(propstr, NULL, 10);
238
239
if (!pc->show_all_datasets && !pc->show_snaps &&
240
originprops != NULL &&
241
nvlist_lookup_string(originprops, "used", &propstr) == 0)
242
space += strtoull(propstr, NULL, 10);
243
244
if (pc->show_space && oname != NULL)
245
space += dataset_space(oname);
246
247
/* Alas, there's more to it,. */
248
be_nicenum(space, buf, 6);
249
printf("%s", buf);
250
print_padding(buf, pc->space_colsz, pc);
251
} else {
252
printf("-");
253
print_padding("-", pc->space_colsz, pc);
254
}
255
256
if (nvlist_lookup_string(dsprops, "creation", &propstr) == 0) {
257
ctimenum = strtoull(propstr, NULL, 10);
258
strftime(buf, BUFSZ, "%Y-%m-%d %H:%M",
259
localtime((time_t *)&ctimenum));
260
printf("%s", buf);
261
}
262
263
printf("\n");
264
if (originprops != NULL)
265
be_prop_list_free(originprops);
266
#undef BUFSZ
267
}
268
269
static void
270
print_headers(nvlist_t *props, struct printc *pc)
271
{
272
const char *chosen_be_header, *propstr;
273
nvpair_t *cur;
274
nvlist_t *dsprops;
275
size_t be_maxcol, mount_colsz;
276
277
if (pc->show_all_datasets || pc->show_snaps)
278
chosen_be_header = HEADER_BEPLUS;
279
else
280
chosen_be_header = HEADER_BE;
281
be_maxcol = strlen(chosen_be_header);
282
mount_colsz = strlen(HEADER_MOUNT);
283
for (cur = nvlist_next_nvpair(props, NULL); cur != NULL;
284
cur = nvlist_next_nvpair(props, cur)) {
285
be_maxcol = MAX(be_maxcol, strlen(nvpair_name(cur)));
286
nvpair_value_nvlist(cur, &dsprops);
287
288
if (nvlist_lookup_string(dsprops, "mounted", &propstr) == 0)
289
mount_colsz = MAX(mount_colsz, strlen(propstr));
290
if (!pc->show_all_datasets && !pc->show_snaps)
291
continue;
292
if (nvlist_lookup_string(dsprops, "dataset", &propstr) != 0)
293
continue;
294
be_maxcol = MAX(be_maxcol, strlen(propstr) + INDENT_INCREMENT);
295
if (nvlist_lookup_string(dsprops, "origin", &propstr) != 0)
296
continue;
297
be_maxcol = MAX(be_maxcol,
298
strlen(propstr) + INDENT_INCREMENT * 2);
299
}
300
301
pc->be_colsz = be_maxcol;
302
pc->active_colsz_def = strlen(HEADER_ACTIVE);
303
pc->mount_colsz = mount_colsz;
304
pc->space_colsz = strlen(HEADER_SPACE);
305
printf("%*s %s %*s %s %s\n", -pc->be_colsz, chosen_be_header,
306
HEADER_ACTIVE, -pc->mount_colsz, HEADER_MOUNT, HEADER_SPACE, HEADER_CREATED);
307
308
/*
309
* All other invocations in which we aren't using the default header
310
* will produce quite a bit of input. Throw an extra blank line after
311
* the header to make it look nicer.
312
*/
313
if (strcmp(chosen_be_header, HEADER_BE) != 0)
314
printf("\n");
315
}
316
317
/*
318
* Sort the given nvlist of boot environments by property.
319
*/
320
static int
321
prop_list_sort(nvlist_t *props, char *property, bool reverse)
322
{
323
nvpair_t *nvp;
324
nvlist_t *nvl;
325
int i, nvp_count;
326
uint64_t lval, rval;
327
struct sort_column sc_prev, sc_next;
328
329
/* a temporary list to work with */
330
nvlist_dup(props, &nvl, 0);
331
332
nvp_count = fnvlist_num_pairs(nvl);
333
for (i = 0; i < nvp_count; i++) {
334
335
nvp = nvlist_next_nvpair(nvl, NULL);
336
nvpair_value_nvlist(nvp, &sc_prev.nvl);
337
nvlist_lookup_string(sc_prev.nvl, "name", &sc_prev.name);
338
nvlist_lookup_string(sc_prev.nvl, property, &sc_prev.val);
339
340
while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
341
342
nvpair_value_nvlist(nvp, &sc_next.nvl);
343
nvlist_lookup_string(sc_next.nvl, "name", &sc_next.name);
344
nvlist_lookup_string(sc_next.nvl, property, &sc_next.val);
345
346
/* properties that use numerical comparison */
347
if (strcmp(property, "creation") == 0 ||
348
strcmp(property, "used") == 0 ||
349
strcmp(property, "usedds") == 0 ||
350
strcmp(property, "usedsnap") == 0 ||
351
strcmp(property, "usedrefreserv") == 0) {
352
353
lval = strtoull(sc_prev.val, NULL, 10);
354
rval = strtoull(sc_next.val, NULL, 10);
355
356
if ((lval < rval && reverse) ||
357
(lval > rval && !reverse))
358
sc_prev = sc_next;
359
}
360
361
/* properties that use string comparison */
362
else if (strcmp(property, "name") == 0 ||
363
strcmp(property, "origin") == 0) {
364
if ((strcmp(sc_prev.val, sc_next.val) < 0 && reverse) ||
365
(strcmp(sc_prev.val, sc_next.val) > 0 && !reverse))
366
sc_prev = sc_next;
367
}
368
}
369
370
/*
371
* The 'props' nvlist has been created to only have unique names.
372
* When a name is added, any existing nvlist's with the same name
373
* will be removed. Eventually, all existing nvlist's are replaced
374
* in sorted order.
375
*/
376
nvlist_add_nvlist(props, sc_prev.name, sc_prev.nvl);
377
nvlist_remove_all(nvl, sc_prev.name);
378
}
379
380
be_prop_list_free(nvl);
381
382
return 0;
383
}
384
385
int
386
bectl_cmd_list(int argc, char *argv[])
387
{
388
struct printc pc;
389
nvpair_t *cur;
390
nvlist_t *dsprops, *props;
391
int opt, printed;
392
char *column;
393
bool reverse;
394
395
column = NULL;
396
props = NULL;
397
printed = 0;
398
bzero(&pc, sizeof(pc));
399
reverse = false;
400
while ((opt = getopt(argc, argv, "aDHsc:C:")) != -1) {
401
switch (opt) {
402
case 'a':
403
pc.show_all_datasets = true;
404
break;
405
case 'D':
406
pc.show_space = true;
407
break;
408
case 'H':
409
pc.script_fmt = true;
410
break;
411
case 's':
412
pc.show_snaps = true;
413
break;
414
case 'c':
415
if (column != NULL)
416
free(column);
417
column = strdup(optarg);
418
reverse = false;
419
break;
420
case 'C':
421
if (column != NULL)
422
free(column);
423
column = strdup(optarg);
424
reverse = true;
425
break;
426
default:
427
fprintf(stderr, "bectl list: unknown option '-%c'\n",
428
optopt);
429
return (usage(false));
430
}
431
}
432
433
argc -= optind;
434
435
if (argc != 0) {
436
fprintf(stderr, "bectl list: extra argument provided\n");
437
return (usage(false));
438
}
439
440
if (be_prop_list_alloc(&props) != 0) {
441
fprintf(stderr, "bectl list: failed to allocate prop nvlist\n");
442
return (1);
443
}
444
if (be_get_bootenv_props(be, props) != 0) {
445
/* XXX TODO: Real errors */
446
fprintf(stderr, "bectl list: failed to fetch boot environments\n");
447
return (1);
448
}
449
450
/* List boot environments in alphabetical order by default */
451
if (column == NULL)
452
column = strdup("name");
453
454
prop_list_sort(props, column, reverse);
455
456
/* Force -D off if either -a or -s are specified */
457
if (pc.show_all_datasets || pc.show_snaps)
458
pc.show_space = false;
459
if (!pc.script_fmt)
460
print_headers(props, &pc);
461
462
/* Print boot environments */
463
for (cur = nvlist_next_nvpair(props, NULL); cur != NULL;
464
cur = nvlist_next_nvpair(props, cur)) {
465
nvpair_value_nvlist(cur, &dsprops);
466
467
if (printed > 0 && (pc.show_all_datasets || pc.show_snaps))
468
printf("\n");
469
470
print_info(nvpair_name(cur), dsprops, &pc);
471
printed++;
472
}
473
474
free(column);
475
be_prop_list_free(props);
476
477
return (0);
478
}
479
480
481