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);