scoutfs-utils: support small keys

Make the changes to support the new small key struct.  mkfs and print
work with simpler keys, segment items, and manifest entries.  The item
cache keys ioctl now just needs to work with arrays of keys.

Signed-off-by: Zach Brown <zab@versity.com>
This commit is contained in:
Zach Brown
2018-01-18 14:19:15 -08:00
committed by Mark Swan
parent 837310e8e6
commit 8e6c18a0fa
10 changed files with 422 additions and 640 deletions

23
utils/src/cmp.h Normal file
View File

@@ -0,0 +1,23 @@
#ifndef _SCOUTFS_CMP_H_
#define _SCOUTFS_CMP_H_
/*
* A generic ternary comparison macro with strict type checking.
*/
#define scoutfs_cmp(a, b) \
({ \
__typeof__(a) _a = (a); \
__typeof__(b) _b = (b); \
int _ret; \
\
(void) (&_a == &_b); \
_ret = _a < _b ? -1 : _a > _b ? 1 : 0; \
_ret; \
})
static inline int scoutfs_cmp_u64s(u64 a, u64 b)
{
return a < b ? -1 : a > b ? 1 : 0;
}
#endif

12
utils/src/endian_swap.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef _SCOUTFS_ENDIAN_SWAP_H_
#define _SCOUTFS_ENDIAN_SWAP_H_
#define le64_to_be64(x) cpu_to_be64(le64_to_cpu(x))
#define le32_to_be32(x) cpu_to_be32(le32_to_cpu(x))
#define le16_to_be16(x) cpu_to_be16(le16_to_cpu(x))
#define be64_to_le64(x) cpu_to_le64(be64_to_cpu(x))
#define be32_to_le32(x) cpu_to_le32(be32_to_cpu(x))
#define be16_to_le16(x) cpu_to_le16(be16_to_cpu(x))
#endif

View File

@@ -52,6 +52,75 @@ struct scoutfs_block_header {
__le64 blkno;
} __packed;
/*
* scoutfs identifies all file system metadata items by a small key
* struct.
*
* Each item type maps their logical structures to the fixed fields in
* sort order. This lets us print keys without needing per-type
* formats.
*
* The keys are compared by considering the fields in struct order from
* most to least significant. They are considered a multi precision
* value when navigating the keys in ordered key space. We can
* increment them, subtract them from each other, etc.
*/
struct scoutfs_key {
__u8 sk_zone;
__le64 _sk_first;
__u8 sk_type;
__le64 _sk_second;
__le64 _sk_third;
__u8 _sk_fourth;
}__packed;
/* inode index */
#define skii_major _sk_second
#define skii_ino _sk_third
/* node free bit map */
#define skf_node_id _sk_first
#define skf_base _sk_second
/* node orphan inode */
#define sko_node_id _sk_first
#define sko_ino _sk_second
/* inode */
#define ski_ino _sk_first
/* xattr parts */
#define skx_ino _sk_first
#define skx_name_hash _sk_second
#define skx_id _sk_third
#define skx_part _sk_fourth
/* directory entries */
#define skd_ino _sk_first
#define skd_major _sk_second
#define skd_minor _sk_third
/* symlink target */
#define sks_ino _sk_first
#define sks_nr _sk_second
/* file data mapping */
#define skm_ino _sk_first
#define skm_base _sk_second
/*
* The btree still uses memcmp() to compare keys. We should fix that
* before too long.
*/
struct scoutfs_key_be {
__u8 sk_zone;
__be64 _sk_first;
__u8 sk_type;
__be64 _sk_second;
__be64 _sk_third;
__u8 _sk_fourth;
}__packed;
/*
* Assert that we'll be able to represent all possible keys with 8 64bit
* primary sort values.
@@ -143,34 +212,24 @@ struct scoutfs_manifest {
} __packed;
/*
* Manifest entries are packed into btree keys and values in a very
* fiddly way so that we can sort them with memcmp first by level then
* by their position in the level. First comes the level.
* Manifest entries are split across btree keys and values. Putting
* some entry fields in the value keeps the key smaller and increases
* the fanout of the btree which keeps the tree smaller and reduces
* block IO.
*
* Level 0 segments are sorted by their seq so they don't have the first
* segment key in the manifest btree key. Both of their keys are in the
* value.
*
* Level 1 segments are sorted by their first key so their last key is
* in the value.
*
* We go to all this trouble so that we can communicate a version of the
* manifest with one btree root, have dense btree keys which are used as
* seperators in parent blocks, and don't duplicate the large keys in
* the manifest btree key and value.
* The key is made up of the level, first key, and seq. At level 0
* segments can completely overlap and have identical key ranges but we
* avoid duplicate btree keys by including the unique seq.
*/
struct scoutfs_manifest_btree_key {
__u8 level;
__u8 bkey[0];
struct scoutfs_key_be first_key;
__be64 seq;
} __packed;
struct scoutfs_manifest_btree_val {
__le64 segno;
__le64 seq;
__le16 first_key_len;
__le16 last_key_len;
__u8 keys[0];
struct scoutfs_key last_key;
} __packed;
#define SCOUTFS_ALLOC_REGION_SHIFT 8
@@ -201,15 +260,12 @@ struct scoutfs_alloc_region_btree_val {
* They're not allowed to cross a block boundary.
*/
struct scoutfs_segment_item {
__le16 key_len;
struct scoutfs_key key;
__le16 val_len;
__u8 flags;
__u8 nr_links;
__le32 skip_links[0];
/*
* __u8 key_bytes[key_len]
* __u8 val_bytes[val_len]
*/
/* __u8 val_bytes[val_len] */
} __packed;
#define SCOUTFS_ITEM_FLAG_DELETION (1 << 0)
@@ -259,30 +315,6 @@ struct scoutfs_segment_block {
#define SCOUTFS_MAX_TYPE 16 /* power of 2 is efficient */
/* value is struct scoutfs_inode */
struct scoutfs_inode_key {
__u8 zone;
__be64 ino;
__u8 type;
} __packed;
/* value is struct scoutfs_dirent with the name */
struct scoutfs_dirent_key {
__u8 zone;
__be64 ino;
__u8 type;
__be64 major;
__be64 minor;
} __packed;
/* key is bytes of encoded block mapping */
struct scoutfs_block_mapping_key {
__u8 zone;
__be64 ino;
__u8 type;
__be64 base;
} __packed;
/* each mapping item describes a fixed number of blocks */
#define SCOUTFS_BLOCK_MAPPING_SHIFT 6
#define SCOUTFS_BLOCK_MAPPING_BLOCKS (1 << SCOUTFS_BLOCK_MAPPING_SHIFT)
@@ -328,33 +360,10 @@ struct scoutfs_block_mapping_key {
#define SCOUTFS_FREE_BITS_U64S \
DIV_ROUND_UP(SCOUTFS_FREE_BITS_BITS, 64)
struct scoutfs_free_bits_key {
__u8 zone;
__be64 node_id;
__u8 type;
__be64 base;
} __packed;
struct scoutfs_free_bits {
__le64 bits[SCOUTFS_FREE_BITS_U64S];
} __packed;
struct scoutfs_orphan_key {
__u8 zone;
__be64 node_id;
__u8 type;
__be64 ino;
} __packed;
struct scoutfs_xattr_key {
__u8 zone;
__be64 ino;
__u8 type;
__be32 name_hash;
__be64 id;
__u8 part;
} __packed;
/*
* The first xattr part item has a header that describes the xattr. The
* name and value are then packed into the following bytes in the first
@@ -366,27 +375,11 @@ struct scoutfs_xattr {
__u8 name[0];
} __packed;
/* size determines nr needed to store full target path in their values */
struct scoutfs_symlink_key {
__u8 zone;
__be64 ino;
__u8 type;
__u8 nr;
} __packed;
struct scoutfs_betimespec {
__be64 sec;
__be32 nsec;
} __packed;
struct scoutfs_inode_index_key {
__u8 zone;
__u8 type;
__be64 major;
__be32 minor;
__be64 ino;
} __packed;
/* XXX does this exist upstream somewhere? */
#define member_sizeof(TYPE, MEMBER) (sizeof(((TYPE *)0)->MEMBER))
@@ -514,9 +507,6 @@ enum {
SCOUTFS_DT_WHT,
};
#define SCOUTFS_MAX_KEY_SIZE \
sizeof(struct scoutfs_dirent_key)
#define SCOUTFS_MAX_VAL_SIZE SCOUTFS_BLOCK_MAPPING_MAX_BYTES
#define SCOUTFS_XATTR_MAX_NAME_LEN 255
@@ -591,8 +581,8 @@ struct scoutfs_net_key_range {
struct scoutfs_net_manifest_entry {
__le64 segno;
__le64 seq;
__le16 first_key_len;
__le16 last_key_len;
struct scoutfs_key first;
struct scoutfs_key last;
__u8 level;
__u8 keys[0];
} __packed;

View File

@@ -208,11 +208,16 @@ struct scoutfs_ioctl_stat_more {
#define SCOUTFS_IOC_STAT_MORE _IOW(SCOUTFS_IOCTL_MAGIC, 7, \
struct scoutfs_ioctl_stat_more)
/*
* Fills the buffer with either the keys for the cached items or the
* keys for the cached ranges found starting with the given key. The
* number of keys filled in the buffer is returned. When filling range
* keys the returned number will always be a multiple of two.
*/
struct scoutfs_ioctl_item_cache_keys {
__u64 key_ptr;
__u64 key_len;
struct scoutfs_key key;
__u64 buf_ptr;
__u64 buf_len;
__u16 buf_nr;
__u8 which;
} __packed;

View File

@@ -16,47 +16,32 @@
#include "cmd.h"
#include "key.h"
#define BUF_SIZE (64 * 1024)
static int item_cache_keys(int argc, char **argv, int which)
{
struct scoutfs_ioctl_item_cache_keys ick;
unsigned nr;
u16 key_len;
void *buf;
void *ptr;
struct scoutfs_key keys[32];
int ret;
int fd;
int i;
if (argc != 2) {
fprintf(stderr, "too many arguments, only scoutfs path needed");
return -EINVAL;
}
buf = malloc(BUF_SIZE);
if (!buf) {
ret = -errno;
fprintf(stderr, "failed to allocate buf: %s (%d)\n",
strerror(errno), errno);
return ret;
}
fd = open(argv[1], O_RDONLY);
if (fd < 0) {
ret = -errno;
fprintf(stderr, "failed to open '%s': %s (%d)\n",
argv[1], strerror(errno), errno);
free(buf);
return ret;
}
ick.buf_ptr = (unsigned long)buf;
ick.buf_len = BUF_SIZE;
ick.key_ptr = 0;
ick.key_len = 0;
memset(&ick, 0, sizeof(ick));
ick.buf_ptr = (unsigned long)keys;
ick.buf_nr = array_size(keys);
ick.which = which;
nr = 1;
for (;;) {
ret = ioctl(fd, SCOUTFS_IOC_ITEM_CACHE_KEYS, &ick);
if (ret < 0) {
@@ -68,47 +53,21 @@ static int item_cache_keys(int argc, char **argv, int which)
break;
}
ptr = (void *)(unsigned long)ick.buf_ptr;
for (i = 0; i < ret; i++) {
printf(SK_FMT, SK_ARG(&keys[i]));
while (ret) {
if (ret < sizeof(key_len)) {
fprintf(stderr, "truncated len: %d\n", ret);
ret = -EINVAL;
break;
}
memcpy(&key_len, ptr, sizeof(key_len));
ptr += sizeof(key_len);
ret -= sizeof(key_len);
if (ret < key_len) {
fprintf(stderr, "key len %d < buffer %d\n",
key_len, ret);
ret = -EINVAL;
break;
}
print_key(ptr, key_len);
if (which == SCOUTFS_IOC_ITEM_CACHE_KEYS_ITEMS ||
(nr % 2) == 0)
(i & 1))
printf("\n");
else
printf(" - ");
ick.key_ptr = (unsigned long)ptr;
ick.key_len = key_len;
ptr += key_len;
ret -= key_len;
nr++;
}
if (ret < 0)
break;
ick.key = keys[i - 1];
scoutfs_key_inc(&ick.key);
}
close(fd);
free(buf);
return ret;
};

View File

@@ -1,349 +1,55 @@
#include <unistd.h>
/*
* Copyright (C) 2018 Versity Software, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License v2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <assert.h>
#include "sparse.h"
#include "util.h"
#include "format.h"
#include "key.h"
/*
* To print keys we wrap the key snprintf code from the kernel with a
* few support functions. We need a few functions that the kernel has
* that we don't provide, then we implement our printing function by
* allocating a buffer for the formatted output then just printing it.
*
* To update the key printing code from the kernel we just need to make
* scoutfs_key_str_size() static and replace the snprintf call with the
* kernel's "%phN" format with the call to our replacement.
*
* This is not efficient but this isn't a performant path.
*/
#define min_t(t, a, b) min(a, b)
struct scoutfs_key_buf {
void *data;
unsigned key_len;
char *scoutfs_zone_strings[SCOUTFS_MAX_ZONE] = {
[SCOUTFS_INODE_INDEX_ZONE] = "ind",
[SCOUTFS_NODE_ZONE] = "nod",
[SCOUTFS_FS_ZONE] = "fs",
};
/*
* like snprintf(buf, size, "%*phN", nr, bytes) in the kernel, but this
* is only called when there's room for the formatted output because
* we've already been through once with a 0 buffer to allocate a buffer
* for the output.
*/
static int snprintf_phN(char *buf, size_t size, unsigned nr, char *bytes)
char *scoutfs_type_strings[SCOUTFS_MAX_ZONE][SCOUTFS_MAX_TYPE] = {
[SCOUTFS_INODE_INDEX_ZONE][SCOUTFS_INODE_INDEX_META_SEQ_TYPE] = "msq",
[SCOUTFS_INODE_INDEX_ZONE][SCOUTFS_INODE_INDEX_DATA_SEQ_TYPE] = "dsq",
[SCOUTFS_NODE_ZONE][SCOUTFS_FREE_BITS_SEGNO_TYPE] = "fsg",
[SCOUTFS_NODE_ZONE][SCOUTFS_FREE_BITS_BLKNO_TYPE] = "fbk",
[SCOUTFS_NODE_ZONE][SCOUTFS_ORPHAN_TYPE] = "orp",
[SCOUTFS_FS_ZONE][SCOUTFS_INODE_TYPE] = "ino",
[SCOUTFS_FS_ZONE][SCOUTFS_XATTR_TYPE] = "xat",
[SCOUTFS_FS_ZONE][SCOUTFS_DIRENT_TYPE] = "dnt",
[SCOUTFS_FS_ZONE][SCOUTFS_READDIR_TYPE] = "rdr",
[SCOUTFS_FS_ZONE][SCOUTFS_LINK_BACKREF_TYPE] = "lbr",
[SCOUTFS_FS_ZONE][SCOUTFS_SYMLINK_TYPE] = "sym",
[SCOUTFS_FS_ZONE][SCOUTFS_BLOCK_MAPPING_TYPE] = "bmp",
};
char scoutfs_unknown_u8_strings[U8_MAX][U8_STR_MAX];
static void __attribute__((constructor)) scoutfs_key_init(void)
{
int ret = 0;
int ret;
int i;
for (i = 0; i < nr; i++)
ret += sprintf(buf + ret, "%02x", bytes[i]);
return ret;
}
static char *memchr_inv(char *str, int c, size_t len)
{
while (len--) {
if (*(str++) != c)
return str - 1;
}
return NULL;
}
static int scoutfs_key_str_size(char *buf, struct scoutfs_key_buf *key,
size_t size);
void print_key(void *key_data, unsigned key_len)
{
struct scoutfs_key_buf key = {.data = key_data, .key_len = key_len};
char *buf;
int size;
size = scoutfs_key_str_size(NULL, &key, 0);
if (size > 0) {
buf = malloc(size);
if (buf) {
size = scoutfs_key_str_size(buf, &key, size);
if (size > 0)
printf("%s", buf);
free(buf);
}
for (i = 0; i <= U8_MAX; i++) {
ret = snprintf(scoutfs_unknown_u8_strings[i], U8_STR_MAX,
"u%u", i);
assert(ret > 0 && ret < U8_STR_MAX);
}
}
/* ------ copied code follows --------- */
#define snprintf_null(buf, size, fmt, args...) \
(snprintf((buf), (size), fmt, ##args) + 1)
/*
* Store a formatted string representing the key in the buffer. The key
* must be at least min_len to store the data needed by the format at
* all. fmt_len is the length of data that's used by the format. These
* are different because we have badly designed keys with variable
* length data that isn't described by the key. It's assumed from the
* length of the key. Take dirents -- they need to at least have a
* dirent struct, but the name length is the rest of the key.
*
* (XXX And this goes horribly wrong when we pad out dirent keys to max
* len to increment at high precision. We'll never see these items used
* by real fs code, but temporary keys and range endpoints can be full
* precision and we can try and print them and get very confused. We
* need to rev the format to include explicit lengths.)
*
* If the format doesn't cover the entire key then we append more
* formatting to represent the trailing bytes: runs of zeros compresesd
* to _ and then hex output of non-zero bytes.
*/
static int snprintf_key(char *buf, size_t size, struct scoutfs_key_buf *key,
unsigned min_len, unsigned fmt_len,
const char *fmt, ...)
{
va_list args;
char *data;
char *end;
int left;
int part;
int ret;
int nr;
if (key->key_len < min_len)
return snprintf_null(buf, size, "[trunc len %u < min %u]",
key->key_len, min_len);
if (fmt_len == 0)
fmt_len = min_len;
va_start(args, fmt);
ret = vsnprintf(buf, size, fmt, args);
va_end(args);
/* next formatting overwrites null */
if (buf) {
buf += ret;
size -= min_t(int, size, ret);
}
data = key->data + fmt_len;
left = key->key_len - fmt_len;
while (left && (!buf || size > 1)) {
/* compress runs of zero bytes to _ */
end = memchr_inv(data, 0, left);
nr = end ? end - data : left;
if (nr) {
if (buf) {
*(buf++) = '_';
size--;
}
ret++;
data += nr;
left -= nr;
continue;
}
/*
* hex print non-zero bytes. %ph is limited to 64 bytes
* and is buggy in that it still tries to print to buf
* past size. (so buf = null, size = 0 crashes instead
* of printing the length of the formatted string.)
*/
end = memchr(data, 0, left);
nr = end ? end - data : left;
nr = min(nr, 64);
if (buf)
part = snprintf_phN(buf, size, nr, data);
else
part = nr * 2;
if (buf) {
buf += part;
size -= min_t(int, size, part);
}
ret += part;
data += nr;
left -= nr;
}
/* always store and include null */
if (buf)
*buf = '\0';
return ret + 1;
}
typedef int (*key_printer_t)(char *buf, struct scoutfs_key_buf *key,
size_t size);
static int pr_ino_idx(char *buf, struct scoutfs_key_buf *key, size_t size)
{
static char *type_strings[] = {
[SCOUTFS_INODE_INDEX_META_SEQ_TYPE] = "msq",
[SCOUTFS_INODE_INDEX_DATA_SEQ_TYPE] = "dsq",
};
struct scoutfs_inode_index_key *ikey = key->data;
return snprintf_key(buf, size, key,
sizeof(struct scoutfs_inode_index_key), 0,
"iin.%s.%llu.%u.%llu",
type_strings[ikey->type], be64_to_cpu(ikey->major),
be32_to_cpu(ikey->minor), be64_to_cpu(ikey->ino));
}
static int pr_free_bits(char *buf, struct scoutfs_key_buf *key, size_t size)
{
static char *type_strings[] = {
[SCOUTFS_FREE_BITS_SEGNO_TYPE] = "fsg",
[SCOUTFS_FREE_BITS_BLKNO_TYPE] = "fbk",
};
struct scoutfs_free_bits_key *frk = key->data;
return snprintf_key(buf, size, key,
sizeof(struct scoutfs_block_mapping_key), 0,
"nod.%llu.%s.%llu",
be64_to_cpu(frk->node_id),
type_strings[frk->type],
be64_to_cpu(frk->base));
}
static int pr_orphan(char *buf, struct scoutfs_key_buf *key, size_t size)
{
struct scoutfs_orphan_key *okey = key->data;
return snprintf_key(buf, size, key,
sizeof(struct scoutfs_orphan_key), 0,
"nod.%llu.orp.%llu",
be64_to_cpu(okey->node_id),
be64_to_cpu(okey->ino));
}
static int pr_inode(char *buf, struct scoutfs_key_buf *key, size_t size)
{
struct scoutfs_inode_key *ikey = key->data;
return snprintf_key(buf, size, key,
sizeof(struct scoutfs_inode_key), 0,
"fs.%llu.ino",
be64_to_cpu(ikey->ino));
}
static int pr_xattr(char *buf, struct scoutfs_key_buf *key, size_t size)
{
struct scoutfs_xattr_key *xkey = key->data;
return snprintf_key(buf, size, key,
sizeof(struct scoutfs_xattr_key), key->key_len,
"fs.%llu.xat.%08x.%llu.%u",
be64_to_cpu(xkey->ino),
be32_to_cpu(xkey->name_hash),
be64_to_cpu(xkey->id), xkey->part);
}
static int pr_dirent(char *buf, struct scoutfs_key_buf *key, size_t size)
{
struct scoutfs_dirent_key *dkey = key->data;
char *which = dkey->type == SCOUTFS_DIRENT_TYPE ? "dnt" :
dkey->type == SCOUTFS_READDIR_TYPE ? "rdr" :
dkey->type == SCOUTFS_LINK_BACKREF_TYPE ? "lbr" :
"unk";
return snprintf_key(buf, size, key,
sizeof(struct scoutfs_dirent_key), key->key_len,
"fs.%llu.%s.%llu.%llu",
be64_to_cpu(dkey->ino), which,
be64_to_cpu(dkey->major),
be64_to_cpu(dkey->minor));
}
static int pr_symlink(char *buf, struct scoutfs_key_buf *key, size_t size)
{
struct scoutfs_symlink_key *skey = key->data;
return snprintf_key(buf, size, key,
sizeof(struct scoutfs_symlink_key), 0,
"fs.%llu.sym",
be64_to_cpu(skey->ino));
}
static int pr_block_mapping(char *buf, struct scoutfs_key_buf *key, size_t size)
{
struct scoutfs_block_mapping_key *bmk = key->data;
return snprintf_key(buf, size, key,
sizeof(struct scoutfs_block_mapping_key), 0,
"fs.%llu.bmp.%llu",
be64_to_cpu(bmk->ino),
be64_to_cpu(bmk->base));
}
const static key_printer_t key_printers[SCOUTFS_MAX_ZONE][SCOUTFS_MAX_TYPE] = {
[SCOUTFS_INODE_INDEX_ZONE][SCOUTFS_INODE_INDEX_META_SEQ_TYPE] =
pr_ino_idx,
[SCOUTFS_INODE_INDEX_ZONE][SCOUTFS_INODE_INDEX_DATA_SEQ_TYPE] =
pr_ino_idx,
[SCOUTFS_NODE_ZONE][SCOUTFS_FREE_BITS_SEGNO_TYPE] = pr_free_bits,
[SCOUTFS_NODE_ZONE][SCOUTFS_FREE_BITS_BLKNO_TYPE] = pr_free_bits,
[SCOUTFS_NODE_ZONE][SCOUTFS_ORPHAN_TYPE] = pr_orphan,
[SCOUTFS_FS_ZONE][SCOUTFS_INODE_TYPE] = pr_inode,
[SCOUTFS_FS_ZONE][SCOUTFS_XATTR_TYPE] = pr_xattr,
[SCOUTFS_FS_ZONE][SCOUTFS_DIRENT_TYPE] = pr_dirent,
[SCOUTFS_FS_ZONE][SCOUTFS_READDIR_TYPE] = pr_dirent,
[SCOUTFS_FS_ZONE][SCOUTFS_LINK_BACKREF_TYPE] = pr_dirent,
[SCOUTFS_FS_ZONE][SCOUTFS_SYMLINK_TYPE] = pr_symlink,
[SCOUTFS_FS_ZONE][SCOUTFS_BLOCK_MAPPING_TYPE] = pr_block_mapping,
};
/*
* Write the null-terminated string that describes the key to the
* buffer. The bytes copied (including the null) is returned. A null
* buffer can be used to find the string size without writing anything.
*
* XXX nonprintable characters in the trace?
*/
static int scoutfs_key_str_size(char *buf, struct scoutfs_key_buf *key,
size_t size)
{
u8 zone;
u8 type;
if (key == NULL || key->data == NULL)
return snprintf_null(buf, size, "[NULL]");
/* always at least zone, some id, and type */
if (key->key_len < (1 + 8 + 1))
return snprintf_null(buf, size, "[trunc len %u]", key->key_len);
zone = *(u8 *)key->data;
/*
* each zone's keys always start with the same fields that let
* us deref any key to get the type. We chose a few representative
* keys from each zone to get the type.
*/
if (zone == SCOUTFS_INODE_INDEX_ZONE) {
struct scoutfs_inode_index_key *ikey = key->data;
type = ikey->type;
} else if (zone == SCOUTFS_NODE_ZONE) {
struct scoutfs_free_bits_key *fbk = key->data;
type = fbk->type;
} else if (zone == SCOUTFS_FS_ZONE) {
struct scoutfs_inode_key *ikey = key->data;
type = ikey->type;
} else {
type = 255;
}
if (zone > SCOUTFS_MAX_ZONE || type > SCOUTFS_MAX_TYPE ||
key_printers[zone][type] == NULL) {
return snprintf_null(buf, size, "[unk zone %u type %u]",
zone, type);
}
return key_printers[zone][type](buf, key, size);
}

View File

@@ -1,6 +1,172 @@
#ifndef _KEY_H_
#define _KEY_H_
#ifndef _SCOUTFS_KEY_H_
#define _SCOUTFS_KEY_H_
void print_key(void *key_data, unsigned key_len);
#include "sparse.h"
#include "util.h"
#include "format.h"
#include "cmp.h"
#include "endian_swap.h"
extern char *scoutfs_zone_strings[SCOUTFS_MAX_ZONE];
extern char *scoutfs_type_strings[SCOUTFS_MAX_ZONE][SCOUTFS_MAX_TYPE];
#define U8_STR_MAX 5 /* u%3u'\0' */
extern char scoutfs_unknown_u8_strings[U8_MAX][U8_STR_MAX];
static inline char *sk_zone_str(u8 zone)
{
if (zone >= SCOUTFS_MAX_ZONE || scoutfs_zone_strings[zone] == NULL)
return scoutfs_unknown_u8_strings[zone];
return scoutfs_zone_strings[zone];
}
static inline char *sk_type_str(u8 zone, u8 type)
{
if (zone >= SCOUTFS_MAX_ZONE || type >= SCOUTFS_MAX_TYPE ||
scoutfs_type_strings[zone][type] == NULL)
return scoutfs_unknown_u8_strings[type];
return scoutfs_type_strings[zone][type];
}
#define SK_FMT "%s.%llu.%s.%llu.%llu.%u"
/* This does not support null keys */
#define SK_ARG(key) sk_zone_str((key)->sk_zone), \
le64_to_cpu((key)->_sk_first), \
sk_type_str((key)->sk_zone, (key)->sk_type), \
le64_to_cpu((key)->_sk_second), \
le64_to_cpu((key)->_sk_third), \
(key)->_sk_fourth
static inline void scoutfs_key_set_zeros(struct scoutfs_key *key)
{
key->sk_zone = 0;
key->_sk_first = 0;
key->sk_type = 0;
key->_sk_second = 0;
key->_sk_third = 0;
key->_sk_fourth = 0;
}
static inline void scoutfs_key_copy_or_zeros(struct scoutfs_key *dst,
struct scoutfs_key *src)
{
if (src)
*dst = *src;
else
scoutfs_key_set_zeros(dst);
}
static inline void scoutfs_key_set_ones(struct scoutfs_key *key)
{
key->sk_zone = U8_MAX;
key->_sk_first = cpu_to_le64(U64_MAX);
key->sk_type = U8_MAX;
key->_sk_second = cpu_to_le64(U64_MAX);
key->_sk_third = cpu_to_le64(U64_MAX);
key->_sk_fourth = U8_MAX;
}
/*
* Return a -1/0/1 comparison of keys.
*
* It turns out that these ternary chains are consistently cheaper than
* other alternatives across keys that first differ in any of the
* values. Say maybe 20% faster than memcmp.
*/
static inline int scoutfs_key_compare(struct scoutfs_key *a,
struct scoutfs_key *b)
{
return scoutfs_cmp(a->sk_zone, b->sk_zone) ?:
scoutfs_cmp(le64_to_cpu(a->_sk_first), le64_to_cpu(b->_sk_first)) ?:
scoutfs_cmp(a->sk_type, b->sk_type) ?:
scoutfs_cmp(le64_to_cpu(a->_sk_second), le64_to_cpu(b->_sk_second)) ?:
scoutfs_cmp(le64_to_cpu(a->_sk_third), le64_to_cpu(b->_sk_third)) ?:
scoutfs_cmp(a->_sk_fourth, b->_sk_fourth);
}
/*
* Compare ranges of keys where overlapping is equality. Returns:
* -1: a_end < b_start
* 1: a_start > b_end
* else 0: ranges overlap
*/
static inline int scoutfs_key_compare_ranges(struct scoutfs_key *a_start,
struct scoutfs_key *a_end,
struct scoutfs_key *b_start,
struct scoutfs_key *b_end)
{
return scoutfs_key_compare(a_end, b_start) < 0 ? -1 :
scoutfs_key_compare(a_start, b_end) > 0 ? 1 :
0;
}
static inline void scoutfs_key_inc(struct scoutfs_key *key)
{
if (++key->_sk_fourth != 0)
return;
le64_add_cpu(&key->_sk_third, 1);
if (key->_sk_third != 0)
return;
le64_add_cpu(&key->_sk_second, 1);
if (key->_sk_second != 0)
return;
if (++key->sk_type != 0)
return;
le64_add_cpu(&key->_sk_first, 1);
if (key->_sk_first != 0)
return;
key->sk_zone++;
}
static inline void scoutfs_key_dec(struct scoutfs_key *key)
{
if (--key->_sk_fourth != U8_MAX)
return;
le64_add_cpu(&key->_sk_third, -1);
if (key->_sk_third != cpu_to_le64(U64_MAX))
return;
le64_add_cpu(&key->_sk_second, -1);
if (key->_sk_second != cpu_to_le64(U64_MAX))
return;
if (--key->sk_type != U8_MAX)
return;
le64_add_cpu(&key->_sk_first, -1);
if (key->_sk_first != cpu_to_le64(U64_MAX))
return;
key->sk_zone--;
}
static inline void scoutfs_key_to_be(struct scoutfs_key_be *be,
struct scoutfs_key *key)
{
be->sk_zone = key->sk_zone;
be->_sk_first = le64_to_be64(key->_sk_first);
be->sk_type = key->sk_type;
be->_sk_second = le64_to_be64(key->_sk_second);
be->_sk_third = le64_to_be64(key->_sk_third);
be->_sk_fourth = key->_sk_fourth;
}
static inline void scoutfs_key_from_be(struct scoutfs_key *key,
struct scoutfs_key_be *be)
{
key->sk_zone = be->sk_zone;
key->_sk_first = be64_to_le64(be->_sk_first);
key->sk_type = be->sk_type;
key->_sk_second = be64_to_le64(be->_sk_second);
key->_sk_third = be64_to_le64(be->_sk_third);
key->_sk_fourth = be->_sk_fourth;
}
#endif

View File

@@ -17,6 +17,7 @@
#include "crc.h"
#include "rand.h"
#include "dev.h"
#include "key.h"
static int write_raw_block(int fd, u64 blkno, void *blk)
{
@@ -107,10 +108,8 @@ static u64 calc_btree_ring_blocks(u64 total_segs)
sizeof(struct scoutfs_alloc_region_btree_val));
blocks += calc_btree_blocks(total_segs,
sizeof(struct scoutfs_manifest_btree_key) +
SCOUTFS_MAX_KEY_SIZE,
sizeof(struct scoutfs_manifest_btree_val) +
SCOUTFS_MAX_KEY_SIZE);
sizeof(struct scoutfs_manifest_btree_key),
sizeof(struct scoutfs_manifest_btree_val));
return round_up(blocks * 4, SCOUTFS_SEGMENT_BLOCKS);
}
@@ -153,8 +152,8 @@ static char *size_str(u64 nr, unsigned size)
static int write_new_fs(char *path, int fd)
{
struct scoutfs_super_block *super;
struct scoutfs_inode_key *ikey;
struct scoutfs_inode_index_key *idx_key;
struct scoutfs_key *ino_key;
struct scoutfs_key *idx_key;
struct scoutfs_inode *inode;
struct scoutfs_segment_block *sblk;
struct scoutfs_manifest_btree_key *mkey;
@@ -162,6 +161,7 @@ static int write_new_fs(char *path, int fd)
struct scoutfs_btree_block *bt;
struct scoutfs_btree_item *btitem;
struct scoutfs_segment_item *item;
struct scoutfs_key key;
__le32 *prev_link;
struct timeval tv;
char uuid_str[37];
@@ -245,34 +245,29 @@ static int write_new_fs(char *path, int fd)
bt->nr_items = cpu_to_le16(1);
/* btree item allocated from the back of the block */
ikey = (void *)bt + SCOUTFS_BLOCK_SIZE - sizeof(*ikey);
mval = (void *)ikey - sizeof(*mval);
idx_key = (void *)mval - sizeof(*idx_key);
mkey = (void *)idx_key - sizeof(*mkey);
mval = (void *)bt + SCOUTFS_BLOCK_SIZE - sizeof(*mval);
ino_key = &mval->last_key;
mkey = (void *)mval - sizeof(*mkey);
btitem = (void *)mkey - sizeof(*btitem);
bt->item_hdrs[0].off = cpu_to_le16((long)btitem - (long)bt);
bt->free_end = bt->item_hdrs[0].off;
btitem->key_len = cpu_to_le16(sizeof(struct scoutfs_manifest_btree_key) +
sizeof(struct scoutfs_inode_index_key));
btitem->val_len = cpu_to_le16(sizeof(struct scoutfs_manifest_btree_val) +
sizeof(struct scoutfs_inode_key));
btitem->key_len = cpu_to_le16(sizeof(*mkey));
btitem->val_len = cpu_to_le16(sizeof(*mval));
mkey->level = 1;
idx_key->zone = SCOUTFS_INODE_INDEX_ZONE;
idx_key->type = SCOUTFS_INODE_INDEX_META_SEQ_TYPE;
idx_key->major = 0;
idx_key->minor = 0;
idx_key->ino = cpu_to_be64(SCOUTFS_ROOT_INO);
mkey->seq = cpu_to_be64(1);
memset(&key, 0, sizeof(key));
key.sk_zone = SCOUTFS_INODE_INDEX_ZONE;
key.sk_type = SCOUTFS_INODE_INDEX_META_SEQ_TYPE;
key.skii_ino = cpu_to_le64(SCOUTFS_ROOT_INO);
scoutfs_key_to_be(&mkey->first_key, &key);
mval->segno = cpu_to_le64(first_segno);
mval->seq = cpu_to_le64(1);
mval->first_key_len = cpu_to_le16(sizeof(struct scoutfs_inode_index_key));
mval->last_key_len = cpu_to_le16(sizeof(struct scoutfs_inode_key));
ikey->zone = SCOUTFS_FS_ZONE;
ikey->ino = cpu_to_be64(SCOUTFS_ROOT_INO);
ikey->type = SCOUTFS_INODE_TYPE;
ino_key->sk_zone = SCOUTFS_FS_ZONE;
ino_key->ski_ino = cpu_to_le64(SCOUTFS_ROOT_INO);
ino_key->sk_type = SCOUTFS_INODE_TYPE;
bt->crc = cpu_to_le32(crc_btree_block(bt));
@@ -294,35 +289,31 @@ static int write_new_fs(char *path, int fd)
*prev_link = cpu_to_le32((long)item -(long)sblk);
prev_link = &item->skip_links[0];
item->key_len = cpu_to_le16(sizeof(*idx_key));
item->val_len = 0;
item->nr_links = 1;
le32_add_cpu(&sblk->nr_items, 1);
idx_key = (void *)&item->skip_links[1];
idx_key->zone = SCOUTFS_INODE_INDEX_ZONE;
idx_key->type = SCOUTFS_INODE_INDEX_META_SEQ_TYPE;
idx_key->ino = cpu_to_be64(SCOUTFS_ROOT_INO);
idx_key->major = 0;
idx_key->minor = 0;
idx_key = &item->key;
idx_key->sk_zone = SCOUTFS_INODE_INDEX_ZONE;
idx_key->sk_type = SCOUTFS_INODE_INDEX_META_SEQ_TYPE;
idx_key->skii_ino = cpu_to_le64(SCOUTFS_ROOT_INO);
item = (void *)(idx_key + 1);
item = (void *)&item->skip_links[1];
*prev_link = cpu_to_le32((long)item -(long)sblk);
prev_link = &item->skip_links[0];
sblk->last_item_off = cpu_to_le32((long)item - (long)sblk);
ikey = (void *)&item->skip_links[1];
inode = (void *)ikey + sizeof(struct scoutfs_inode_key);
ino_key = (void *)&item->key;
inode = (void *)&item->skip_links[1];
item->key_len = cpu_to_le16(sizeof(struct scoutfs_inode_key));
item->val_len = cpu_to_le16(sizeof(struct scoutfs_inode));
item->nr_links = 1;
le32_add_cpu(&sblk->nr_items, 1);
ikey->zone = SCOUTFS_FS_ZONE;
ikey->ino = cpu_to_be64(SCOUTFS_ROOT_INO);
ikey->type = SCOUTFS_INODE_TYPE;
ino_key->sk_zone = SCOUTFS_FS_ZONE;
ino_key->ski_ino = cpu_to_le64(SCOUTFS_ROOT_INO);
ino_key->sk_type = SCOUTFS_INODE_TYPE;
inode->next_readdir_pos = cpu_to_le64(2);
inode->nlink = cpu_to_le32(SCOUTFS_DIRENT_FIRST_POS);

View File

@@ -74,9 +74,8 @@ static void print_block_header(struct scoutfs_block_header *hdr)
le64_to_cpu(hdr->seq), le64_to_cpu(hdr->blkno));
}
static void print_inode(void *key, int key_len, void *val, int val_len)
static void print_inode(struct scoutfs_key *key, void *val, int val_len)
{
struct scoutfs_inode_key *ikey = key;
struct scoutfs_inode *inode = val;
printf(" inode: ino %llu size %llu nlink %u\n"
@@ -84,7 +83,7 @@ static void print_inode(void *key, int key_len, void *val, int val_len)
" next_readdir_pos %llu meta_seq %llu data_seq %llu data_version %llu\n"
" atime %llu.%08u ctime %llu.%08u\n"
" mtime %llu.%08u\n",
be64_to_cpu(ikey->ino),
le64_to_cpu(key->ski_ino),
le64_to_cpu(inode->size),
le32_to_cpu(inode->nlink), le32_to_cpu(inode->uid),
le32_to_cpu(inode->gid), le32_to_cpu(inode->mode),
@@ -102,11 +101,9 @@ static void print_inode(void *key, int key_len, void *val, int val_len)
le32_to_cpu(inode->mtime.nsec));
}
static void print_orphan(void *key, int key_len, void *val, int val_len)
static void print_orphan(struct scoutfs_key *key, void *val, int val_len)
{
struct scoutfs_orphan_key *okey = key;
printf(" orphan: ino %llu\n", be64_to_cpu(okey->ino));
printf(" orphan: ino %llu\n", le64_to_cpu(key->sko_ino));
}
static u8 *global_printable_name(u8 *name, int name_len)
@@ -122,38 +119,35 @@ static u8 *global_printable_name(u8 *name, int name_len)
return name_buf;
}
static void print_xattr(void *key, int key_len, void *val, int val_len)
static void print_xattr(struct scoutfs_key *key, void *val, int val_len)
{
struct scoutfs_xattr_key *xkey = key;
struct scoutfs_xattr *xat = val;
printf(" xattr: ino %llu name_hash %08x id %llu part %u\n",
be64_to_cpu(xkey->ino), be32_to_cpu(xkey->name_hash),
be64_to_cpu(xkey->id), xkey->part);
le64_to_cpu(key->skx_ino), (u32)le64_to_cpu(key->skx_name_hash),
le64_to_cpu(key->skx_id), key->skx_part);
if (xkey->part == 0)
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));
}
static void print_dirent(void *key, int key_len, void *val, int val_len)
static void print_dirent(struct scoutfs_key *key, void *val, int val_len)
{
struct scoutfs_dirent_key *dkey = key;
struct scoutfs_dirent *dent = val;
unsigned int name_len = val_len - sizeof(*dent);
u8 *name = global_printable_name(dent->name, name_len);
printf(" dirent: dir %llu hash %016llx pos %llu type %u ino %llu\n"
" name %s\n",
be64_to_cpu(dkey->ino), le64_to_cpu(dent->hash),
le64_to_cpu(key->skd_ino), le64_to_cpu(dent->hash),
le64_to_cpu(dent->pos), dent->type, le64_to_cpu(dent->ino),
name);
}
static void print_symlink(void *key, int key_len, void *val, int val_len)
static void print_symlink(struct scoutfs_key *key, void *val, int val_len)
{
struct scoutfs_symlink_key *skey = key;
u8 *frag = val;
u8 *name;
@@ -162,32 +156,30 @@ static void print_symlink(void *key, int key_len, void *val, int val_len)
val_len--;
name = global_printable_name(frag, val_len);
printf(" symlink: ino %llu nr %u\n"
printf(" symlink: ino %llu nr %llu\n"
" target %s\n",
be64_to_cpu(skey->ino), skey->nr, name);
le64_to_cpu(key->sks_ino), le64_to_cpu(key->sks_nr), name);
}
/*
* XXX not decoding the bytes yet
*/
static void print_block_mapping(void *key, int key_len, void *val, int val_len)
static void print_block_mapping(struct scoutfs_key *key, void *val, int val_len)
{
struct scoutfs_block_mapping_key *bmk = key;
u64 blk_off = be64_to_cpu(bmk->base) << SCOUTFS_BLOCK_MAPPING_SHIFT;
u64 blk_off = le64_to_cpu(key->skm_base) << SCOUTFS_BLOCK_MAPPING_SHIFT;
u8 nr = *((u8 *)val) & 63;
printf(" block mapping: ino %llu blk_off %llu blocks %u\n",
be64_to_cpu(bmk->ino), blk_off, nr);
le64_to_cpu(key->skm_ino), blk_off, nr);
}
static void print_free_bits(void *key, int key_len, void *val, int val_len)
static void print_free_bits(struct scoutfs_key *key, void *val, int val_len)
{
struct scoutfs_free_bits_key *fbk = key;
struct scoutfs_free_bits *frb = val;
int i;
printf(" node_id %llx base %llu\n",
be64_to_cpu(fbk->node_id), be64_to_cpu(fbk->base));
le64_to_cpu(key->skf_node_id), le64_to_cpu(key->skf_base));
printf(" bits:");
for (i = 0; i < array_size(frb->bits); i++)
@@ -195,16 +187,13 @@ static void print_free_bits(void *key, int key_len, void *val, int val_len)
printf("\n");
}
static void print_inode_index(void *key, int key_len, void *val, int val_len)
static void print_inode_index(struct scoutfs_key *key, void *val, int val_len)
{
struct scoutfs_inode_index_key *ikey = key;
printf(" index: major %llu minor %u ino %llu\n",
be64_to_cpu(ikey->major), be32_to_cpu(ikey->minor),
be64_to_cpu(ikey->ino));
printf(" index: major %llu ino %llu\n",
le64_to_cpu(key->skii_major), le64_to_cpu(key->skii_ino));
}
typedef void (*print_func_t)(void *key, int key_len, 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)
{
@@ -237,59 +226,28 @@ static print_func_t find_printer(u8 zone, u8 type)
return NULL;
}
static void find_zone_type(void *key, u8 *zone, u8 *type)
{
struct scoutfs_inode_index_key *idx_key = key;
struct scoutfs_inode_key *ikey = key;
struct scoutfs_orphan_key *okey = key;
*zone = *(u8 *)key;
switch (*zone) {
case SCOUTFS_INODE_INDEX_ZONE:
*type = idx_key->type;
break;
case SCOUTFS_NODE_ZONE:
*type = okey->type;
break;
case SCOUTFS_FS_ZONE:
*type = ikey->type;
break;
default:
*type = 0;
}
}
static void print_item(struct scoutfs_segment_block *sblk,
struct scoutfs_segment_item *item, u32 which, u32 off)
{
print_func_t printer;
void *key;
void *val;
u8 type;
u8 zone;
int i;
key = (char *)&item->skip_links[item->nr_links];
val = (char *)key + le16_to_cpu(item->key_len);
val = (char *)&item->skip_links[item->nr_links];
find_zone_type(key, &zone, &type);
printer = find_printer(zone, type);
printer = find_printer(item->key.sk_zone, item->key.sk_type);
printf(" [%u]: off %u key_len %u val_len %u nr_links %u flags %x%s\n",
which, off, le16_to_cpu(item->key_len),
le16_to_cpu(item->val_len), item->nr_links,
printf(" [%u]: key "SK_FMT" off %u val_len %u nr_links %u flags %x%s\n",
which, SK_ARG(&item->key), off, le16_to_cpu(item->val_len),
item->nr_links,
item->flags, printer ? "" : " (unrecognized zone+type)");
printf(" links:");
for (i = 0; i < item->nr_links; i++)
printf(" %u", le32_to_cpu(item->skip_links[i]));
printf("\n key: ");
print_key(key, le16_to_cpu(item->key_len));
printf("\n");
if (printer)
printer(key, le16_to_cpu(item->key_len),
val, le16_to_cpu(item->val_len));
printer(&item->key, val, le16_to_cpu(item->val_len));
}
static void print_segment_block(struct scoutfs_segment_block *sblk)
@@ -341,51 +299,22 @@ static int print_manifest_entry(void *key, unsigned key_len, void *val,
{
struct scoutfs_manifest_btree_key *mkey = key;
struct scoutfs_manifest_btree_val *mval = val;
struct scoutfs_key first;
unsigned long *seg_map = arg;
unsigned first_len;
unsigned last_len;
void *first;
void *last;
__be64 seq;
/* parent items only have the key */
if (val == NULL) {
if (mkey->level == 0) {
memcpy(&seq, mkey->bkey, sizeof(seq));
printf(" level %u seq %llu\n",
mkey->level, be64_to_cpu(seq));
} else {
printf(" level %u first ", mkey->level);
print_key(mkey->bkey, key_len - sizeof(mkey->level));
printf("\n");
}
return 0;
scoutfs_key_from_be(&first, &mkey->first_key);
printf(" level %u first "SK_FMT" seq %llu\n",
mkey->level, SK_ARG(&first), be64_to_cpu(mkey->seq));
/* only items in leaf blocks have values */
if (val) {
printf(" segno %llu last "SK_FMT"\n",
le64_to_cpu(mval->segno), SK_ARG(&mval->last_key));
set_bit(seg_map, le64_to_cpu(mval->segno));
}
/* leaf items print the whole entry */
first_len = le16_to_cpu(mval->first_key_len);
last_len = le16_to_cpu(mval->last_key_len);
if (mkey->level == 0) {
first = mval->keys;
last = mval->keys + first_len;
} else {
first = mkey->bkey;
last = mval->keys;
}
printf(" level %u segno %llu seq %llu first_len %u last_len %u\n",
mkey->level, le64_to_cpu(mval->segno), le64_to_cpu(mval->seq),
first_len, last_len);
printf(" first ");
print_key(first, first_len);
printf("\n last ");
print_key(last, last_len);
printf("\n");
set_bit(seg_map, le64_to_cpu(mval->segno));
return 0;
}
@@ -514,7 +443,7 @@ static int print_btree(int fd, struct scoutfs_super_block *super, char *which,
static void print_super_block(struct scoutfs_super_block *super, u64 blkno)
{
char uuid_str[37];
__le64 *counts;
u64 count;
int i;
uuid_unparse(super->uuid, uuid_str);
@@ -553,10 +482,10 @@ static void print_super_block(struct scoutfs_super_block *super, u64 blkno)
le16_to_cpu(super->manifest.root.migration_key_len));
printf(" level_counts:");
counts = super->manifest.level_counts;
for (i = 0; i < SCOUTFS_MANIFEST_MAX_LEVEL; i++) {
if (le64_to_cpu(counts[i]))
printf(" %u: %llu", i, le64_to_cpu(counts[i]));
count = le64_to_cpu(super->manifest.level_counts[i]);
if (count)
printf(" %u: %llu", i, count);
}
printf("\n");
}

View File

@@ -11,6 +11,7 @@
#include "sparse.h"
#include "util.h"
#include "format.h"
#include "ioctl.h"
#include "cmd.h"