diff --git a/utils/src/format.h b/utils/src/format.h index 66222e8e..1e004fbe 100644 --- a/utils/src/format.h +++ b/utils/src/format.h @@ -9,6 +9,8 @@ #define SCOUTFS_BLOCK_MAGIC_BTREE 0xe597f96d #define SCOUTFS_BLOCK_MAGIC_BLOOM 0x31995604 #define SCOUTFS_BLOCK_MAGIC_RADIX 0xebeb5e65 +#define SCOUTFS_BLOCK_MAGIC_SRCH_BLOCK 0x897e4a7d +#define SCOUTFS_BLOCK_MAGIC_SRCH_PARENT 0xb23a2a05 /* * The super block, quorum block, and file data allocation granularity @@ -275,6 +277,93 @@ struct scoutfs_mounted_client_btree_val { #define SCOUTFS_MOUNTED_CLIENT_VOTER (1 << 0) +/* + * srch files are a contiguous run of blocks with compressed entries + * described by a dense parent radix. The files can be stored in + * log_tree items when the files contain unsorted entries written by + * mounts during their transactions. Sorted files of increasing size + * are kept in a btree off the super for searching and further + * compacting. + */ +struct scoutfs_srch_entry { + __le64 hash; + __le64 ino; + __le64 id; +} __packed; + +#define SCOUTFS_SRCH_ENTRY_MAX_BYTES (2 + (sizeof(__u64) * 3)) + +struct scoutfs_srch_ref { + __le64 blkno; + __le64 seq; +} __packed; + +struct scoutfs_srch_file { + struct scoutfs_srch_entry first; + struct scoutfs_srch_entry last; + __le64 blocks; + __le64 entries; + struct scoutfs_srch_ref ref; + __u8 height; +} __packed; + +struct scoutfs_srch_parent { + struct scoutfs_block_header hdr; + struct scoutfs_srch_ref refs[0]; +} __packed; + +#define SCOUTFS_SRCH_PARENT_REFS \ + ((SCOUTFS_BLOCK_LG_SIZE - \ + offsetof(struct scoutfs_srch_parent, refs)) / \ + sizeof(struct scoutfs_srch_ref)) + +struct scoutfs_srch_block { + struct scoutfs_block_header hdr; + struct scoutfs_srch_entry first; + struct scoutfs_srch_entry last; + struct scoutfs_srch_entry tail; + __le32 entry_nr; + __le32 entry_bytes; + __u8 entries[0]; +} __packed; + +/* + * Decoding loads final small deltas with full __u64 loads. Rather than + * check the size before each load we stop coding entries past the point + * where a full size entry could overflow the block. A final entry can + * start at this byte count and consume the rest of the block, though + * its unlikely. + */ +#define SCOUTFS_SRCH_BLOCK_SAFE_BYTES \ + (SCOUTFS_BLOCK_LG_SIZE - sizeof(struct scoutfs_srch_block) - \ + SCOUTFS_SRCH_ENTRY_MAX_BYTES) + +#define SCOUTFS_SRCH_LOG_BLOCK_LIMIT (1024 * 1024 / SCOUTFS_BLOCK_LG_SIZE) +#define SCOUTFS_SRCH_COMPACT_ORDER 3 +#define SCOUTFS_SRCH_COMPACT_NR (1 << SCOUTFS_SRCH_COMPACT_ORDER) + +struct scoutfs_srch_compact_input { + struct scoutfs_radix_root meta_avail; + struct scoutfs_radix_root meta_freed; + __le64 id; + __u8 nr; + __u8 flags; + struct scoutfs_srch_file sfl[SCOUTFS_SRCH_COMPACT_NR]; +} __packed; + +struct scoutfs_srch_compact_result { + struct scoutfs_radix_root meta_avail; + struct scoutfs_radix_root meta_freed; + __le64 id; + __u8 flags; + struct scoutfs_srch_file sfl; +} __packed; + +/* files are insorted logs */ +#define SCOUTFS_SRCH_COMPACT_FLAG_LOG (1 << 0) +/* compaction failed, release inputs */ +#define SCOUTFS_SRCH_COMPACT_FLAG_ERROR (1 << 1) + /* * XXX I imagine we should rename these now that they've evolved to track * all the btrees that clients use during a transaction. It's not just @@ -287,6 +376,7 @@ struct scoutfs_log_trees { struct scoutfs_btree_ref bloom_ref; struct scoutfs_radix_root data_avail; struct scoutfs_radix_root data_freed; + struct scoutfs_srch_file srch_file; __le64 rid; __le64 nr; } __packed; @@ -298,6 +388,7 @@ struct scoutfs_log_trees_val { struct scoutfs_btree_ref bloom_ref; struct scoutfs_radix_root data_avail; struct scoutfs_radix_root data_freed; + struct scoutfs_srch_file srch_file; } __packed; struct scoutfs_log_item_value { @@ -348,6 +439,7 @@ struct scoutfs_bloom_block { #define SCOUTFS_LOCK_CLIENTS_ZONE 7 #define SCOUTFS_TRANS_SEQ_ZONE 8 #define SCOUTFS_MOUNTED_CLIENT_ZONE 9 +#define SCOUTFS_SRCH_ZONE 10 /* inode index zone */ #define SCOUTFS_INODE_INDEX_META_SEQ_TYPE 1 @@ -372,6 +464,11 @@ struct scoutfs_bloom_block { /* lock zone, only ever found in lock ranges, never in persistent items */ #define SCOUTFS_RENAME_TYPE 1 +/* srch zone, only in server btrees */ +#define SCOUTFS_SRCH_LOG_TYPE 1 +#define SCOUTFS_SRCH_BLOCKS_TYPE 2 +#define SCOUTFS_SRCH_BUSY_TYPE 3 + /* * The extents that map blocks in a fixed-size logical region of a file * are packed and stored in item values. The packed extents are @@ -496,6 +593,7 @@ struct scoutfs_super_block { struct scoutfs_btree_root lock_clients; struct scoutfs_btree_root trans_seqs; struct scoutfs_btree_root mounted_clients; + struct scoutfs_btree_root srch_root; } __packed; #define SCOUTFS_ROOT_INO 1 @@ -688,6 +786,8 @@ enum { SCOUTFS_NET_CMD_STATFS, SCOUTFS_NET_CMD_LOCK, SCOUTFS_NET_CMD_LOCK_RECOVER, + SCOUTFS_NET_CMD_SRCH_GET_COMPACT, + SCOUTFS_NET_CMD_SRCH_COMMIT_COMPACT, SCOUTFS_NET_CMD_FAREWELL, SCOUTFS_NET_CMD_UNKNOWN, }; @@ -734,6 +834,7 @@ struct scoutfs_net_statfs { struct scoutfs_net_roots { struct scoutfs_btree_root fs_root; struct scoutfs_btree_root logs_root; + struct scoutfs_btree_root srch_root; } __packed; struct scoutfs_net_lock { diff --git a/utils/src/ioctl.h b/utils/src/ioctl.h index 4b635f88..2f861a4d 100644 --- a/utils/src/ioctl.h +++ b/utils/src/ioctl.h @@ -296,34 +296,57 @@ struct scoutfs_ioctl_listxattr_hidden { /* * Return the inode numbers of inodes which might contain the given - * named xattr. The inode may not have a set xattr with that name, the - * caller must check the returned inodes to see if they match. + * xattr. The inode may not have a set xattr with that name, the caller + * must check the returned inodes to see if they match. * * @next_ino: The next inode number that could be returned. Initialized * to 0 when first searching and set to one past the last inode number * returned to continue searching. - * @name_ptr: The address of the name of the xattr to search for. It does - * not need to be null terminated. - * @inodes_ptr: The address of the array of uint64_t inode numbers in which - * to store inode numbers that may contain the xattr. EFAULT may be returned - * if this address is not naturally aligned. - * @name_bytes: The number of non-null bytes found in the name at name_ptr. + * @last_ino: The last inode number that could be returned. U64_MAX to + * find all inodes. + * @name_ptr: The address of the name of the xattr to search for. It is + * not null terminated. + * @inodes_ptr: The address of the array of uint64_t inode numbers in + * which to store inode numbers that may contain the xattr. EFAULT may + * be returned if this address is not naturally aligned. + * @output_flags: Set as success is returned. If an error is returned + * then this field is undefined and should not be read. * @nr_inodes: The number of elements in the array found at inodes_ptr. + * @name_bytes: The number of non-null bytes found in the name at + * name_ptr. * * This requires the CAP_SYS_ADMIN capability and will return -EPERM if * it's not granted. + * + * The number of inode numbers stored in the inodes_ptr array is + * returned. If nr_inodes is 0 or last_ino is less than next_ino then 0 + * will be immediately returned. + * + * Partial progress can be returned if an error is hit or if nr_inodes + * was larger than the internal limit on the number of inodes returned + * in a search pass. The _END output flag is set if all the results + * including last_ino were searched in this pass. + * + * It's valuable to provide a large inodes array so that all the results + * can be found in one search pass and _END can be set. There are + * significant constant costs for performing each search pass. */ -struct scoutfs_ioctl_find_xattrs { +struct scoutfs_ioctl_search_xattrs { __u64 next_ino; + __u64 last_ino; __u64 name_ptr; __u64 inodes_ptr; + __u64 output_flags; + __u64 nr_inodes; __u16 name_bytes; - __u16 nr_inodes; - __u8 _pad[4]; + __u8 _pad[6]; }; -#define SCOUTFS_IOC_FIND_XATTRS _IOR(SCOUTFS_IOCTL_MAGIC, 9, \ - struct scoutfs_ioctl_find_xattrs) +/* set in output_flags if returned inodes reached last_ino */ +#define SCOUTFS_SEARCH_XATTRS_OFLAG_END (1ULL << 0) + +#define SCOUTFS_IOC_SEARCH_XATTRS _IOR(SCOUTFS_IOCTL_MAGIC, 9, \ + struct scoutfs_ioctl_search_xattrs) /* * Give the user information about the filesystem. diff --git a/utils/src/print.c b/utils/src/print.c index ecf4e862..74d03c34 100644 --- a/utils/src/print.c +++ b/utils/src/print.c @@ -22,6 +22,7 @@ #include "key.h" #include "radix.h" #include "avl.h" +#include "srch.h" #include "leaf_item_hash.h" static void *read_block(int fd, u64 blkno, int shift) @@ -324,6 +325,19 @@ static int print_logs_item(struct scoutfs_key *key, void *val, (root)->height, le64_to_cpu((root)->next_find_bit), \ RADREF_A(&(root)->ref) +#define SRE_FMT "%016llx.%llu.%llu" +#define SRE_A(sre) \ + le64_to_cpu((sre)->hash), le64_to_cpu((sre)->ino), \ + le64_to_cpu((sre)->id) + +#define SRF_FMT \ + "f "SRE_FMT" l "SRE_FMT" blks %llu ents %llu hei %u blkno %llu seq %016llx" +#define SRF_A(srf) \ + SRE_A(&(srf)->first), SRE_A(&(srf)->last), \ + le64_to_cpu((srf)->blocks), le64_to_cpu((srf)->entries), \ + (srf)->height, le64_to_cpu((srf)->ref.blkno), \ + le64_to_cpu((srf)->ref.seq) + /* same as fs item but with a small header in the value */ static int print_log_trees_item(struct scoutfs_key *key, void *val, unsigned val_len, void *arg) @@ -340,7 +354,8 @@ static int print_log_trees_item(struct scoutfs_key *key, void *val, " item_root: height %u blkno %llu seq %llu\n" " bloom_ref: blkno %llu seq %llu\n" " data_avail: "RADROOT_F"\n" - " data_freed: "RADROOT_F"\n", + " data_freed: "RADROOT_F"\n" + " srch_file: "SRF_FMT"\n", RADROOT_A(<v->meta_avail), RADROOT_A(<v->meta_freed), ltv->item_root.height, @@ -349,7 +364,37 @@ static int print_log_trees_item(struct scoutfs_key *key, void *val, le64_to_cpu(ltv->bloom_ref.blkno), le64_to_cpu(ltv->bloom_ref.seq), RADROOT_A(<v->data_avail), - RADROOT_A(<v->data_freed)); + RADROOT_A(<v->data_freed), + SRF_A(<v->srch_file)); + } + + return 0; +} + +static int print_srch_root_item(struct scoutfs_key *key, void *val, + unsigned val_len, void *arg) +{ + struct scoutfs_srch_file *sfl = val; + struct scoutfs_srch_compact_input *scin = val; + int i; + + printf(" "SK_FMT"\n", SK_ARG(key)); + + /* only items in leaf blocks have values */ + if (val) { + if (key->sk_type == SCOUTFS_SRCH_BUSY_TYPE) { + scin = val; + printf(" compact: nr_in %u in_flags 0x%x\n", + scin->nr, scin->flags); + for (i = 0; i < scin->nr; i++) { + sfl = &scin->sfl[i]; + printf(" [%u] "SRF_FMT"\n", + i, SRF_A(sfl)); + } + } else { + sfl = val; + printf(" "SRF_FMT"\n", SRF_A(sfl)); + } } return 0; @@ -595,6 +640,79 @@ out: return ret; } +static int print_srch_block(int fd, struct scoutfs_srch_ref *ref, int level) +{ + struct scoutfs_srch_parent *srp; + struct scoutfs_srch_block *srb; + struct scoutfs_srch_entry sre; + struct scoutfs_srch_entry prev; + u64 blkno; + int pos; + int ret; + int err; + int i; + + blkno = le64_to_cpu(ref->blkno); + if (blkno == 0) + return 0; + + srp = read_block(fd, blkno, SCOUTFS_BLOCK_LG_SHIFT); + if (!srp) { + ret = -ENOMEM; + goto out; + } + srb = (void *)srp; + + printf("srch %sblock blkno %llu\n", level ? "parent " : "", blkno); + print_block_header(&srp->hdr, SCOUTFS_BLOCK_LG_SIZE); + + for (i = 0; level > 0 && i < SCOUTFS_SRCH_PARENT_REFS; i++) { + if (le64_to_cpu(srp->refs[i].blkno) == 0) + continue; + printf(" [%u]: blkno %llu seq %llu\n", + i, le64_to_cpu(srp->refs[i].blkno), + le64_to_cpu(srp->refs[i].seq)); + } + + ret = 0; + for (i = 0; level > 0 && i < SCOUTFS_SRCH_PARENT_REFS; i++) { + if (le64_to_cpu(srp->refs[i].blkno) == 0) + continue; + err = print_srch_block(fd, &srp->refs[i], level - 1); + if (err < 0 && ret == 0) + ret = err; + } + + if (level > 0) + goto out; + + printf(" first "SRE_FMT" last "SRE_FMT" tail "SRE_FMT"\n" + " entry_nr %u entry_bytes %u\n", + SRE_A(&srb->first), SRE_A(&srb->last), SRE_A(&srb->tail), + le32_to_cpu(srb->entry_nr), le32_to_cpu(srb->entry_bytes)); + + memset(&prev, 0, sizeof(prev)); + pos = 0; + for (i = 0; level == 0 && i < le32_to_cpu(srb->entry_nr); i++) { + if (pos > SCOUTFS_SRCH_BLOCK_SAFE_BYTES) { + ret = EIO; + break; + } + + ret = srch_decode_entry(srb->entries + pos, &sre, &prev); + if (ret < 0) + break; + pos += ret; + prev = sre; + printf(" [%u]: (%u) "SRE_FMT"\n", i, ret, SRE_A(&sre)); + } + +out: + free(srp); + + return ret; +} + struct print_recursion_args { struct scoutfs_super_block *super; int fd; @@ -627,6 +745,10 @@ static int print_log_trees_roots(struct scoutfs_key *key, void *val, ltv->data_avail.height - 1); if (err && !ret) ret = err; + err = print_srch_block(pa->fd, <v->srch_file.ref, + ltv->srch_file.height - 1); + if (err && !ret) + ret = err; err = print_btree(pa->fd, pa->super, "", <v->item_root, print_logs_item, NULL); @@ -636,6 +758,33 @@ static int print_log_trees_roots(struct scoutfs_key *key, void *val, return ret; } +static int print_srch_root_files(struct scoutfs_key *key, void *val, + unsigned val_len, void *arg) +{ + struct print_recursion_args *pa = arg; + struct scoutfs_srch_compact_input *scin; + struct scoutfs_srch_file *sfl; + int ret = 0; + int i; + + if (key->sk_type == SCOUTFS_SRCH_BUSY_TYPE) { + scin = val; + for (i = 0; i < scin->nr; i++) { + sfl = &scin->sfl[i]; + ret = print_srch_block(pa->fd, &sfl->ref, + sfl->height - 1); + if (ret < 0) + break; + } + + } else { + sfl = val; + ret = print_srch_block(pa->fd, &sfl->ref, sfl->height - 1); + } + + return ret; +} + static int print_btree_leaf_items(int fd, struct scoutfs_super_block *super, struct scoutfs_btree_ref *ref, print_item_func func, void *arg) @@ -785,6 +934,7 @@ static void print_super_block(struct scoutfs_super_block *super, u64 blkno) " core_data_freed: "RADROOT_F"\n" " lock_clients root: height %u blkno %llu seq %llu\n" " mounted_clients root: height %u blkno %llu seq %llu\n" + " srch_root root: height %u blkno %llu seq %llu\n" " trans_seqs root: height %u blkno %llu seq %llu\n" " fs_root btree root: height %u blkno %llu seq %llu\n", le64_to_cpu(super->next_ino), @@ -812,6 +962,9 @@ static void print_super_block(struct scoutfs_super_block *super, u64 blkno) super->mounted_clients.height, le64_to_cpu(super->mounted_clients.ref.blkno), le64_to_cpu(super->mounted_clients.ref.seq), + super->srch_root.height, + le64_to_cpu(super->srch_root.ref.blkno), + le64_to_cpu(super->srch_root.ref.seq), super->trans_seqs.height, le64_to_cpu(super->trans_seqs.ref.blkno), le64_to_cpu(super->trans_seqs.ref.seq), @@ -869,6 +1022,10 @@ static int print_volume(int fd) if (err && !ret) ret = err; + err = print_btree(fd, super, "srch_root", &super->srch_root, + print_srch_root_item, NULL); + if (err && !ret) + ret = err; err = print_btree(fd, super, "logs_root", &super->logs_root, print_log_trees_item, NULL); if (err && !ret) @@ -876,6 +1033,10 @@ static int print_volume(int fd) pa.super = super; pa.fd = fd; + 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) diff --git a/utils/src/find_xattrs.c b/utils/src/search_xattrs.c similarity index 58% rename from utils/src/find_xattrs.c rename to utils/src/search_xattrs.c index ab9da445..4d7e86e8 100644 --- a/utils/src/find_xattrs.c +++ b/utils/src/search_xattrs.c @@ -21,18 +21,30 @@ static struct option long_ops[] = { { NULL, 0, NULL, 0} }; -static int find_xattrs_cmd(int argc, char **argv) +/* + * There are significant constant costs to each search call, we + * want to get the inodes in as few calls as possible. + */ +#define BATCH_SIZE 1000000 + +static int search_xattrs_cmd(int argc, char **argv) { - struct scoutfs_ioctl_find_xattrs fx; + struct scoutfs_ioctl_search_xattrs sx; char *path = NULL; char *name = NULL; - u64 inos[32]; + u64 *inos = NULL; int fd = -1; int ret; int c; int i; - memset(&fx, 0, sizeof(fx)); + memset(&sx, 0, sizeof(sx)); + inos = malloc(BATCH_SIZE * sizeof(inos[0])); + if (!inos) { + fprintf(stderr, "inos mem alloc failed\n"); + ret = -ENOMEM; + goto out; + } while ((c = getopt_long(argc, argv, "f:n:", long_ops, NULL)) != -1) { switch (c) { @@ -65,6 +77,12 @@ static int find_xattrs_cmd(int argc, char **argv) goto out; } + if (name == NULL) { + fprintf(stderr, "must specify -n xattr name to search for\n"); + ret = -EINVAL; + goto out; + } + fd = open(path, O_RDONLY); if (fd < 0) { ret = -errno; @@ -73,20 +91,20 @@ static int find_xattrs_cmd(int argc, char **argv) goto out; } - fx.next_ino = 0; - fx.name_ptr = (unsigned long)name; - fx.inodes_ptr = (unsigned long)inos; - fx.name_bytes = strlen(name); - fx.nr_inodes = array_size(inos); + sx.next_ino = 0; + sx.last_ino = U64_MAX; + sx.name_ptr = (unsigned long)name; + sx.inodes_ptr = (unsigned long)inos; + sx.name_bytes = strlen(name); + sx.nr_inodes = BATCH_SIZE; - for (;;) { - - ret = ioctl(fd, SCOUTFS_IOC_FIND_XATTRS, &fx); + do { + ret = ioctl(fd, SCOUTFS_IOC_SEARCH_XATTRS, &sx); if (ret == 0) break; if (ret < 0) { ret = -errno; - fprintf(stderr, "find_xattrs ioctl failed: " + fprintf(stderr, "search_xattrs ioctl failed: " "%s (%d)\n", strerror(errno), errno); goto out; } @@ -94,10 +112,8 @@ static int find_xattrs_cmd(int argc, char **argv) for (i = 0; i < ret; i++) printf("%llu\n", inos[i]); - fx.next_ino = inos[ret - 1] + 1; - if (fx.next_ino == 0) - break; - } + sx.next_ino = inos[ret - 1] + 1; + } while (!(sx.output_flags & SCOUTFS_SEARCH_XATTRS_OFLAG_END)); ret = 0; out: @@ -105,13 +121,14 @@ out: close(fd); free(path); free(name); + free(inos); return ret; }; -static void __attribute__((constructor)) find_xattrs_ctor(void) +static void __attribute__((constructor)) search_xattrs_ctor(void) { - cmd_register("find-xattrs", "-n name -f ", + cmd_register("search-xattrs", "-n name -f ", "print inode numbers of inodes which may have given xattr", - find_xattrs_cmd); + search_xattrs_cmd); } diff --git a/utils/src/srch.c b/utils/src/srch.c new file mode 100644 index 00000000..b58075f7 --- /dev/null +++ b/utils/src/srch.c @@ -0,0 +1,46 @@ +#include + +#include "sparse.h" +#include "util.h" +#include "format.h" +#include "srch.h" + +/* shifting by width is undefined :/ */ +#define BYTE_MASK(b) ((1ULL << (b << 3)) - 1) +static u64 byte_masks[] = { + 0, BYTE_MASK(1), BYTE_MASK(2), BYTE_MASK(3), + BYTE_MASK(4), BYTE_MASK(5), BYTE_MASK(6), BYTE_MASK(7), U64_MAX, +}; + +static u64 decode_u64(void *buf, int bytes) +{ + u64 val = get_unaligned_le64(buf) & byte_masks[bytes]; + + return (val >> 1) ^ (-(val & 1)); +} + +int srch_decode_entry(void *buf, struct scoutfs_srch_entry *sre, + struct scoutfs_srch_entry *prev) +{ + u64 diffs[3]; + u16 lengths; + int bytes; + int tot; + int i; + + lengths = get_unaligned_le16(buf); + tot = 2; + + for (i = 0; i < array_size(diffs); i++) { + bytes = min(8, lengths & 15); + diffs[i] = decode_u64(buf + tot, bytes); + tot += bytes; + lengths >>= 4; + } + + sre->hash = cpu_to_le64(le64_to_cpu(prev->hash) + diffs[0]); + sre->ino = cpu_to_le64(le64_to_cpu(prev->ino) + diffs[1]); + sre->id = cpu_to_le64(le64_to_cpu(prev->id) + diffs[2]); + + return tot; +} diff --git a/utils/src/srch.h b/utils/src/srch.h new file mode 100644 index 00000000..c44c52f9 --- /dev/null +++ b/utils/src/srch.h @@ -0,0 +1,7 @@ +#ifndef _SRCH_H_ +#define _SRCH_H_ + +int srch_decode_entry(void *buf, struct scoutfs_srch_entry *sre, + struct scoutfs_srch_entry *prev); + +#endif