xref: /freebsd/sbin/bectl/bectl_list.c (revision 52c2bb75163559a6e2866ad374a7de67a4ea1273)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
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 __FBSDID("$FreeBSD$");
30 
31 #include <sys/param.h>
32 #include <stdbool.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include <unistd.h>
36 
37 #include <be.h>
38 
39 #include "bectl.h"
40 
41 struct printc {
42 	int	active_colsz_def;
43 	int	be_colsz;
44 	int	current_indent;
45 	int	mount_colsz;
46 	int	space_colsz;
47 	bool	script_fmt;
48 	bool	show_all_datasets;
49 	bool	show_snaps;
50 	bool	show_space;
51 };
52 
53 static const char *get_origin_props(nvlist_t *dsprops, nvlist_t **originprops);
54 static void print_padding(const char *fval, int colsz, struct printc *pc);
55 static int print_snapshots(const char *dsname, struct printc *pc);
56 static void print_info(const char *name, nvlist_t *dsprops, struct printc *pc);
57 static void print_headers(nvlist_t *props, struct printc *pc);
58 static unsigned long long dataset_space(const char *oname);
59 
60 #define	HEADER_BE	"BE"
61 #define	HEADER_BEPLUS	"BE/Dataset/Snapshot"
62 #define	HEADER_ACTIVE	"Active"
63 #define	HEADER_MOUNT	"Mountpoint"
64 #define	HEADER_SPACE	"Space"
65 #define	HEADER_CREATED	"Created"
66 
67 /* Spaces */
68 #define	INDENT_INCREMENT	2
69 
70 /*
71  * Given a set of dataset properties (for a BE dataset), populate originprops
72  * with the origin's properties.
73  */
74 static const char *
75 get_origin_props(nvlist_t *dsprops, nvlist_t **originprops)
76 {
77 	char *propstr;
78 
79 	if (nvlist_lookup_string(dsprops, "origin", &propstr) == 0) {
80 		if (be_prop_list_alloc(originprops) != 0) {
81 			fprintf(stderr,
82 			    "bectl list: failed to allocate origin prop nvlist\n");
83 			return (NULL);
84 		}
85 		if (be_get_dataset_props(be, propstr, *originprops) != 0) {
86 			/* XXX TODO: Real errors */
87 			fprintf(stderr,
88 			    "bectl list: failed to fetch origin properties\n");
89 			return (NULL);
90 		}
91 
92 		return (propstr);
93 	}
94 	return (NULL);
95 }
96 
97 static void
98 print_padding(const char *fval, int colsz, struct printc *pc)
99 {
100 
101 	/* -H flag handling; all delimiters/padding are a single tab */
102 	if (pc->script_fmt) {
103 		printf("\t");
104 		return;
105 	}
106 
107 	if (fval != NULL)
108 		colsz -= strlen(fval);
109 	printf("%*s ", colsz, "");
110 }
111 
112 static unsigned long long
113 dataset_space(const char *oname)
114 {
115 	unsigned long long space;
116 	char *dsname, *propstr, *sep;
117 	nvlist_t *dsprops;
118 
119 	space = 0;
120 	dsname = strdup(oname);
121 	if (dsname == NULL)
122 		return (0);
123 
124 	/* Truncate snapshot to dataset name, as needed */
125 	if ((sep = strchr(dsname, '@')) != NULL)
126 		*sep = '\0';
127 
128 	if (be_prop_list_alloc(&dsprops) != 0) {
129 		free(dsname);
130 		return (0);
131 	}
132 
133 	if (be_get_dataset_props(be, dsname, dsprops) != 0) {
134 		nvlist_free(dsprops);
135 		free(dsname);
136 		return (0);
137 	}
138 
139 	if (nvlist_lookup_string(dsprops, "used", &propstr) == 0)
140 		space = strtoull(propstr, NULL, 10);
141 
142 	nvlist_free(dsprops);
143 	free(dsname);
144 	return (space);
145 }
146 
147 static int
148 print_snapshots(const char *dsname, struct printc *pc)
149 {
150 	nvpair_t *cur;
151 	nvlist_t *props, *sprops;
152 
153 	if (be_prop_list_alloc(&props) != 0) {
154 		fprintf(stderr, "bectl list: failed to allocate snapshot nvlist\n");
155 		return (1);
156 	}
157 	if (be_get_dataset_snapshots(be, dsname, props) != 0) {
158 		fprintf(stderr, "bectl list: failed to fetch boot ds snapshots\n");
159 		return (1);
160 	}
161 	for (cur = nvlist_next_nvpair(props, NULL); cur != NULL;
162 	    cur = nvlist_next_nvpair(props, cur)) {
163 		nvpair_value_nvlist(cur, &sprops);
164 		print_info(nvpair_name(cur), sprops, pc);
165 	}
166 	return (0);
167 }
168 
169 static void
170 print_info(const char *name, nvlist_t *dsprops, struct printc *pc)
171 {
172 #define	BUFSZ	64
173 	char buf[BUFSZ];
174 	unsigned long long ctimenum, space;
175 	nvlist_t *originprops;
176 	const char *oname;
177 	char *dsname, *propstr;
178 	int active_colsz;
179 	boolean_t active_now, active_reboot;
180 
181 	dsname = NULL;
182 	originprops = NULL;
183 	printf("%*s%s", pc->current_indent, "", name);
184 	nvlist_lookup_string(dsprops, "dataset", &dsname);
185 
186 	/* Recurse at the base level if we're breaking info down */
187 	if (pc->current_indent == 0 && (pc->show_all_datasets ||
188 	    pc->show_snaps)) {
189 		printf("\n");
190 		if (dsname == NULL)
191 			/* XXX TODO: Error? */
192 			return;
193 		/*
194 		 * Whether we're dealing with -a or -s, we'll always print the
195 		 * dataset name/information followed by its origin. For -s, we
196 		 * additionally iterate through all snapshots of this boot
197 		 * environment and also print their information.
198 		 */
199 		pc->current_indent += INDENT_INCREMENT;
200 		print_info(dsname, dsprops, pc);
201 		pc->current_indent += INDENT_INCREMENT;
202 		if ((oname = get_origin_props(dsprops, &originprops)) != NULL) {
203 			print_info(oname, originprops, pc);
204 			nvlist_free(originprops);
205 		}
206 
207 		/* Back up a level; snapshots at the same level as dataset */
208 		pc->current_indent -= INDENT_INCREMENT;
209 		if (pc->show_snaps)
210 			print_snapshots(dsname, pc);
211 		pc->current_indent = 0;
212 		return;
213 	} else
214 		print_padding(name, pc->be_colsz - pc->current_indent, pc);
215 
216 	active_colsz = pc->active_colsz_def;
217 	if (nvlist_lookup_boolean_value(dsprops, "active",
218 	    &active_now) == 0 && active_now) {
219 		printf("N");
220 		active_colsz--;
221 	}
222 	if (nvlist_lookup_boolean_value(dsprops, "nextboot",
223 	    &active_reboot) == 0 && active_reboot) {
224 		printf("R");
225 		active_colsz--;
226 	}
227 	if (active_colsz == pc->active_colsz_def) {
228 		printf("-");
229 		active_colsz--;
230 	}
231 	print_padding(NULL, active_colsz, pc);
232 	if (nvlist_lookup_string(dsprops, "mounted", &propstr) == 0) {
233 		printf("%s", propstr);
234 		print_padding(propstr, pc->mount_colsz, pc);
235 	} else {
236 		printf("%s", "-");
237 		print_padding("-", pc->mount_colsz, pc);
238 	}
239 
240 	oname = get_origin_props(dsprops, &originprops);
241 	if (nvlist_lookup_string(dsprops, "used", &propstr) == 0) {
242 		/*
243 		 * The space used column is some composition of:
244 		 * - The "used" property of the dataset
245 		 * - The "used" property of the origin snapshot (not -a or -s)
246 		 * - The "used" property of the origin dataset (-D flag only)
247 		 *
248 		 * The -D flag is ignored if -a or -s are specified.
249 		 */
250 		space = strtoull(propstr, NULL, 10);
251 
252 		if (!pc->show_all_datasets && !pc->show_snaps &&
253 		    originprops != NULL &&
254 		    nvlist_lookup_string(originprops, "used", &propstr) == 0)
255 			space += strtoull(propstr, NULL, 10);
256 
257 		if (pc->show_space && oname != NULL)
258 			space += dataset_space(oname);
259 
260 		/* Alas, there's more to it,. */
261 		be_nicenum(space, buf, 6);
262 		printf("%s", buf);
263 		print_padding(buf, pc->space_colsz, pc);
264 	} else {
265 		printf("-");
266 		print_padding("-", pc->space_colsz, pc);
267 	}
268 
269 	if (nvlist_lookup_string(dsprops, "creation", &propstr) == 0) {
270 		ctimenum = strtoull(propstr, NULL, 10);
271 		strftime(buf, BUFSZ, "%Y-%m-%d %H:%M",
272 		    localtime((time_t *)&ctimenum));
273 		printf("%s", buf);
274 	}
275 
276 	printf("\n");
277 	if (originprops != NULL)
278 		be_prop_list_free(originprops);
279 #undef BUFSZ
280 }
281 
282 static void
283 print_headers(nvlist_t *props, struct printc *pc)
284 {
285 	const char *chosen_be_header;
286 	nvpair_t *cur;
287 	nvlist_t *dsprops;
288 	char *propstr;
289 	size_t be_maxcol;
290 
291 	if (pc->show_all_datasets || pc->show_snaps)
292 		chosen_be_header = HEADER_BEPLUS;
293 	else
294 		chosen_be_header = HEADER_BE;
295 	be_maxcol = strlen(chosen_be_header);
296 	for (cur = nvlist_next_nvpair(props, NULL); cur != NULL;
297 	    cur = nvlist_next_nvpair(props, cur)) {
298 		be_maxcol = MAX(be_maxcol, strlen(nvpair_name(cur)));
299 		if (!pc->show_all_datasets && !pc->show_snaps)
300 			continue;
301 		nvpair_value_nvlist(cur, &dsprops);
302 		if (nvlist_lookup_string(dsprops, "dataset", &propstr) != 0)
303 			continue;
304 		be_maxcol = MAX(be_maxcol, strlen(propstr) + INDENT_INCREMENT);
305 		if (nvlist_lookup_string(dsprops, "origin", &propstr) != 0)
306 			continue;
307 		be_maxcol = MAX(be_maxcol,
308 		    strlen(propstr) + INDENT_INCREMENT * 2);
309 	}
310 
311 	pc->be_colsz = be_maxcol;
312 	pc->active_colsz_def = strlen(HEADER_ACTIVE);
313 	pc->mount_colsz = strlen(HEADER_MOUNT);
314 	pc->space_colsz = strlen(HEADER_SPACE);
315 	printf("%*s %s %s %s %s\n", -pc->be_colsz, chosen_be_header,
316 	    HEADER_ACTIVE, HEADER_MOUNT, HEADER_SPACE, HEADER_CREATED);
317 
318 	/*
319 	 * All other invocations in which we aren't using the default header
320 	 * will produce quite a bit of input.  Throw an extra blank line after
321 	 * the header to make it look nicer.
322 	 */
323 	if (strcmp(chosen_be_header, HEADER_BE) != 0)
324 		printf("\n");
325 }
326 
327 int
328 bectl_cmd_list(int argc, char *argv[])
329 {
330 	struct printc pc;
331 	nvpair_t *cur;
332 	nvlist_t *dsprops, *props;
333 	int opt, printed;
334 	boolean_t active_now, active_reboot;
335 
336 	props = NULL;
337 	printed = 0;
338 	bzero(&pc, sizeof(pc));
339 	while ((opt = getopt(argc, argv, "aDHs")) != -1) {
340 		switch (opt) {
341 		case 'a':
342 			pc.show_all_datasets = true;
343 			break;
344 		case 'D':
345 			pc.show_space = true;
346 			break;
347 		case 'H':
348 			pc.script_fmt = true;
349 			break;
350 		case 's':
351 			pc.show_snaps = true;
352 			break;
353 		default:
354 			fprintf(stderr, "bectl list: unknown option '-%c'\n",
355 			    optopt);
356 			return (usage(false));
357 		}
358 	}
359 
360 	argc -= optind;
361 
362 	if (argc != 0) {
363 		fprintf(stderr, "bectl list: extra argument provided\n");
364 		return (usage(false));
365 	}
366 
367 	if (be_prop_list_alloc(&props) != 0) {
368 		fprintf(stderr, "bectl list: failed to allocate prop nvlist\n");
369 		return (1);
370 	}
371 	if (be_get_bootenv_props(be, props) != 0) {
372 		/* XXX TODO: Real errors */
373 		fprintf(stderr, "bectl list: failed to fetch boot environments\n");
374 		return (1);
375 	}
376 
377 	/* Force -D off if either -a or -s are specified */
378 	if (pc.show_all_datasets || pc.show_snaps)
379 		pc.show_space = false;
380 	if (!pc.script_fmt)
381 		print_headers(props, &pc);
382 	/* Do a first pass to print active and next active first */
383 	for (cur = nvlist_next_nvpair(props, NULL); cur != NULL;
384 	    cur = nvlist_next_nvpair(props, cur)) {
385 		nvpair_value_nvlist(cur, &dsprops);
386 		active_now = active_reboot = false;
387 
388 		nvlist_lookup_boolean_value(dsprops, "active", &active_now);
389 		nvlist_lookup_boolean_value(dsprops, "nextboot",
390 		    &active_reboot);
391 		if (!active_now && !active_reboot)
392 			continue;
393 		if (printed > 0 && (pc.show_all_datasets || pc.show_snaps))
394 			printf("\n");
395 		print_info(nvpair_name(cur), dsprops, &pc);
396 		printed++;
397 	}
398 
399 	/* Now pull everything else */
400 	for (cur = nvlist_next_nvpair(props, NULL); cur != NULL;
401 	    cur = nvlist_next_nvpair(props, cur)) {
402 		nvpair_value_nvlist(cur, &dsprops);
403 		active_now = active_reboot = false;
404 
405 		nvlist_lookup_boolean_value(dsprops, "active", &active_now);
406 		nvlist_lookup_boolean_value(dsprops, "nextboot",
407 		    &active_reboot);
408 		if (active_now || active_reboot)
409 			continue;
410 		if (printed > 0 && (pc.show_all_datasets || pc.show_snaps))
411 			printf("\n");
412 		print_info(nvpair_name(cur), dsprops, &pc);
413 		printed++;
414 	}
415 	be_prop_list_free(props);
416 
417 	return (0);
418 }
419 
420