bectl(8): implement sorting for 'bectl list' output

Allow 'bectl list' to sort output by a given property name. The property
name is passed in using a command-line flag, '-c' for ascending order and
'-C' for descending order. The properties allowed to sort by are:

- name (the default output, even if '-c' or '-C' are not used)
- creation
- origin
- used
- usedds
- usedsnap
- usedrefreserv

The default output for 'bectl list' is now ascending alphabetical order of
BE name.

To sort by creation time from earliest to latest, the command would be
'bectl list -c creation'

Submitted by:	Rob Fairbanks <rob.fx907 gmail com>
Reviewed by:	ler
MFC after:	1 week
Differential Revision:	https://reviews.freebsd.org/D20818
This commit is contained in:
kevans 2019-09-04 13:59:06 +00:00
parent ceb1b924ec
commit a7bf7ad218
3 changed files with 148 additions and 40 deletions

View File

@ -18,7 +18,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd May 12, 2019
.Dd September 4, 2019
.Dt BECTL 8
.Os
.Sh NAME
@ -57,6 +57,9 @@
.Nm
.Cm list
.Op Fl aDHs
.Op Fl c Ar property
.Op Fl C Ar property
.Oo Bro Fl c Ar property | Fl C Ar property Brc Oc
.Nm
.Cm mount
.Ar beName
@ -234,7 +237,12 @@ generated by
.El
.Pp
All default parameters may be overwritten.
.It Cm list Op Fl aDHs
.It Xo
.Cm list
.Op Fl DHas
.Oo Bro Fl c Ar property | Fl C Ar property Brc Oc
.Xc
.Pp
Display all boot environments.
The
.Em Active
@ -245,21 +253,44 @@ active on reboot
or both
.Pq Em \&NR .
.Pp
If
.Fl a
is used, display all datasets.
If
.Fl D
is used, display the full space usage for each boot environment, assuming all
.Bl -tag -width indent
.It Fl a
Display all datasets.
.It Fl D
Display the full space usage for each boot environment, assuming all
other boot environments were destroyed.
The
.Fl H
option is used for scripting.
It does not print headers and separate fields by a single tab instead of
.It Fl H
Used for scripting.
Do not print headers and separate fields by a single tab instead of
arbitrary white space.
If
.It Fl s
Display all snapshots as well.
.It Fl c Ar property
Sort boot environments by given property name.
The following properties are supported:
.Pp
.Bl -tag -width 4n -offset indent -compact
.It name (default output)
.It creation
.It origin
.It used
.It usedds
.It usedsnap
.It usedrefreserv
.El
.It Fl C Ar property
Same as the
.Fl c
option, but displays in descending order.
.El
.Pp
The
.Fl D
option is ignored when either the
.Fl s
is used, display all snapshots as well.
or
.Fl a
option is used.
.It Cm mount Ar beName Op Ar mountpoint
Temporarily mount the boot environment.
Mount at the specified

View File

@ -80,7 +80,7 @@ usage(bool explicit)
"\tbectl jail {-b | -U} [{-o key=value | -u key}]... "
"{jailID | jailName}\n"
"\t bootenv [utility [argument ...]]\n"
"\tbectl list [-DHas]\n"
"\tbectl list [-DHas] [{-c property | -C property}]\n"
"\tbectl mount beName [mountpoint]\n"
"\tbectl rename origBeName newBeName\n"
"\tbectl {ujail | unjail} {jailID | jailName} bootenv\n"

View File

@ -38,6 +38,12 @@ __FBSDID("$FreeBSD$");
#include "bectl.h"
struct sort_column {
char *name;
char *val;
nvlist_t *nvl;
};
struct printc {
int active_colsz_def;
int be_colsz;
@ -324,6 +330,74 @@ print_headers(nvlist_t *props, struct printc *pc)
printf("\n");
}
/*
* Sort the given nvlist of boot environments by property.
*/
static int
prop_list_sort(nvlist_t *props, char *property, bool reverse)
{
nvpair_t *nvp;
nvlist_t *nvl;
int i, nvp_count;
uint64_t lval, rval;
struct sort_column sc_prev, sc_next;
/* a temporary list to work with */
nvlist_dup(props, &nvl, 0);
nvp_count = fnvlist_num_pairs(nvl);
for (i = 0; i < nvp_count; i++) {
nvp = nvlist_next_nvpair(nvl, NULL);
nvpair_value_nvlist(nvp, &sc_prev.nvl);
nvlist_lookup_string(sc_prev.nvl, "name", &sc_prev.name);
nvlist_lookup_string(sc_prev.nvl, property, &sc_prev.val);
while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
nvpair_value_nvlist(nvp, &sc_next.nvl);
nvlist_lookup_string(sc_next.nvl, "name", &sc_next.name);
nvlist_lookup_string(sc_next.nvl, property, &sc_next.val);
/* properties that use numerical comparison */
if (strcmp(property, "creation") == 0 ||
strcmp(property, "used") == 0 ||
strcmp(property, "usedds") == 0 ||
strcmp(property, "usedsnap") == 0 ||
strcmp(property, "usedrefreserv") == 0) {
lval = strtoull(sc_prev.val, NULL, 10);
rval = strtoull(sc_next.val, NULL, 10);
if ((lval < rval && reverse) ||
(lval > rval && !reverse))
sc_prev = sc_next;
}
/* properties that use string comparison */
else if (strcmp(property, "name") == 0 ||
strcmp(property, "origin") == 0) {
if ((strcmp(sc_prev.val, sc_next.val) < 0 && reverse) ||
(strcmp(sc_prev.val, sc_next.val) > 0 && !reverse))
sc_prev = sc_next;
}
}
/*
* The 'props' nvlist has been created to only have unique names.
* When a name is added, any existing nvlist's with the same name
* will be removed. Eventually, all existing nvlist's are replaced
* in sorted order.
*/
nvlist_add_nvlist(props, sc_prev.name, sc_prev.nvl);
nvlist_remove_all(nvl, sc_prev.name);
}
be_prop_list_free(nvl);
return 0;
}
int
bectl_cmd_list(int argc, char *argv[])
{
@ -331,12 +405,14 @@ bectl_cmd_list(int argc, char *argv[])
nvpair_t *cur;
nvlist_t *dsprops, *props;
int opt, printed;
boolean_t active_now, active_reboot;
char *column;
bool reverse;
column = NULL;
props = NULL;
printed = 0;
bzero(&pc, sizeof(pc));
while ((opt = getopt(argc, argv, "aDHs")) != -1) {
while ((opt = getopt(argc, argv, "aDHsc:C:")) != -1) {
switch (opt) {
case 'a':
pc.show_all_datasets = true;
@ -350,6 +426,18 @@ bectl_cmd_list(int argc, char *argv[])
case 's':
pc.show_snaps = true;
break;
case 'c':
if (column != NULL)
free(column);
column = strdup(optarg);
reverse = false;
break;
case 'C':
if (column != NULL)
free(column);
column = strdup(optarg);
reverse = true;
break;
default:
fprintf(stderr, "bectl list: unknown option '-%c'\n",
optopt);
@ -374,44 +462,33 @@ bectl_cmd_list(int argc, char *argv[])
return (1);
}
/* List boot environments in alphabetical order by default */
if (column == NULL) {
column = strdup("name");
reverse = false;
}
prop_list_sort(props, column, reverse);
/* Force -D off if either -a or -s are specified */
if (pc.show_all_datasets || pc.show_snaps)
pc.show_space = false;
if (!pc.script_fmt)
print_headers(props, &pc);
/* Do a first pass to print active and next active first */
/* Print boot environments */
for (cur = nvlist_next_nvpair(props, NULL); cur != NULL;
cur = nvlist_next_nvpair(props, cur)) {
nvpair_value_nvlist(cur, &dsprops);
active_now = active_reboot = false;
nvlist_lookup_boolean_value(dsprops, "active", &active_now);
nvlist_lookup_boolean_value(dsprops, "nextboot",
&active_reboot);
if (!active_now && !active_reboot)
continue;
if (printed > 0 && (pc.show_all_datasets || pc.show_snaps))
printf("\n");
print_info(nvpair_name(cur), dsprops, &pc);
printed++;
}
/* Now pull everything else */
for (cur = nvlist_next_nvpair(props, NULL); cur != NULL;
cur = nvlist_next_nvpair(props, cur)) {
nvpair_value_nvlist(cur, &dsprops);
active_now = active_reboot = false;
nvlist_lookup_boolean_value(dsprops, "active", &active_now);
nvlist_lookup_boolean_value(dsprops, "nextboot",
&active_reboot);
if (active_now || active_reboot)
continue;
if (printed > 0 && (pc.show_all_datasets || pc.show_snaps))
printf("\n");
print_info(nvpair_name(cur), dsprops, &pc);
printed++;
}
free(column);
be_prop_list_free(props);
return (0);