diff --git a/kmod/src/Makefile b/kmod/src/Makefile index 2666e6df..dde66039 100644 --- a/kmod/src/Makefile +++ b/kmod/src/Makefile @@ -9,6 +9,7 @@ CFLAGS_scoutfs_trace.o = -I$(src) # define_trace.h double include scoutfs-y += \ acl.o \ + attr_x.o \ avl.o \ alloc.o \ block.o \ diff --git a/kmod/src/attr_x.c b/kmod/src/attr_x.c new file mode 100644 index 00000000..75c62b32 --- /dev/null +++ b/kmod/src/attr_x.c @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2024 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 +#include + +#include "format.h" +#include "super.h" +#include "inode.h" +#include "ioctl.h" +#include "lock.h" +#include "trans.h" +#include "attr_x.h" + +static int validate_attr_x_input(struct super_block *sb, struct scoutfs_ioctl_inode_attr_x *iax) +{ + int ret; + + if ((iax->x_mask & SCOUTFS_IOC_IAX__UNKNOWN) || + (iax->x_flags & SCOUTFS_IOC_IAX_F__UNKNOWN)) + return -EINVAL; + + return 0; +} + +/* + * If the mask indicates interest in the given attr then set the field + * to the caller's value and return the new size if it didn't already + * include the attr field. + */ +#define fill_attr(size, iax, bit, field, val) \ +({ \ + __typeof__(iax) _iax = (iax); \ + __typeof__(size) _size = (size); \ + \ + if (_iax->x_mask & (bit)) { \ + _iax->field = (val); \ + _size = max(_size, offsetof(struct scoutfs_ioctl_inode_attr_x, field) + \ + sizeof_field(struct scoutfs_ioctl_inode_attr_x, field)); \ + } \ + \ + _size; \ +}) + +/* + * Returns -errno on error, or >= number of bytes filled by the + * response. 0 can be returned if no attributes are requested in the + * input x_mask. + */ +int scoutfs_get_attr_x(struct inode *inode, struct scoutfs_ioctl_inode_attr_x *iax) +{ + struct super_block *sb = inode->i_sb; + struct scoutfs_inode_info *si = SCOUTFS_I(inode); + struct scoutfs_lock *lock = NULL; + size_t size = 0; + u64 offline; + u64 online; + u64 bits; + int ret; + + if (iax->x_mask == 0) { + ret = 0; + goto out; + } + + ret = validate_attr_x_input(sb, iax); + if (ret < 0) + goto out; + + inode_lock(inode); + + ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_READ, SCOUTFS_LKF_REFRESH_INODE, inode, &lock); + if (ret) + goto unlock; + + size = fill_attr(size, iax, SCOUTFS_IOC_IAX_META_SEQ, + meta_seq, scoutfs_inode_meta_seq(inode)); + size = fill_attr(size, iax, SCOUTFS_IOC_IAX_DATA_SEQ, + data_seq, scoutfs_inode_data_seq(inode)); + size = fill_attr(size, iax, SCOUTFS_IOC_IAX_DATA_VERSION, + data_version, scoutfs_inode_data_version(inode)); + if (iax->x_mask & (SCOUTFS_IOC_IAX_ONLINE_BLOCKS | SCOUTFS_IOC_IAX_OFFLINE_BLOCKS)) { + scoutfs_inode_get_onoff(inode, &online, &offline); + size = fill_attr(size, iax, SCOUTFS_IOC_IAX_ONLINE_BLOCKS, + online_blocks, online); + size = fill_attr(size, iax, SCOUTFS_IOC_IAX_OFFLINE_BLOCKS, + offline_blocks, offline); + } + size = fill_attr(size, iax, SCOUTFS_IOC_IAX_CTIME, ctime_sec, inode->i_ctime.tv_sec); + size = fill_attr(size, iax, SCOUTFS_IOC_IAX_CTIME, ctime_nsec, inode->i_ctime.tv_nsec); + size = fill_attr(size, iax, SCOUTFS_IOC_IAX_CRTIME, crtime_sec, si->crtime.tv_sec); + size = fill_attr(size, iax, SCOUTFS_IOC_IAX_CRTIME, crtime_nsec, si->crtime.tv_nsec); + size = fill_attr(size, iax, SCOUTFS_IOC_IAX_SIZE, size, i_size_read(inode)); + + ret = size; +unlock: + scoutfs_unlock(sb, lock, SCOUTFS_LOCK_READ); + inode_unlock(inode); +out: + return ret; +} + +static bool valid_attr_changes(struct inode *inode, struct scoutfs_ioctl_inode_attr_x *iax) +{ + /* provided data_version must be non-zero */ + if ((iax->x_mask & SCOUTFS_IOC_IAX_DATA_VERSION) && (iax->data_version == 0)) + return false; + + /* can only set size or data version in new regular files */ + if (((iax->x_mask & SCOUTFS_IOC_IAX_SIZE) || + (iax->x_mask & SCOUTFS_IOC_IAX_DATA_VERSION)) && + (!S_ISREG(inode->i_mode) || scoutfs_inode_data_version(inode) != 0)) + return false; + + /* must provide non-zero data_version with non-zero size */ + if (((iax->x_mask & SCOUTFS_IOC_IAX_SIZE) && (iax->size > 0)) && + (!(iax->x_mask & SCOUTFS_IOC_IAX_DATA_VERSION) || (iax->data_version == 0))) + return false; + + /* must provide non-zero size when setting offline extents to that size */ + if ((iax->x_flags & SCOUTFS_IOC_IAX_F_SIZE_OFFLINE) && + (!(iax->x_mask & SCOUTFS_IOC_IAX_SIZE) || (iax->size == 0))) + return false; + + return true; +} + +int scoutfs_set_attr_x(struct inode *inode, struct scoutfs_ioctl_inode_attr_x *iax) +{ + struct super_block *sb = inode->i_sb; + struct scoutfs_inode_info *si = SCOUTFS_I(inode); + struct scoutfs_lock *lock = NULL; + LIST_HEAD(ind_locks); + bool set_data_seq; + int ret; + + /* initially all setting is root only, could loosen with finer grained checks */ + if (!capable(CAP_SYS_ADMIN)) { + ret = -EPERM; + goto out; + } + + if (iax->x_mask == 0) { + ret = 0; + goto out; + } + + ret = validate_attr_x_input(sb, iax); + if (ret < 0) + goto out; + + inode_lock(inode); + + ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_WRITE, SCOUTFS_LKF_REFRESH_INODE, inode, &lock); + if (ret) + goto unlock; + + /* check for errors before making any changes */ + if (!valid_attr_changes(inode, iax)) { + ret = -EINVAL; + goto unlock; + } + + /* setting only so we don't see 0 data seq with nonzero data_version */ + if ((iax->x_mask & SCOUTFS_IOC_IAX_DATA_VERSION) && (iax->data_version > 0)) + set_data_seq = true; + else + set_data_seq = false; + + ret = scoutfs_inode_index_lock_hold(inode, &ind_locks, set_data_seq, true); + if (ret) + goto unlock; + + ret = scoutfs_dirty_inode_item(inode, lock); + if (ret < 0) + goto release; + + /* creating offline extent first, it might fail */ + if (iax->x_flags & SCOUTFS_IOC_IAX_F_SIZE_OFFLINE) { + ret = scoutfs_data_init_offline_extent(inode, iax->size, lock); + if (ret) + goto release; + } + + /* make all changes once they're all checked and will succeed */ + if (iax->x_mask & SCOUTFS_IOC_IAX_DATA_VERSION) + scoutfs_inode_set_data_version(inode, iax->data_version); + if (iax->x_mask & SCOUTFS_IOC_IAX_SIZE) + i_size_write(inode, iax->size); + if (iax->x_mask & SCOUTFS_IOC_IAX_CTIME) { + inode->i_ctime.tv_sec = iax->ctime_sec; + inode->i_ctime.tv_nsec = iax->ctime_nsec; + } + if (iax->x_mask & SCOUTFS_IOC_IAX_CRTIME) { + si->crtime.tv_sec = iax->crtime_sec; + si->crtime.tv_nsec = iax->crtime_nsec; + } + + scoutfs_update_inode_item(inode, lock, &ind_locks); + ret = 0; +release: + scoutfs_release_trans(sb); +unlock: + scoutfs_inode_index_unlock(sb, &ind_locks); + scoutfs_unlock(sb, lock, SCOUTFS_LOCK_WRITE); + inode_unlock(inode); +out: + return ret; +} diff --git a/kmod/src/attr_x.h b/kmod/src/attr_x.h new file mode 100644 index 00000000..2aaf8f98 --- /dev/null +++ b/kmod/src/attr_x.h @@ -0,0 +1,11 @@ +#ifndef _SCOUTFS_ATTR_X_H_ +#define _SCOUTFS_ATTR_X_H_ + +#include +#include +#include "ioctl.h" + +int scoutfs_get_attr_x(struct inode *inode, struct scoutfs_ioctl_inode_attr_x *iax); +int scoutfs_set_attr_x(struct inode *inode, struct scoutfs_ioctl_inode_attr_x *iax); + +#endif diff --git a/kmod/src/ioctl.c b/kmod/src/ioctl.c index 885ad330..74abcf1d 100644 --- a/kmod/src/ioctl.c +++ b/kmod/src/ioctl.c @@ -42,6 +42,7 @@ #include "alloc.h" #include "server.h" #include "counters.h" +#include "attr_x.h" #include "scoutfs_trace.h" /* @@ -1504,6 +1505,68 @@ out: return nr ?: ret; } +static long scoutfs_ioc_get_attr_x(struct file *file, unsigned long arg) +{ + struct inode *inode = file_inode(file); + struct scoutfs_ioctl_inode_attr_x __user *uiax = (void __user *)arg; + struct scoutfs_ioctl_inode_attr_x *iax = NULL; + int ret; + + iax = kmalloc(sizeof(struct scoutfs_ioctl_inode_attr_x), GFP_KERNEL); + if (!iax) { + ret = -ENOMEM; + goto out; + } + + ret = get_user(iax->x_mask, &uiax->x_mask) ?: + get_user(iax->x_flags, &uiax->x_flags); + if (ret < 0) + goto out; + + ret = scoutfs_get_attr_x(inode, iax); + if (ret < 0) + goto out; + + /* only copy results after dropping cluster locks (could fault) */ + if (ret > 0 && copy_to_user(uiax, iax, ret) != 0) + ret = -EFAULT; + else + ret = 0; +out: + kfree(iax); + return ret; +} + +static long scoutfs_ioc_set_attr_x(struct file *file, unsigned long arg) +{ + struct inode *inode = file_inode(file); + struct scoutfs_ioctl_inode_attr_x __user *uiax = (void __user *)arg; + struct scoutfs_ioctl_inode_attr_x *iax = NULL; + int ret; + + iax = kmalloc(sizeof(struct scoutfs_ioctl_inode_attr_x), GFP_KERNEL); + if (!iax) { + ret = -ENOMEM; + goto out; + } + + if (copy_from_user(iax, uiax, sizeof(struct scoutfs_ioctl_inode_attr_x))) { + ret = -EFAULT; + goto out; + } + + ret = mnt_want_write_file(file); + if (ret < 0) + goto out; + + ret = scoutfs_set_attr_x(inode, iax); + + mnt_drop_write_file(file); +out: + kfree(iax); + return ret; +} + long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { switch (cmd) { @@ -1541,6 +1604,10 @@ long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) return scoutfs_ioc_get_allocated_inos(file, arg); case SCOUTFS_IOC_GET_REFERRING_ENTRIES: return scoutfs_ioc_get_referring_entries(file, arg); + case SCOUTFS_IOC_GET_ATTR_X: + return scoutfs_ioc_get_attr_x(file, arg); + case SCOUTFS_IOC_SET_ATTR_X: + return scoutfs_ioc_set_attr_x(file, arg); } return -ENOTTY; diff --git a/kmod/src/ioctl.h b/kmod/src/ioctl.h index 7be22dbb..d62f0283 100644 --- a/kmod/src/ioctl.h +++ b/kmod/src/ioctl.h @@ -673,4 +673,58 @@ struct scoutfs_ioctl_dirent { #define SCOUTFS_IOC_GET_REFERRING_ENTRIES \ _IOW(SCOUTFS_IOCTL_MAGIC, 17, struct scoutfs_ioctl_get_referring_entries) +struct scoutfs_ioctl_inode_attr_x { + __u64 x_mask; + __u64 x_flags; + __u64 meta_seq; + __u64 data_seq; + __u64 data_version; + __u64 online_blocks; + __u64 offline_blocks; + __u64 ctime_sec; + __u32 ctime_nsec; + __u32 crtime_nsec; + __u64 crtime_sec; + __u64 size; +}; + +/* + * Behavioral flags set in the x_flags field. These flags don't + * necessarily correspond to specific attributes, but instead change the + * behaviour of a _get_ or _set_ operation. + * + * @SCOUTFS_IOC_IAX_F_SIZE_OFFLINE: When setting i_size, also create + * extents which are marked offline for the region of the file from + * offset 0 to the new set size. This can only be set when setting the + * size and has no effect if setting the size fails. + */ +#define SCOUTFS_IOC_IAX_F_SIZE_OFFLINE (1ULL << 0) +#define SCOUTFS_IOC_IAX_F__UNKNOWN (U64_MAX << 1) + +/* + * x_mask bits which indicate which attributes of the inode to populate + * on return for _get or to set on the inode for _set. Each mask bit + * corresponds to the matching named field in the attr_x struct passed + * to the _get_ and _set_ calls. + * + * Each field can have different permissions or other attribute + * requirements which can cause calls to fail. If _set_ fails then no + * other attribute changes will have been made by the same call. + */ +#define SCOUTFS_IOC_IAX_META_SEQ (1ULL << 0) +#define SCOUTFS_IOC_IAX_DATA_SEQ (1ULL << 1) +#define SCOUTFS_IOC_IAX_DATA_VERSION (1ULL << 2) +#define SCOUTFS_IOC_IAX_ONLINE_BLOCKS (1ULL << 3) +#define SCOUTFS_IOC_IAX_OFFLINE_BLOCKS (1ULL << 4) +#define SCOUTFS_IOC_IAX_CTIME (1ULL << 5) +#define SCOUTFS_IOC_IAX_CRTIME (1ULL << 6) +#define SCOUTFS_IOC_IAX_SIZE (1ULL << 7) +#define SCOUTFS_IOC_IAX__UNKNOWN (U64_MAX << 8) + +#define SCOUTFS_IOC_GET_ATTR_X \ + _IOW(SCOUTFS_IOCTL_MAGIC, 18, struct scoutfs_ioctl_inode_attr_x) + +#define SCOUTFS_IOC_SET_ATTR_X \ + _IOW(SCOUTFS_IOCTL_MAGIC, 19, struct scoutfs_ioctl_inode_attr_x) + #endif