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