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
30 #include <sys/param.h>
47 bool show_all_datasets;
52 static const char *get_origin_props(nvlist_t *dsprops, nvlist_t **originprops);
53 static void print_padding(const char *fval, int colsz, struct printc *pc);
54 static int print_snapshots(const char *dsname, struct printc *pc);
55 static void print_info(const char *name, nvlist_t *dsprops, struct printc *pc);
56 static void print_headers(nvlist_t *props, struct printc *pc);
57 static unsigned long long dataset_space(const char *oname);
59 #define HEADER_BE "BE"
60 #define HEADER_BEPLUS "BE/Dataset/Snapshot"
61 #define HEADER_ACTIVE "Active"
62 #define HEADER_MOUNT "Mountpoint"
63 #define HEADER_SPACE "Space"
64 #define HEADER_CREATED "Created"
67 #define INDENT_INCREMENT 2
70 * Given a set of dataset properties (for a BE dataset), populate originprops
71 * with the origin's properties.
74 get_origin_props(nvlist_t *dsprops, nvlist_t **originprops)
78 if (nvlist_lookup_string(dsprops, "origin", &propstr) == 0) {
79 if (be_prop_list_alloc(originprops) != 0) {
81 "bectl list: failed to allocate origin prop nvlist\n");
84 if (be_get_dataset_props(be, propstr, *originprops) != 0) {
85 /* XXX TODO: Real errors */
87 "bectl list: failed to fetch origin properties\n");
97 print_padding(const char *fval, int colsz, struct printc *pc)
100 /* -H flag handling; all delimiters/padding are a single tab */
101 if (pc->script_fmt) {
107 colsz -= strlen(fval);
108 printf("%*s ", colsz, "");
111 static unsigned long long
112 dataset_space(const char *oname)
114 unsigned long long space;
115 char *dsname, *propstr, *sep;
119 dsname = strdup(oname);
123 /* Truncate snapshot to dataset name, as needed */
124 if ((sep = strchr(dsname, '@')) != NULL)
127 if (be_prop_list_alloc(&dsprops) != 0) {
132 if (be_get_dataset_props(be, dsname, dsprops) != 0) {
133 nvlist_free(dsprops);
138 if (nvlist_lookup_string(dsprops, "used", &propstr) == 0)
139 space = strtoull(propstr, NULL, 10);
141 nvlist_free(dsprops);
147 print_snapshots(const char *dsname, struct printc *pc)
150 nvlist_t *props, *sprops;
152 if (be_prop_list_alloc(&props) != 0) {
153 fprintf(stderr, "bectl list: failed to allocate snapshot nvlist\n");
156 if (be_get_dataset_snapshots(be, dsname, props) != 0) {
157 fprintf(stderr, "bectl list: failed to fetch boot ds snapshots\n");
160 for (cur = nvlist_next_nvpair(props, NULL); cur != NULL;
161 cur = nvlist_next_nvpair(props, cur)) {
162 nvpair_value_nvlist(cur, &sprops);
163 print_info(nvpair_name(cur), sprops, pc);
169 print_info(const char *name, nvlist_t *dsprops, struct printc *pc)
173 unsigned long long ctimenum, space;
174 nvlist_t *originprops;
176 char *dsname, *propstr;
178 boolean_t active_now, active_reboot;
182 printf("%*s%s", pc->current_indent, "", name);
183 nvlist_lookup_string(dsprops, "dataset", &dsname);
185 /* Recurse at the base level if we're breaking info down */
186 if (pc->current_indent == 0 && (pc->show_all_datasets ||
190 /* XXX TODO: Error? */
193 * Whether we're dealing with -a or -s, we'll always print the
194 * dataset name/information followed by its origin. For -s, we
195 * additionally iterate through all snapshots of this boot
196 * environment and also print their information.
198 pc->current_indent += INDENT_INCREMENT;
199 print_info(dsname, dsprops, pc);
200 pc->current_indent += INDENT_INCREMENT;
201 if ((oname = get_origin_props(dsprops, &originprops)) != NULL) {
202 print_info(oname, originprops, pc);
203 nvlist_free(originprops);
206 /* Back up a level; snapshots at the same level as dataset */
207 pc->current_indent -= INDENT_INCREMENT;
209 print_snapshots(dsname, pc);
210 pc->current_indent = 0;
213 print_padding(name, pc->be_colsz - pc->current_indent, pc);
215 active_colsz = pc->active_colsz_def;
216 if (nvlist_lookup_boolean_value(dsprops, "active",
217 &active_now) == 0 && active_now) {
221 if (nvlist_lookup_boolean_value(dsprops, "nextboot",
222 &active_reboot) == 0 && active_reboot) {
226 if (active_colsz == pc->active_colsz_def) {
230 print_padding(NULL, active_colsz, pc);
231 if (nvlist_lookup_string(dsprops, "mountpoint", &propstr) == 0){
232 printf("%s", propstr);
233 print_padding(propstr, pc->mount_colsz, pc);
236 print_padding("-", pc->mount_colsz, pc);
239 oname = get_origin_props(dsprops, &originprops);
240 if (nvlist_lookup_string(dsprops, "used", &propstr) == 0) {
242 * The space used column is some composition of:
243 * - The "used" property of the dataset
244 * - The "used" property of the origin snapshot (not -a or -s)
245 * - The "used" property of the origin dataset (-D flag only)
247 * The -D flag is ignored if -a or -s are specified.
249 space = strtoull(propstr, NULL, 10);
251 if (!pc->show_all_datasets && !pc->show_snaps &&
252 originprops != NULL &&
253 nvlist_lookup_string(originprops, "used", &propstr) == 0)
254 space += strtoull(propstr, NULL, 10);
256 if (pc->show_space && oname != NULL)
257 space += dataset_space(oname);
259 /* Alas, there's more to it,. */
260 be_nicenum(space, buf, 6);
262 print_padding(buf, pc->space_colsz, pc);
265 print_padding("-", pc->space_colsz, pc);
268 if (nvlist_lookup_string(dsprops, "creation", &propstr) == 0) {
269 ctimenum = strtoull(propstr, NULL, 10);
270 strftime(buf, BUFSZ, "%Y-%m-%d %H:%M",
271 localtime((time_t *)&ctimenum));
276 if (originprops != NULL)
277 be_prop_list_free(originprops);
282 print_headers(nvlist_t *props, struct printc *pc)
284 const char *chosen_be_header;
290 if (pc->show_all_datasets || pc->show_snaps)
291 chosen_be_header = HEADER_BEPLUS;
293 chosen_be_header = HEADER_BE;
294 be_maxcol = strlen(chosen_be_header);
295 for (cur = nvlist_next_nvpair(props, NULL); cur != NULL;
296 cur = nvlist_next_nvpair(props, cur)) {
297 be_maxcol = MAX(be_maxcol, strlen(nvpair_name(cur)));
298 if (!pc->show_all_datasets && !pc->show_snaps)
300 nvpair_value_nvlist(cur, &dsprops);
301 if (nvlist_lookup_string(dsprops, "dataset", &propstr) != 0)
303 be_maxcol = MAX(be_maxcol, strlen(propstr) + INDENT_INCREMENT);
304 if (nvlist_lookup_string(dsprops, "origin", &propstr) != 0)
306 be_maxcol = MAX(be_maxcol,
307 strlen(propstr) + INDENT_INCREMENT * 2);
310 pc->be_colsz = be_maxcol;
311 pc->active_colsz_def = strlen(HEADER_ACTIVE);
312 pc->mount_colsz = strlen(HEADER_MOUNT);
313 pc->space_colsz = strlen(HEADER_SPACE);
314 printf("%*s %s %s %s %s\n", -pc->be_colsz, chosen_be_header,
315 HEADER_ACTIVE, HEADER_MOUNT, HEADER_SPACE, HEADER_CREATED);
318 * All other invocations in which we aren't using the default header
319 * will produce quite a bit of input. Throw an extra blank line after
320 * the header to make it look nicer.
322 if (chosen_be_header != HEADER_BE)
327 bectl_cmd_list(int argc, char *argv[])
331 nvlist_t *dsprops, *props;
333 boolean_t active_now, active_reboot;
337 bzero(&pc, sizeof(pc));
338 while ((opt = getopt(argc, argv, "aDHs")) != -1) {
341 pc.show_all_datasets = true;
344 pc.show_space = true;
347 pc.script_fmt = true;
350 pc.show_snaps = true;
353 fprintf(stderr, "bectl list: unknown option '-%c'\n",
355 return (usage(false));
362 fprintf(stderr, "bectl list: extra argument provided\n");
363 return (usage(false));
366 if (be_prop_list_alloc(&props) != 0) {
367 fprintf(stderr, "bectl list: failed to allocate prop nvlist\n");
370 if (be_get_bootenv_props(be, props) != 0) {
371 /* XXX TODO: Real errors */
372 fprintf(stderr, "bectl list: failed to fetch boot environments\n");
376 /* Force -D off if either -a or -s are specified */
377 if (pc.show_all_datasets || pc.show_snaps)
378 pc.show_space = false;
380 print_headers(props, &pc);
381 /* Do a first pass to print active and next active first */
382 for (cur = nvlist_next_nvpair(props, NULL); cur != NULL;
383 cur = nvlist_next_nvpair(props, cur)) {
384 nvpair_value_nvlist(cur, &dsprops);
385 active_now = active_reboot = false;
387 nvlist_lookup_boolean_value(dsprops, "active", &active_now);
388 nvlist_lookup_boolean_value(dsprops, "nextboot",
390 if (!active_now && !active_reboot)
392 if (printed > 0 && (pc.show_all_datasets || pc.show_snaps))
394 print_info(nvpair_name(cur), dsprops, &pc);
398 /* Now pull everything else */
399 for (cur = nvlist_next_nvpair(props, NULL); cur != NULL;
400 cur = nvlist_next_nvpair(props, cur)) {
401 nvpair_value_nvlist(cur, &dsprops);
402 active_now = active_reboot = false;
404 nvlist_lookup_boolean_value(dsprops, "active", &active_now);
405 nvlist_lookup_boolean_value(dsprops, "nextboot",
407 if (active_now || active_reboot)
409 if (printed > 0 && (pc.show_all_datasets || pc.show_snaps))
411 print_info(nvpair_name(cur), dsprops, &pc);
414 be_prop_list_free(props);