2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2018 Kyle Evans <kevans@FreeBSD.org>
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
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.
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
28 #include <sys/cdefs.h>
29 __FBSDID("$FreeBSD$");
31 #include <sys/param.h>
48 bool show_all_datasets;
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);
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"
68 #define INDENT_INCREMENT 2
71 * Given a set of dataset properties (for a BE dataset), populate originprops
72 * with the origin's properties.
75 get_origin_props(nvlist_t *dsprops, nvlist_t **originprops)
79 if (nvlist_lookup_string(dsprops, "origin", &propstr) == 0) {
80 if (be_prop_list_alloc(originprops) != 0) {
82 "bectl list: failed to allocate origin prop nvlist\n");
85 if (be_get_dataset_props(be, propstr, *originprops) != 0) {
86 /* XXX TODO: Real errors */
88 "bectl list: failed to fetch origin properties\n");
98 print_padding(const char *fval, int colsz, struct printc *pc)
101 /* -H flag handling; all delimiters/padding are a single tab */
102 if (pc->script_fmt) {
108 colsz -= strlen(fval);
109 printf("%*s ", colsz, "");
112 static unsigned long long
113 dataset_space(const char *oname)
115 unsigned long long space;
116 char *dsname, *propstr, *sep;
120 dsname = strdup(oname);
124 /* Truncate snapshot to dataset name, as needed */
125 if ((sep = strchr(dsname, '@')) != NULL)
128 if (be_prop_list_alloc(&dsprops) != 0) {
133 if (be_get_dataset_props(be, dsname, dsprops) != 0) {
134 nvlist_free(dsprops);
139 if (nvlist_lookup_string(dsprops, "used", &propstr) == 0)
140 space = strtoull(propstr, NULL, 10);
142 nvlist_free(dsprops);
148 print_snapshots(const char *dsname, struct printc *pc)
151 nvlist_t *props, *sprops;
153 if (be_prop_list_alloc(&props) != 0) {
154 fprintf(stderr, "bectl list: failed to allocate snapshot nvlist\n");
157 if (be_get_dataset_snapshots(be, dsname, props) != 0) {
158 fprintf(stderr, "bectl list: failed to fetch boot ds snapshots\n");
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);
170 print_info(const char *name, nvlist_t *dsprops, struct printc *pc)
174 unsigned long long ctimenum, space;
175 nvlist_t *originprops;
177 char *dsname, *propstr;
179 boolean_t active_now, active_reboot;
183 printf("%*s%s", pc->current_indent, "", name);
184 nvlist_lookup_string(dsprops, "dataset", &dsname);
186 /* Recurse at the base level if we're breaking info down */
187 if (pc->current_indent == 0 && (pc->show_all_datasets ||
191 /* XXX TODO: Error? */
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.
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);
207 /* Back up a level; snapshots at the same level as dataset */
208 pc->current_indent -= INDENT_INCREMENT;
210 print_snapshots(dsname, pc);
211 pc->current_indent = 0;
214 print_padding(name, pc->be_colsz - pc->current_indent, pc);
216 active_colsz = pc->active_colsz_def;
217 if (nvlist_lookup_boolean_value(dsprops, "active",
218 &active_now) == 0 && active_now) {
222 if (nvlist_lookup_boolean_value(dsprops, "nextboot",
223 &active_reboot) == 0 && active_reboot) {
227 if (active_colsz == pc->active_colsz_def) {
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);
237 print_padding("-", pc->mount_colsz, pc);
240 oname = get_origin_props(dsprops, &originprops);
241 if (nvlist_lookup_string(dsprops, "used", &propstr) == 0) {
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)
248 * The -D flag is ignored if -a or -s are specified.
250 space = strtoull(propstr, NULL, 10);
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);
257 if (pc->show_space && oname != NULL)
258 space += dataset_space(oname);
260 /* Alas, there's more to it,. */
261 be_nicenum(space, buf, 6);
263 print_padding(buf, pc->space_colsz, pc);
266 print_padding("-", pc->space_colsz, pc);
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));
277 if (originprops != NULL)
278 be_prop_list_free(originprops);
283 print_headers(nvlist_t *props, struct printc *pc)
285 const char *chosen_be_header;
291 if (pc->show_all_datasets || pc->show_snaps)
292 chosen_be_header = HEADER_BEPLUS;
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)
301 nvpair_value_nvlist(cur, &dsprops);
302 if (nvlist_lookup_string(dsprops, "dataset", &propstr) != 0)
304 be_maxcol = MAX(be_maxcol, strlen(propstr) + INDENT_INCREMENT);
305 if (nvlist_lookup_string(dsprops, "origin", &propstr) != 0)
307 be_maxcol = MAX(be_maxcol,
308 strlen(propstr) + INDENT_INCREMENT * 2);
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);
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.
323 if (strcmp(chosen_be_header, HEADER_BE) != 0)
328 bectl_cmd_list(int argc, char *argv[])
332 nvlist_t *dsprops, *props;
334 boolean_t active_now, active_reboot;
338 bzero(&pc, sizeof(pc));
339 while ((opt = getopt(argc, argv, "aDHs")) != -1) {
342 pc.show_all_datasets = true;
345 pc.show_space = true;
348 pc.script_fmt = true;
351 pc.show_snaps = true;
354 fprintf(stderr, "bectl list: unknown option '-%c'\n",
356 return (usage(false));
363 fprintf(stderr, "bectl list: extra argument provided\n");
364 return (usage(false));
367 if (be_prop_list_alloc(&props) != 0) {
368 fprintf(stderr, "bectl list: failed to allocate prop nvlist\n");
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");
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;
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;
388 nvlist_lookup_boolean_value(dsprops, "active", &active_now);
389 nvlist_lookup_boolean_value(dsprops, "nextboot",
391 if (!active_now && !active_reboot)
393 if (printed > 0 && (pc.show_all_datasets || pc.show_snaps))
395 print_info(nvpair_name(cur), dsprops, &pc);
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;
405 nvlist_lookup_boolean_value(dsprops, "active", &active_now);
406 nvlist_lookup_boolean_value(dsprops, "nextboot",
408 if (active_now || active_reboot)
410 if (printed > 0 && (pc.show_all_datasets || pc.show_snaps))
412 print_info(nvpair_name(cur), dsprops, &pc);
415 be_prop_list_free(props);