diff --git a/kmod/src/ioctl.c b/kmod/src/ioctl.c index e4cdeb7d..588dcf3e 100644 --- a/kmod/src/ioctl.c +++ b/kmod/src/ioctl.c @@ -433,6 +433,73 @@ static long scoutfs_ioc_stat_more(struct file *file, unsigned long arg) return 0; } +static long scoutfs_ioc_item_cache_keys(struct file *file, unsigned long arg) +{ + struct super_block *sb = file_inode(file)->i_sb; + struct scoutfs_ioctl_item_cache_keys ick; + struct scoutfs_key_buf *key; + struct page *page; + unsigned bytes; + void *buf; + int total; + int ret; + + if (copy_from_user(&ick, (void __user *)arg, sizeof(ick))) + return -EFAULT; + + if ((!!ick.key_ptr != !!ick.key_len) || + ick.key_len > SCOUTFS_MAX_KEY_SIZE || + ick.which > SCOUTFS_IOC_ITEM_CACHE_KEYS_RANGES) + return -EINVAL; + + /* don't overflow signed 32bit syscall return longs */ + ick.buf_len = min_t(u64, ick.buf_len, S32_MAX); + + key = scoutfs_key_alloc(sb, SCOUTFS_MAX_KEY_SIZE); + page = alloc_page(GFP_KERNEL); + if (!key || !page) { + ret = -ENOMEM; + goto out; + } + + if (copy_from_user(key->data, (void __user *)ick.key_ptr, ick.key_len)) { + ret = -EFAULT; + goto out; + } + scoutfs_key_init_buf_len(key, key->data, ick.key_len, + SCOUTFS_MAX_KEY_SIZE); + scoutfs_key_inc(key); + + buf = page_address(page); + total = 0; + ret = 0; + while (ick.buf_len) { + bytes = min_t(u64, ick.buf_len, PAGE_SIZE); + + if (ick.which == SCOUTFS_IOC_ITEM_CACHE_KEYS_ITEMS) + ret = scoutfs_item_copy_keys(sb, key, buf, bytes); + else + ret = scoutfs_item_copy_range_keys(sb, key, buf, bytes); + + if (ret > 0 && copy_to_user((void __user *)ick.buf_ptr, buf, ret)) + ret = -EFAULT; + if (ret <= 0) + break; + + ick.buf_len -= ret; + ick.buf_ptr += ret; + total += ret; + ret = 0; + } + +out: + scoutfs_key_free(sb, key); + if (page) + __free_page(page); + + return ret ?: total; +} + long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { switch (cmd) { @@ -446,6 +513,8 @@ long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) return scoutfs_ioc_stage(file, arg); case SCOUTFS_IOC_STAT_MORE: return scoutfs_ioc_stat_more(file, arg); + case SCOUTFS_IOC_ITEM_CACHE_KEYS: + return scoutfs_ioc_item_cache_keys(file, arg); } return -ENOTTY; diff --git a/kmod/src/ioctl.h b/kmod/src/ioctl.h index d1814b8c..79241e9f 100644 --- a/kmod/src/ioctl.h +++ b/kmod/src/ioctl.h @@ -166,4 +166,20 @@ struct scoutfs_ioctl_stat_more { #define SCOUTFS_IOC_STAT_MORE _IOW(SCOUTFS_IOCTL_MAGIC, 7, \ struct scoutfs_ioctl_stat_more) +struct scoutfs_ioctl_item_cache_keys { + __u64 key_ptr; + __u64 key_len; + __u64 buf_ptr; + __u64 buf_len; + __u8 which; +} __packed; + +enum { + SCOUTFS_IOC_ITEM_CACHE_KEYS_ITEMS = 0, + SCOUTFS_IOC_ITEM_CACHE_KEYS_RANGES, +}; + +#define SCOUTFS_IOC_ITEM_CACHE_KEYS _IOW(SCOUTFS_IOCTL_MAGIC, 8, \ + struct scoutfs_ioctl_item_cache_keys) + #endif diff --git a/kmod/src/item.c b/kmod/src/item.c index c9c13906..5cfb7ab3 100644 --- a/kmod/src/item.c +++ b/kmod/src/item.c @@ -457,6 +457,16 @@ static struct cached_range *rb_first_rng(struct rb_root *root) return NULL; } +static struct cached_range *rb_next_rng(struct cached_range *rng) +{ + struct rb_node *node; + + if (rng && (node = rb_next(&rng->node))) + return container_of(node, struct cached_range, node); + + return NULL; +} + static struct cached_range *walk_ranges(struct rb_root *root, struct scoutfs_key_buf *key, struct cached_range **prev, @@ -1849,6 +1859,120 @@ out: return min_t(unsigned long, cac->lru_nr, INT_MAX); } +static void *copy_key_with_len(void *data, struct scoutfs_key_buf *key) +{ + u16 len = key->key_len; + + memcpy(data, &len, sizeof(len)); + data += sizeof(len); + memcpy(data, key->data, len); + + return data + len; +} + +/* + * Copy the next cached ranges starting with the key into the caller's + * buffer. Each range copied by storing each keys size in a u16 + * followed by the binary key data. The number of bytes of full copied + * ranges is returned. The caller's key is incremented past the last + * key returned so that they can iterate without worrying about + * examining the returned keys. + */ +int scoutfs_item_copy_range_keys(struct super_block *sb, + struct scoutfs_key_buf *key, void *data, + unsigned len) +{ + struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); + struct item_cache *cac = sbi->item_cache; + struct rb_node *node = cac->ranges.rb_node; + struct cached_range *next = NULL; + struct scoutfs_key_buf *last = NULL; + struct cached_range *rng; + unsigned long flags; + unsigned bytes; + int ret = 0; + int cmp; + + spin_lock_irqsave(&cac->lock, flags); + + while (node) { + rng = container_of(node, struct cached_range, node); + + cmp = scoutfs_key_compare_ranges(key, key, + rng->start, rng->end); + if (cmp < 0) { + next = rng; + node = node->rb_left; + } else if (cmp > 0) { + node = node->rb_right; + } else { + next = rng; + break; + } + } + + for (rng = next; rng; rng = rb_next_rng(rng)) { + bytes = 2 + rng->start->key_len + 2 + rng->end->key_len; + if (len < bytes) + break; + + data = copy_key_with_len(data, rng->start); + data = copy_key_with_len(data, rng->end); + len -= bytes; + ret += bytes; + + last = rng->end; + } + + if (last) { + scoutfs_key_copy(key, last); + scoutfs_key_inc(key); + } + + spin_unlock_irqrestore(&cac->lock, flags); + + return ret; +} + +/* like copy_range_keys, but for present items */ +int scoutfs_item_copy_keys(struct super_block *sb, struct scoutfs_key_buf *key, + void *data, unsigned len) +{ + struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); + struct item_cache *cac = sbi->item_cache; + struct scoutfs_key_buf *last = NULL; + struct cached_item *item = NULL; + unsigned long flags; + unsigned bytes; + int ret = 0; + + spin_lock_irqsave(&cac->lock, flags); + + for (item = next_item(&cac->items, key); item; item = rb_next_item(item)) { + if (item->deletion) + continue; + + bytes = 2 + item->key->key_len; + if (len < bytes) + break; + + data = copy_key_with_len(data, item->key); + len -= bytes; + ret += bytes; + + last = item->key; + } + + if (last) { + scoutfs_key_copy(key, last); + scoutfs_key_inc(key); + } + + spin_unlock_irqrestore(&cac->lock, flags); + + return ret; +} + int scoutfs_item_setup(struct super_block *sb) { struct scoutfs_sb_info *sbi = SCOUTFS_SB(sb); diff --git a/kmod/src/item.h b/kmod/src/item.h index 68f14f8c..fb9c1df4 100644 --- a/kmod/src/item.h +++ b/kmod/src/item.h @@ -57,6 +57,12 @@ int scoutfs_item_invalidate(struct super_block *sb, struct scoutfs_key_buf *start, struct scoutfs_key_buf *end); +int scoutfs_item_copy_range_keys(struct super_block *sb, + struct scoutfs_key_buf *key, void *data, + unsigned len); +int scoutfs_item_copy_keys(struct super_block *sb, struct scoutfs_key_buf *key, + void *data, unsigned len); + int scoutfs_item_setup(struct super_block *sb); void scoutfs_item_destroy(struct super_block *sb);