]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sbin/bectl/bectl_list.c
bectl(8): Split list functionality out into its own file as well
[FreeBSD/FreeBSD.git] / sbin / bectl / bectl_list.c
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  * $FreeBSD$
28  */
29
30 #include <sys/param.h>
31 #include <stdbool.h>
32 #include <stdio.h>
33 #include <string.h>
34 #include <unistd.h>
35
36 #include <be.h>
37
38 #include "bectl.h"
39
40 struct printc {
41         int     active_colsz_def;
42         int     be_colsz;
43         int     current_indent;
44         int     mount_colsz;
45         int     space_colsz;
46         bool    script_fmt;
47         bool    show_all_datasets;
48         bool    show_snaps;
49         bool    show_space;
50 };
51
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);
58
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"
65
66 /* Spaces */
67 #define INDENT_INCREMENT        2
68
69 /*
70  * Given a set of dataset properties (for a BE dataset), populate originprops
71  * with the origin's properties.
72  */
73 static const char *
74 get_origin_props(nvlist_t *dsprops, nvlist_t **originprops)
75 {
76         char *propstr;
77
78         if (nvlist_lookup_string(dsprops, "origin", &propstr) == 0) {
79                 if (be_prop_list_alloc(originprops) != 0) {
80                         fprintf(stderr,
81                             "bectl list: failed to allocate origin prop nvlist\n");
82                         return (NULL);
83                 }
84                 if (be_get_dataset_props(be, propstr, *originprops) != 0) {
85                         /* XXX TODO: Real errors */
86                         fprintf(stderr,
87                             "bectl list: failed to fetch origin properties\n");
88                         return (NULL);
89                 }
90
91                 return (propstr);
92         }
93         return (NULL);
94 }
95
96 static void
97 print_padding(const char *fval, int colsz, struct printc *pc)
98 {
99
100         /* -H flag handling; all delimiters/padding are a single tab */
101         if (pc->script_fmt) {
102                 printf("\t");
103                 return;
104         }
105
106         if (fval != NULL)
107                 colsz -= strlen(fval);
108         printf("%*s ", colsz, "");
109 }
110
111 static unsigned long long
112 dataset_space(const char *oname)
113 {
114         unsigned long long space;
115         char *dsname, *propstr, *sep;
116         nvlist_t *dsprops;
117
118         space = 0;
119         dsname = strdup(oname);
120         if (dsname == NULL)
121                 return (0);
122
123         /* Truncate snapshot to dataset name, as needed */
124         if ((sep = strchr(dsname, '@')) != NULL)
125                 *sep = '\0';
126
127         if (be_prop_list_alloc(&dsprops) != 0) {
128                 free(dsname);
129                 return (0);
130         }
131
132         if (be_get_dataset_props(be, dsname, dsprops) != 0) {
133                 nvlist_free(dsprops);
134                 free(dsname);
135                 return (0);
136         }
137
138         if (nvlist_lookup_string(dsprops, "used", &propstr) == 0)
139                 space = strtoull(propstr, NULL, 10);
140
141         nvlist_free(dsprops);
142         free(dsname);
143         return (space);
144 }
145
146 static int
147 print_snapshots(const char *dsname, struct printc *pc)
148 {
149         nvpair_t *cur;
150         nvlist_t *props, *sprops;
151
152         if (be_prop_list_alloc(&props) != 0) {
153                 fprintf(stderr, "bectl list: failed to allocate snapshot nvlist\n");
154                 return (1);
155         }
156         if (be_get_dataset_snapshots(be, dsname, props) != 0) {
157                 fprintf(stderr, "bectl list: failed to fetch boot ds snapshots\n");
158                 return (1);
159         }
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);
164         }
165         return (0);
166 }
167
168 static void
169 print_info(const char *name, nvlist_t *dsprops, struct printc *pc)
170 {
171 #define BUFSZ   64
172         char buf[BUFSZ];
173         unsigned long long ctimenum, space;
174         nvlist_t *originprops;
175         const char *oname;
176         char *dsname, *propstr;
177         int active_colsz;
178         boolean_t active_now, active_reboot;
179
180         dsname = NULL;
181         originprops = NULL;
182         printf("%*s%s", pc->current_indent, "", name);
183         nvlist_lookup_string(dsprops, "dataset", &dsname);
184
185         /* Recurse at the base level if we're breaking info down */
186         if (pc->current_indent == 0 && (pc->show_all_datasets ||
187             pc->show_snaps)) {
188                 printf("\n");
189                 if (dsname == NULL)
190                         /* XXX TODO: Error? */
191                         return;
192                 /*
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.
197                  */
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);
204                 }
205
206                 /* Back up a level; snapshots at the same level as dataset */
207                 pc->current_indent -= INDENT_INCREMENT;
208                 if (pc->show_snaps)
209                         print_snapshots(dsname, pc);
210                 pc->current_indent = 0;
211                 return;
212         } else
213                 print_padding(name, pc->be_colsz - pc->current_indent, pc);
214
215         active_colsz = pc->active_colsz_def;
216         if (nvlist_lookup_boolean_value(dsprops, "active",
217             &active_now) == 0 && active_now) {
218                 printf("N");
219                 active_colsz--;
220         }
221         if (nvlist_lookup_boolean_value(dsprops, "nextboot",
222             &active_reboot) == 0 && active_reboot) {
223                 printf("R");
224                 active_colsz--;
225         }
226         if (active_colsz == pc->active_colsz_def) {
227                 printf("-");
228                 active_colsz--;
229         }
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);
234         } else {
235                 printf("%s", "-");
236                 print_padding("-", pc->mount_colsz, pc);
237         }
238
239         oname = get_origin_props(dsprops, &originprops);
240         if (nvlist_lookup_string(dsprops, "used", &propstr) == 0) {
241                 /*
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)
246                  *
247                  * The -D flag is ignored if -a or -s are specified.
248                  */
249                 space = strtoull(propstr, NULL, 10);
250
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);
255
256                 if (pc->show_space && oname != NULL)
257                         space += dataset_space(oname);
258
259                 /* Alas, there's more to it,. */
260                 be_nicenum(space, buf, 6);
261                 printf("%s", buf);
262                 print_padding(buf, pc->space_colsz, pc);
263         } else {
264                 printf("-");
265                 print_padding("-", pc->space_colsz, pc);
266         }
267
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));
272                 printf("%s", buf);
273         }
274
275         printf("\n");
276         if (originprops != NULL)
277                 be_prop_list_free(originprops);
278 #undef BUFSZ
279 }
280
281 static void
282 print_headers(nvlist_t *props, struct printc *pc)
283 {
284         const char *chosen_be_header;
285         nvpair_t *cur;
286         nvlist_t *dsprops;
287         char *propstr;
288         size_t be_maxcol;
289
290         if (pc->show_all_datasets || pc->show_snaps)
291                 chosen_be_header = HEADER_BEPLUS;
292         else
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)
299                         continue;
300                 nvpair_value_nvlist(cur, &dsprops);
301                 if (nvlist_lookup_string(dsprops, "dataset", &propstr) != 0)
302                         continue;
303                 be_maxcol = MAX(be_maxcol, strlen(propstr) + INDENT_INCREMENT);
304                 if (nvlist_lookup_string(dsprops, "origin", &propstr) != 0)
305                         continue;
306                 be_maxcol = MAX(be_maxcol,
307                     strlen(propstr) + INDENT_INCREMENT * 2);
308         }
309
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);
316
317         /*
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.
321          */
322         if (chosen_be_header != HEADER_BE)
323                 printf("\n");
324 }
325
326 int
327 bectl_cmd_list(int argc, char *argv[])
328 {
329         struct printc pc;
330         nvpair_t *cur;
331         nvlist_t *dsprops, *props;
332         int opt, printed;
333         boolean_t active_now, active_reboot;
334
335         props = NULL;
336         printed = 0;
337         bzero(&pc, sizeof(pc));
338         while ((opt = getopt(argc, argv, "aDHs")) != -1) {
339                 switch (opt) {
340                 case 'a':
341                         pc.show_all_datasets = true;
342                         break;
343                 case 'D':
344                         pc.show_space = true;
345                         break;
346                 case 'H':
347                         pc.script_fmt = true;
348                         break;
349                 case 's':
350                         pc.show_snaps = true;
351                         break;
352                 default:
353                         fprintf(stderr, "bectl list: unknown option '-%c'\n",
354                             optopt);
355                         return (usage(false));
356                 }
357         }
358
359         argc -= optind;
360
361         if (argc != 0) {
362                 fprintf(stderr, "bectl list: extra argument provided\n");
363                 return (usage(false));
364         }
365
366         if (be_prop_list_alloc(&props) != 0) {
367                 fprintf(stderr, "bectl list: failed to allocate prop nvlist\n");
368                 return (1);
369         }
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");
373                 return (1);
374         }
375
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;
379         if (!pc.script_fmt)
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;
386
387                 nvlist_lookup_boolean_value(dsprops, "active", &active_now);
388                 nvlist_lookup_boolean_value(dsprops, "nextboot",
389                     &active_reboot);
390                 if (!active_now && !active_reboot)
391                         continue;
392                 if (printed > 0 && (pc.show_all_datasets || pc.show_snaps))
393                         printf("\n");
394                 print_info(nvpair_name(cur), dsprops, &pc);
395                 printed++;
396         }
397
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;
403
404                 nvlist_lookup_boolean_value(dsprops, "active", &active_now);
405                 nvlist_lookup_boolean_value(dsprops, "nextboot",
406                     &active_reboot);
407                 if (active_now || active_reboot)
408                         continue;
409                 if (printed > 0 && (pc.show_all_datasets || pc.show_snaps))
410                         printf("\n");
411                 print_info(nvpair_name(cur), dsprops, &pc);
412                 printed++;
413         }
414         be_prop_list_free(props);
415
416         return (0);
417 }
418