From a7fef3d7dd652288eb91dc0176f01a13f8dc0885 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Fri, 14 Jun 2019 14:45:39 -0700 Subject: [PATCH] scoutfs: add listxattr_raw ioctl Add an ioctl which can be used to iterate over the keys for all the xattrs on an inode. It is privileged, can see hidden inodes, and has an iteration cursor so that it can make its way through very large numbers of xattrs. Signed-off-by: Zach Brown --- kmod/src/ioctl.c | 65 ++++++++++++++++++++++++++++++++++++++++++++++++ kmod/src/ioctl.h | 10 ++++++++ kmod/src/xattr.c | 45 +++++++++++++++++++++++---------- kmod/src/xattr.h | 3 +++ 4 files changed, 110 insertions(+), 13 deletions(-) diff --git a/kmod/src/ioctl.c b/kmod/src/ioctl.c index 26ea6512..16bcc0f8 100644 --- a/kmod/src/ioctl.c +++ b/kmod/src/ioctl.c @@ -33,6 +33,7 @@ #include "lock.h" #include "manifest.h" #include "trans.h" +#include "xattr.h" #include "scoutfs_trace.h" /* @@ -684,6 +685,68 @@ out: return ret; } +static long scoutfs_ioc_listxattr_raw(struct file *file, unsigned long arg) +{ + struct inode *inode = file->f_inode; + struct scoutfs_ioctl_listxattr_raw __user *ulxr = (void __user *)arg; + struct scoutfs_ioctl_listxattr_raw lxr; + struct page *page = NULL; + unsigned int bytes; + int total = 0; + int ret; + + if (!(file->f_mode & FMODE_READ)) { + ret = -EBADF; + goto out; + } + + if (!capable(CAP_SYS_ADMIN)) { + ret = -EBADF; + goto out; + } + + if (copy_from_user(&lxr, ulxr, sizeof(lxr))) { + ret = -EFAULT; + goto out; + } + + page = alloc_page(GFP_KERNEL); + if (!page) { + ret = -ENOMEM; + goto out; + } + + while (lxr.buf_bytes) { + bytes = min_t(int, lxr.buf_bytes, PAGE_SIZE); + ret = scoutfs_list_xattrs(inode, page_address(page), bytes, + &lxr.hash_pos, &lxr.id_pos, + false, true); + if (ret <= 0) + break; + + if (copy_to_user((void __user *)lxr.buf_ptr, + page_address(page), ret)) { + ret = -EFAULT; + break; + } + + lxr.buf_ptr += ret; + lxr.buf_bytes -= ret; + total += ret; + ret = 0; + } + +out: + if (page) + __free_page(page); + + if (ret == 0 && (__put_user(lxr.hash_pos, &ulxr->hash_pos) || + __put_user(lxr.id_pos, &ulxr->id_pos))) + ret = -EFAULT; + + return ret ?: total; +} + long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { switch (cmd) { @@ -703,6 +766,8 @@ long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) return scoutfs_ioc_data_waiting(file, arg); case SCOUTFS_IOC_SETATTR_MORE: return scoutfs_ioc_setattr_more(file, arg); + case SCOUTFS_IOC_LISTXATTR_RAW: + return scoutfs_ioc_listxattr_raw(file, arg); } return -ENOTTY; diff --git a/kmod/src/ioctl.h b/kmod/src/ioctl.h index e49116b4..1c03c25e 100644 --- a/kmod/src/ioctl.h +++ b/kmod/src/ioctl.h @@ -271,4 +271,14 @@ struct scoutfs_ioctl_setattr_more { #define SCOUTFS_IOC_SETATTR_MORE _IOW(SCOUTFS_IOCTL_MAGIC, 10, \ struct scoutfs_ioctl_setattr_more) +struct scoutfs_ioctl_listxattr_raw { + __u64 id_pos; + __u64 buf_ptr; + __u32 buf_bytes; + __u32 hash_pos; +} __packed; + +#define SCOUTFS_IOC_LISTXATTR_RAW _IOW(SCOUTFS_IOCTL_MAGIC, 11, \ + struct scoutfs_ioctl_listxattr_raw) + #endif diff --git a/kmod/src/xattr.c b/kmod/src/xattr.c index 23aca918..62ad6a70 100644 --- a/kmod/src/xattr.c +++ b/kmod/src/xattr.c @@ -533,9 +533,10 @@ int scoutfs_removexattr(struct dentry *dentry, const char *name) return scoutfs_xattr_set(dentry, name, NULL, 0, XATTR_REPLACE); } -ssize_t scoutfs_listxattr(struct dentry *dentry, char *buffer, size_t size) +ssize_t scoutfs_list_xattrs(struct inode *inode, char *buffer, + size_t size, __u32 *hash_pos, __u64 *id_pos, + bool e_range, bool hidden) { - struct inode *inode = dentry->d_inode; struct scoutfs_inode_info *si = SCOUTFS_I(inode); struct super_block *sb = inode->i_sb; struct scoutfs_xattr *xat = NULL; @@ -543,11 +544,16 @@ ssize_t scoutfs_listxattr(struct dentry *dentry, char *buffer, size_t size) struct scoutfs_key key; struct prefix_tags tgs; unsigned int bytes; - ssize_t total; - u32 name_hash; - u64 id; + ssize_t total = 0; + u32 name_hash = 0; + u64 id = 0; int ret; + if (hash_pos) + name_hash = *hash_pos; + if (id_pos) + id = *id_pos; + /* need a buffer large enough for all possible names */ bytes = sizeof(struct scoutfs_xattr) + SCOUTFS_XATTR_MAX_NAME_LEN; xat = kmalloc(bytes, GFP_NOFS); @@ -562,10 +568,6 @@ ssize_t scoutfs_listxattr(struct dentry *dentry, char *buffer, size_t size) down_read(&si->xattr_rwsem); - name_hash = 0; - id = 0; - total = 0; - for (;;) { ret = get_next_xattr(inode, &key, xat, bytes, NULL, 0, name_hash, id, lck); @@ -575,12 +577,14 @@ ssize_t scoutfs_listxattr(struct dentry *dentry, char *buffer, size_t size) break; } - if (parse_tags(xat->name, &tgs) != 0 || !tgs.hide) { - total += xat->name_len + 1; + if (hidden || parse_tags(xat->name, &tgs) != 0 || !tgs.hide) { if (size) { - if (total > size) { - ret = -ERANGE; + if ((total + xat->name_len + 1) > size) { + if (e_range) + ret = -ERANGE; + else + ret = total; break; } @@ -588,6 +592,8 @@ ssize_t scoutfs_listxattr(struct dentry *dentry, char *buffer, size_t size) buffer += xat->name_len; *(buffer++) = '\0'; } + + total += xat->name_len + 1; } name_hash = le64_to_cpu(key.skx_name_hash); @@ -599,9 +605,22 @@ ssize_t scoutfs_listxattr(struct dentry *dentry, char *buffer, size_t size) out: kfree(xat); + if (hash_pos) + *hash_pos = name_hash; + if (id_pos) + *id_pos = id; + return ret; } +ssize_t scoutfs_listxattr(struct dentry *dentry, char *buffer, size_t size) +{ + struct inode *inode = dentry->d_inode; + + return scoutfs_list_xattrs(inode, buffer, size, + NULL, NULL, true, false); +} + /* * Delete all the xattr items associated with this inode. The inode is * dead so we don't need the xattr rwsem. diff --git a/kmod/src/xattr.h b/kmod/src/xattr.h index 6c205358..ca4fb7fc 100644 --- a/kmod/src/xattr.h +++ b/kmod/src/xattr.h @@ -7,6 +7,9 @@ int scoutfs_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags); int scoutfs_removexattr(struct dentry *dentry, const char *name); ssize_t scoutfs_listxattr(struct dentry *dentry, char *buffer, size_t size); +ssize_t scoutfs_list_xattrs(struct inode *inode, char *buffer, + size_t size, __u32 *hash_pos, __u64 *id_pos, + bool e_range, bool hidden); int scoutfs_xattr_drop(struct super_block *sb, u64 ino, struct scoutfs_lock *lock);