Compare commits

...

3 Commits

Author SHA1 Message Date
Auke Kok
4e2c1e83be Optionally print out xattr values
We cannot validate that totl keys have the correct count/value without
also extracting and printing the value for xattrs, which by default
is omitted (a sane default). Add a default-disabled --xattr-values/-V
flag to enable printing these out.

Because xattr values can span multiple items, this only will print
out the first one, and ellipsize it if it continues elsewhere. It is
filtered through isprint() to avoid printing non-printable characters.

Signed-off-by: Auke Kok <auke.kok@versity.com>
2026-04-16 11:26:29 -07:00
Auke Kok
8c4e9bfa3e Add print filters for remaining types.
Chris already added print filters for most common types, but
omitted totl, indx, orphan, quota, and inode_index items. This
adds those as well, completing the set.

Signed-off-by: Auke Kok <auke.kok@versity.com>
2026-04-16 11:01:47 -07:00
Chris Kirby
91638191de Add finer grained options to scoutfs print
The default output from scoutfs print can be very large, even
when using the -S option. Add three new command line options
to allow more targeted selection of btrees and their items.

--allocs prints the metadata and data allocators
--roots allows the selection of btree roots to walk (logs, srch, fs)
--items allows the selection of items to print from the selected btrees

Signed-off-by: Chris Kirby <ckirby@versity.com>
2025-11-21 10:39:58 -06:00
2 changed files with 360 additions and 52 deletions

View File

@@ -402,25 +402,45 @@ before destroying an old empty data device.
.PD
.TP
.BI "print {-S|--skip-likely-huge} META-DEVICE"
.BI "print {-a|--allocs} {-i|--items ITEMS} {-r|--roots ROOTS} {-S|--skip-likely-huge} {-V|--xattr-values} META-DEVICE"
.sp
Prints out all of the metadata in the file system. This makes no effort
Prints out some or all of the metadata in the file system. This makes no effort
to ensure that the structures are consistent as they're traversed and
can present structures that seem corrupt as they change as they're
output.
.sp
Structures that are related to the number of mounts and are maintained at a
relatively reasonable size are always printed. These include per-mount log
trees, srch files, allocators, and the metadata allocators used by server
commits. Other btrees and their items can be selected as desired.
.RS 1.0i
.PD 0
.TP
.sp
.TP
.B "-a, --allocs"
Print the metadata and data allocators. Enabled by default.
.TP
.B "-r, --roots ROOTS"
This option can be used to select which btrees are traversed. It is a comma-separated list containing one or more of the following btree roots: logs, srch, fs. Default is all roots.
.TP
.B "-i, --items ITEMS"
This option can be used to choose which btree items are printed from the
selected btree roots. It is a comma-separated list containing one or
more of the following items: inode, xattr, dirent, symlink, backref, extent,
totl, indx, inoindex, orphan, quota.
Default is all items.
.TP
.B "-V, --xattr-values"
Print xattr values alongside the xattr item. Non-printable bytes are
rendered as '.'. A trailing '...' indicates the value continues in
additional item parts that aren't shown.
.TP
.B "-S, --skip-likely-huge"
Skip printing structures that are likely to be very large. The
structures that are skipped tend to be global and whose size tends to be
related to the size of the volume. Examples of skipped structures include
the global fs items, srch files, and metadata and data
allocators. Similar structures that are not skipped are related to the
number of mounts and are maintained at a relatively reasonable size.
These include per-mount log trees, srch files, allocators, and the
metadata allocators used by server commits.
allocators.
.sp
Skipping the larger structures limits the print output to a relatively
constant size rather than being a large multiple of the used metadata

View File

@@ -29,6 +29,54 @@
#include "leaf_item_hash.h"
#include "dev.h"
struct print_args {
char *meta_device;
bool skip_likely_huge;
bool roots_requested;
bool items_requested;
bool allocs_requested;
bool walk_allocs;
bool walk_logs_root;
bool walk_fs_root;
bool walk_srch_root;
bool print_inodes;
bool print_xattrs;
bool print_dirents;
bool print_symlinks;
bool print_backrefs;
bool print_extents;
bool print_totl;
bool print_indx;
bool print_inode_index;
bool print_orphan;
bool print_quota;
bool print_xattr_values;
};
static struct print_args print_args = {
.meta_device = NULL,
.skip_likely_huge = false,
.roots_requested = false,
.items_requested = false,
.allocs_requested = false,
.walk_allocs = true,
.walk_logs_root = true,
.walk_fs_root = true,
.walk_srch_root = true,
.print_inodes = true,
.print_xattrs = true,
.print_dirents = true,
.print_symlinks = true,
.print_backrefs = true,
.print_extents = true,
.print_totl = true,
.print_indx = true,
.print_inode_index = true,
.print_orphan = true,
.print_quota = true,
.print_xattr_values = false
};
static void print_block_header(struct scoutfs_block_header *hdr, int size)
{
u32 crc = crc_block(hdr, size);
@@ -135,15 +183,42 @@ static u8 *global_printable_name(u8 *name, int name_len)
static void print_xattr(struct scoutfs_key *key, void *val, int val_len)
{
struct scoutfs_xattr *xat = val;
unsigned int full_val_len;
int avail;
int show;
int i;
printf(" xattr: ino %llu name_hash %08x id %llu part %u\n",
le64_to_cpu(key->skx_ino), (u32)le64_to_cpu(key->skx_name_hash),
le64_to_cpu(key->skx_id), key->skx_part);
if (key->skx_part == 0)
printf(" name_len %u val_len %u name %s\n",
xat->name_len, le16_to_cpu(xat->val_len),
global_printable_name(xat->name, xat->name_len));
if (key->skx_part != 0)
return;
full_val_len = le16_to_cpu(xat->val_len);
printf(" name_len %u val_len %u name %s",
xat->name_len, full_val_len,
global_printable_name(xat->name, xat->name_len));
if (!print_args.print_xattr_values) {
putchar('\n');
return;
}
avail = val_len - (int)sizeof(*xat) - xat->name_len;
if (avail < 0)
avail = 0;
show = avail < (int)full_val_len ? avail : (int)full_val_len;
printf(" value ");
for (i = 0; i < show; i++) {
u8 c = xat->name[xat->name_len + i];
putchar(isprint(c) ? c : '.');
}
if (show < (int)full_val_len)
printf("...");
putchar('\n');
}
static void print_dirent(struct scoutfs_key *key, void *val, int val_len)
@@ -195,36 +270,72 @@ static void print_inode_index(struct scoutfs_key *key, void *val, int val_len)
typedef void (*print_func_t)(struct scoutfs_key *key, void *val, int val_len);
static print_func_t find_printer(u8 zone, u8 type)
static print_func_t find_printer(u8 zone, u8 type, bool *suppress)
{
if (zone == SCOUTFS_INODE_INDEX_ZONE &&
type >= SCOUTFS_INODE_INDEX_META_SEQ_TYPE &&
type <= SCOUTFS_INODE_INDEX_DATA_SEQ_TYPE)
type <= SCOUTFS_INODE_INDEX_DATA_SEQ_TYPE) {
if (!print_args.print_inode_index)
*suppress = true;
return print_inode_index;
if (zone == SCOUTFS_ORPHAN_ZONE) {
if (type == SCOUTFS_ORPHAN_TYPE)
return print_orphan;
}
if (zone == SCOUTFS_QUOTA_ZONE)
if (zone == SCOUTFS_ORPHAN_ZONE) {
if (type == SCOUTFS_ORPHAN_TYPE) {
if (!print_args.print_orphan)
*suppress = true;
return print_orphan;
}
}
if (zone == SCOUTFS_QUOTA_ZONE) {
if (!print_args.print_quota)
*suppress = true;
return print_quota;
}
if (zone == SCOUTFS_XATTR_TOTL_ZONE)
if (zone == SCOUTFS_XATTR_TOTL_ZONE) {
if (!print_args.print_totl)
*suppress = true;
return print_xattr_totl;
}
if (zone == SCOUTFS_XATTR_INDX_ZONE)
if (zone == SCOUTFS_XATTR_INDX_ZONE) {
if (!print_args.print_indx)
*suppress = true;
return print_xattr_indx;
}
if (zone == SCOUTFS_FS_ZONE) {
switch(type) {
case SCOUTFS_INODE_TYPE: return print_inode;
case SCOUTFS_XATTR_TYPE: return print_xattr;
case SCOUTFS_DIRENT_TYPE: return print_dirent;
case SCOUTFS_READDIR_TYPE: return print_dirent;
case SCOUTFS_SYMLINK_TYPE: return print_symlink;
case SCOUTFS_LINK_BACKREF_TYPE: return print_dirent;
case SCOUTFS_DATA_EXTENT_TYPE: return print_data_extent;
case SCOUTFS_INODE_TYPE:
if (!print_args.print_inodes)
*suppress = true;
return print_inode;
case SCOUTFS_XATTR_TYPE:
if (!print_args.print_xattrs)
*suppress = true;
return print_xattr;
case SCOUTFS_DIRENT_TYPE:
if (!print_args.print_dirents)
*suppress = true;
return print_dirent;
case SCOUTFS_READDIR_TYPE:
if (!print_args.print_dirents)
*suppress = true;
return print_dirent;
case SCOUTFS_SYMLINK_TYPE:
if (!print_args.print_symlinks)
*suppress = true;
return print_symlink;
case SCOUTFS_LINK_BACKREF_TYPE:
if (!print_args.print_backrefs)
*suppress = true;
return print_dirent;
case SCOUTFS_DATA_EXTENT_TYPE:
if (!print_args.print_extents)
*suppress = true;
return print_data_extent;
}
}
@@ -244,12 +355,16 @@ static int print_fs_item(struct scoutfs_key *key, u64 seq, u8 flags, void *val,
/* only items in leaf blocks have values */
if (val != NULL && !(flags & SCOUTFS_ITEM_FLAG_DELETION)) {
printer = find_printer(key->sk_zone, key->sk_type);
if (printer)
printer(key, val, val_len);
else
bool suppress = false;
printer = find_printer(key->sk_zone, key->sk_type, &suppress);
if (printer) {
if (!suppress)
printer(key, val, val_len);
} else {
printf(" (unknown zone %u type %u)\n",
key->sk_zone, key->sk_type);
}
}
return 0;
@@ -1037,12 +1152,7 @@ static void print_super_block(struct scoutfs_super_block *super, u64 blkno)
}
}
struct print_args {
char *meta_device;
bool skip_likely_huge;
};
static int print_volume(int fd, struct print_args *args)
static int print_volume(int fd)
{
struct scoutfs_super_block *super = NULL;
struct print_recursion_args pa;
@@ -1092,7 +1202,7 @@ static int print_volume(int fd, struct print_args *args)
ret = err;
}
if (!args->skip_likely_huge) {
if (print_args.walk_allocs) {
for (i = 0; i < array_size(super->meta_alloc); i++) {
snprintf(str, sizeof(str), "meta_alloc[%u]", i);
err = print_btree(fd, super, str, &super->meta_alloc[i].root,
@@ -1119,18 +1229,21 @@ static int print_volume(int fd, struct print_args *args)
pa.super = super;
pa.fd = fd;
if (!args->skip_likely_huge) {
if (print_args.walk_srch_root) {
err = print_btree_leaf_items(fd, super, &super->srch_root.ref,
print_srch_root_files, &pa);
if (err && !ret)
ret = err;
}
err = print_btree_leaf_items(fd, super, &super->logs_root.ref,
print_log_trees_roots, &pa);
if (err && !ret)
ret = err;
if (!args->skip_likely_huge) {
if (print_args.walk_logs_root) {
err = print_btree_leaf_items(fd, super, &super->logs_root.ref,
print_log_trees_roots, &pa);
if (err && !ret)
ret = err;
}
if (print_args.walk_fs_root) {
err = print_btree(fd, super, "fs_root", &super->fs_root,
print_fs_item, NULL);
if (err && !ret)
@@ -1143,16 +1256,16 @@ out:
return ret;
}
static int do_print(struct print_args *args)
static int do_print(void)
{
int ret;
int fd;
fd = open(args->meta_device, O_RDONLY);
fd = open(print_args.meta_device, O_RDONLY);
if (fd < 0) {
ret = -errno;
fprintf(stderr, "failed to open '%s': %s (%d)\n",
args->meta_device, strerror(errno), errno);
print_args.meta_device, strerror(errno), errno);
return ret;
}
@@ -1160,30 +1273,203 @@ static int do_print(struct print_args *args)
if (ret < 0)
goto out;
ret = print_volume(fd, args);
ret = print_volume(fd);
out:
close(fd);
return ret;
};
enum {
LOGS_OPT = 0,
FS_OPT,
SRCH_OPT
};
static char *const root_tokens[] = {
[LOGS_OPT] = "logs",
[FS_OPT] = "fs",
[SRCH_OPT] = "srch",
NULL
};
enum {
INODE_OPT = 0,
XATTR_OPT,
DIRENT_OPT,
SYMLINK_OPT,
BACKREF_OPT,
EXTENT_OPT,
TOTL_OPT,
INDX_OPT,
INOINDEX_OPT,
ORPHAN_OPT,
QUOTA_OPT
};
static char *const item_tokens[] = {
[INODE_OPT] = "inode",
[XATTR_OPT] = "xattr",
[DIRENT_OPT] = "dirent",
[SYMLINK_OPT] = "symlink",
[BACKREF_OPT] = "backref",
[EXTENT_OPT] = "extent",
[TOTL_OPT] = "totl",
[INDX_OPT] = "indx",
[INOINDEX_OPT] = "inoindex",
[ORPHAN_OPT] = "orphan",
[QUOTA_OPT] = "quota",
NULL
};
static void clear_items(void)
{
print_args.print_inodes = false;
print_args.print_xattrs = false;
print_args.print_dirents = false;
print_args.print_symlinks = false;
print_args.print_backrefs = false;
print_args.print_extents = false;
print_args.print_totl = false;
print_args.print_indx = false;
print_args.print_inode_index = false;
print_args.print_orphan = false;
print_args.print_quota = false;
}
static void clear_roots(void)
{
print_args.walk_logs_root = false;
print_args.walk_fs_root = false;
print_args.walk_srch_root = false;
}
static int parse_opt(int key, char *arg, struct argp_state *state)
{
struct print_args *args = state->input;
char *subopts;
char *value;
bool parse_err = false;
switch (key) {
case 'S':
args->skip_likely_huge = true;
break;
case 'a':
args->allocs_requested = true;
args->walk_allocs = true;
break;
case 'V':
args->print_xattr_values = true;
break;
case 'i':
/* Specific items being requested- clear them all to start */
if (!args->items_requested) {
clear_items();
if (!args->allocs_requested)
args->walk_allocs = false;
args->items_requested = true;
}
subopts = arg;
while (*subopts != '\0' && !parse_err) {
switch (getsubopt(&subopts, item_tokens, &value)) {
case INODE_OPT:
args->print_inodes = true;
break;
case XATTR_OPT:
args->print_xattrs = true;
break;
case DIRENT_OPT:
args->print_dirents = true;
break;
case SYMLINK_OPT:
args->print_symlinks = true;
break;
case BACKREF_OPT:
args->print_backrefs = true;
break;
case EXTENT_OPT:
args->print_extents = true;
break;
case TOTL_OPT:
args->print_totl = true;
break;
case INDX_OPT:
args->print_indx = true;
break;
case INOINDEX_OPT:
args->print_inode_index = true;
break;
case ORPHAN_OPT:
args->print_orphan = true;
break;
case QUOTA_OPT:
args->print_quota = true;
break;
default:
argp_usage(state);
parse_err = true;
break;
}
}
break;
case 'r':
/* Specific roots being requested- clear them all to start */
if (!args->roots_requested) {
clear_roots();
if (!args->allocs_requested)
args->walk_allocs = false;
args->roots_requested = true;
}
subopts = arg;
while (*subopts != '\0' && !parse_err) {
switch (getsubopt(&subopts, root_tokens, &value)) {
case LOGS_OPT:
args->walk_logs_root = true;
break;
case FS_OPT:
args->walk_fs_root = true;
break;
case SRCH_OPT:
args->walk_srch_root = true;
break;
default:
argp_usage(state);
parse_err = true;
break;
}
}
break;
case ARGP_KEY_ARG:
if (!args->meta_device)
args->meta_device = strdup_or_error(state, arg);
else
argp_error(state, "more than one argument given");
break;
case ARGP_KEY_FINI:
if (!args->meta_device)
argp_error(state, "no metadata device argument given");
/*
* For backwards compatibility, translate -S. Should we warn if
* this conflicts with other explicit options?
*/
if (args->skip_likely_huge) {
if (!args->allocs_requested)
args->walk_allocs = false;
args->walk_fs_root = false;
args->walk_srch_root = false;
}
break;
default:
break;
}
@@ -1192,7 +1478,11 @@ static int parse_opt(int key, char *arg, struct argp_state *state)
}
static struct argp_option options[] = {
{ "skip-likely-huge", 'S', NULL, 0, "Skip large structures to minimize output size"},
{ "allocs", 'a', NULL, 0, "Print metadata and data alloc lists" },
{ "items", 'i', "ITEMS", 0, "Item(s) to print (inode, xattr, dirent, symlink, backref, extent, totl, indx, inoindex, orphan, quota)" },
{ "roots", 'r', "ROOTS", 0, "Tree root(s) to walk (logs, srch, fs)" },
{ "skip-likely-huge", 'S', NULL, 0, "Skip allocs, srch root and fs root to minimize output size" },
{ "xattr-values", 'V', NULL, 0, "Print xattr values (non-printable bytes rendered as '.')" },
{ NULL }
};
@@ -1205,17 +1495,15 @@ static struct argp argp = {
static int print_cmd(int argc, char **argv)
{
struct print_args print_args = {NULL};
int ret;
ret = argp_parse(&argp, argc, argv, 0, NULL, &print_args);
if (ret)
return ret;
return do_print(&print_args);
return do_print();
}
static void __attribute__((constructor)) print_ctor(void)
{
cmd_register_argp("print", &argp, GROUP_DEBUG, print_cmd);