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