1 /*
2 * Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org>
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 *
get_origin_props(nvlist_t * dsprops,nvlist_t ** originprops)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
print_padding(const char * fval,int colsz,struct printc * pc)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
dataset_space(const char * oname)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
print_snapshots(const char * dsname,struct printc * pc)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
print_info(const char * name,nvlist_t * dsprops,struct printc * pc)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
print_headers(nvlist_t * props,struct printc * pc)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
prop_list_sort(nvlist_t * props,char * property,bool reverse)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
bectl_cmd_list(int argc,char * argv[])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