From c010afa8fff89bddcacb68e6b72b292c38161614 Mon Sep 17 00:00:00 2001 From: Zach Brown Date: Fri, 24 May 2019 10:09:24 -0700 Subject: [PATCH] scoutfs: add setattr_more ioctl Add an ioctl that can be used by userspace to restore a file to its offline state. To do that it needs to set inode fields that are otherwise not exposed and create an offline extent. Signed-off-by: Zach Brown --- kmod/src/count.h | 15 ++++++++ kmod/src/data.c | 43 ++++++++++++++++++++++ kmod/src/data.h | 2 + kmod/src/inode.c | 11 ++++++ kmod/src/inode.h | 1 + kmod/src/ioctl.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++++ kmod/src/ioctl.h | 18 +++++++++ 7 files changed, 185 insertions(+) diff --git a/kmod/src/count.h b/kmod/src/count.h index db35ebec..bf95cc44 100644 --- a/kmod/src/count.h +++ b/kmod/src/count.h @@ -313,4 +313,19 @@ static inline const struct scoutfs_item_count SIC_FALLOCATE_ONE(void) return cnt; } +/* + * ioc_setattr_more can dirty the inode and add a single offline extent. + */ +static inline const struct scoutfs_item_count SIC_SETATTR_MORE(void) +{ + struct scoutfs_item_count cnt = {0,}; + + __count_dirty_inode(&cnt); + + cnt.items++; + cnt.vals += sizeof(struct scoutfs_file_extent); + + return cnt; +} + #endif diff --git a/kmod/src/data.c b/kmod/src/data.c index ce31e642..f0b284ae 100644 --- a/kmod/src/data.c +++ b/kmod/src/data.c @@ -1239,6 +1239,49 @@ out: return ret; } +/* + * A special case of initialzing a single large offline extent. This + * chooses not to deal with any existing extents. It can only be used + * on regular files with no data extents. It's used to restore a file + * with an offline extent which can then trigger staging. + * + * The caller has taken care of locking and holding a transaction. + * + * This could be an fallocate mode. + */ +int scoutfs_data_init_offline_extent(struct inode *inode, u64 size, + struct scoutfs_lock *lock) + +{ + struct super_block *sb = inode->i_sb; + struct scoutfs_extent ext; + u64 ino = scoutfs_ino(inode); + u64 len; + int ret; + + if (!S_ISREG(inode->i_mode)) { + ret = -EINVAL; + goto out; + } + + scoutfs_extent_init(&ext, SCOUTFS_FILE_EXTENT_TYPE, ino, 0, 1, 0, 0); + ret = scoutfs_extent_next(sb, data_extent_io, &ext, lock); + if (ret != -ENOENT) { + if (ret == 0) + ret = -EINVAL; + goto out; + } + + len = (size + SCOUTFS_BLOCK_SIZE - 1) >> SCOUTFS_BLOCK_SHIFT; + scoutfs_extent_init(&ext, SCOUTFS_FILE_EXTENT_TYPE, ino, + 0, len, 0, SEF_OFFLINE); + ret = scoutfs_extent_add(sb, data_extent_io, &ext, lock); + if (ret == 0) + scoutfs_inode_add_onoff(inode, 0, len); +out: + return ret; +} + /* * Return all the file's extents whose blocks overlap with the caller's diff --git a/kmod/src/data.h b/kmod/src/data.h index d45114da..21deeac4 100644 --- a/kmod/src/data.h +++ b/kmod/src/data.h @@ -45,6 +45,8 @@ int scoutfs_data_truncate_items(struct super_block *sb, struct inode *inode, int scoutfs_data_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo, u64 start, u64 len); long scoutfs_fallocate(struct file *file, int mode, loff_t offset, loff_t len); +int scoutfs_data_init_offline_extent(struct inode *inode, u64 size, + struct scoutfs_lock *lock); int scoutfs_data_wait_check(struct inode *inode, loff_t pos, loff_t len, u8 sef, u8 op, struct scoutfs_data_wait *ow, diff --git a/kmod/src/inode.c b/kmod/src/inode.c index 3bdc756a..afcd67aa 100644 --- a/kmod/src/inode.c +++ b/kmod/src/inode.c @@ -540,6 +540,17 @@ void scoutfs_inode_inc_data_version(struct inode *inode) preempt_enable(); } +void scoutfs_inode_set_data_version(struct inode *inode, u64 data_version) +{ + struct scoutfs_inode_info *si = SCOUTFS_I(inode); + + preempt_disable(); + write_seqcount_begin(&si->seqcount); + si->data_version = data_version; + write_seqcount_end(&si->seqcount); + preempt_enable(); +} + void scoutfs_inode_add_onoff(struct inode *inode, s64 on, s64 off) { struct scoutfs_inode_info *si; diff --git a/kmod/src/inode.h b/kmod/src/inode.h index 0ccd0184..719fb391 100644 --- a/kmod/src/inode.h +++ b/kmod/src/inode.h @@ -103,6 +103,7 @@ struct inode *scoutfs_new_inode(struct super_block *sb, struct inode *dir, void scoutfs_inode_set_meta_seq(struct inode *inode); void scoutfs_inode_set_data_seq(struct inode *inode); void scoutfs_inode_inc_data_version(struct inode *inode); +void scoutfs_inode_set_data_version(struct inode *inode, u64 data_version); void scoutfs_inode_add_onoff(struct inode *inode, s64 on, s64 off); u64 scoutfs_inode_meta_seq(struct inode *inode); u64 scoutfs_inode_data_seq(struct inode *inode); diff --git a/kmod/src/ioctl.c b/kmod/src/ioctl.c index 738173e9..26ea6512 100644 --- a/kmod/src/ioctl.c +++ b/kmod/src/ioctl.c @@ -32,6 +32,7 @@ #include "client.h" #include "lock.h" #include "manifest.h" +#include "trans.h" #include "scoutfs_trace.h" /* @@ -591,6 +592,98 @@ static long scoutfs_ioc_data_waiting(struct file *file, unsigned long arg) return ret ?: total; } +/* + * This is used when restoring files, it lets the caller set all the + * inode attributes which are otherwise unreachable. Changing the file + * size can only be done for regular files with a data_version of 0. + */ +static long scoutfs_ioc_setattr_more(struct file *file, unsigned long arg) +{ + struct inode *inode = file->f_inode; + struct super_block *sb = inode->i_sb; + struct scoutfs_ioctl_setattr_more __user *usm = (void __user *)arg; + struct scoutfs_ioctl_setattr_more sm; + struct scoutfs_lock *lock = NULL; + LIST_HEAD(ind_locks); + bool set_data_seq; + int ret; + + if (!capable(CAP_SYS_ADMIN)) { + ret = -EPERM; + goto out; + } + + if (!(file->f_mode & FMODE_WRITE)) { + ret = -EBADF; + goto out; + } + + if (copy_from_user(&sm, usm, sizeof(sm))) { + ret = -EFAULT; + goto out; + } + + if ((sm.i_size > 0 && sm.data_version == 0) || + ((sm.flags & SCOUTFS_IOC_SETATTR_MORE_OFFLINE) && !sm.i_size) || + (sm.flags & SCOUTFS_IOC_SETATTR_MORE_UNKNOWN)) { + ret = -EINVAL; + goto out; + } + + ret = mnt_want_write_file(file); + if (ret) + goto out; + + mutex_lock(&inode->i_mutex); + + ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_WRITE, + SCOUTFS_LKF_REFRESH_INODE, inode, &lock); + if (ret) + goto unlock; + + /* can only change size/dv on untouched regular files */ + if ((sm.i_size != 0 || sm.data_version != 0) && + ((!S_ISREG(inode->i_mode) || + scoutfs_inode_data_version(inode) != 0))) { + ret = -EINVAL; + goto unlock; + } + + /* setting only so we don't see 0 data seq with nonzero data_version */ + set_data_seq = sm.data_version != 0 ? true : false; + ret = scoutfs_inode_index_lock_hold(inode, &ind_locks, set_data_seq, + SIC_SETATTR_MORE()); + if (ret) + goto unlock; + + if (sm.flags & SCOUTFS_IOC_SETATTR_MORE_OFFLINE) { + ret = scoutfs_data_init_offline_extent(inode, sm.i_size, lock); + if (ret) + goto release; + } + + if (sm.data_version) + scoutfs_inode_set_data_version(inode, sm.data_version); + if (sm.i_size) + i_size_write(inode, sm.i_size); + inode->i_ctime.tv_sec = le64_to_cpu(sm.ctime.sec); + inode->i_ctime.tv_nsec = le32_to_cpu(sm.ctime.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); + mutex_unlock(&inode->i_mutex); + mnt_drop_write_file(file); +out: + + return ret; +} + long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { switch (cmd) { @@ -608,6 +701,8 @@ long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) return scoutfs_ioc_item_cache_keys(file, arg); case SCOUTFS_IOC_DATA_WAITING: return scoutfs_ioc_data_waiting(file, arg); + case SCOUTFS_IOC_SETATTR_MORE: + return scoutfs_ioc_setattr_more(file, arg); } return -ENOTTY; diff --git a/kmod/src/ioctl.h b/kmod/src/ioctl.h index 1b592522..e49116b4 100644 --- a/kmod/src/ioctl.h +++ b/kmod/src/ioctl.h @@ -253,4 +253,22 @@ struct scoutfs_ioctl_data_waiting { #define SCOUTFS_IOC_DATA_WAITING _IOW(SCOUTFS_IOCTL_MAGIC, 9, \ struct scoutfs_ioctl_data_waiting) +/* + * If i_size is set then data_version must be non-zero. If the offline + * flag is set then i_size must be set and a offline extent will be + * created from offset 0 to i_size. + */ +struct scoutfs_ioctl_setattr_more { + __u64 data_version; + __u64 i_size; + __u64 flags; + struct scoutfs_timespec ctime; +} __packed; + +#define SCOUTFS_IOC_SETATTR_MORE_OFFLINE (1 << 0) +#define SCOUTFS_IOC_SETATTR_MORE_UNKNOWN (U8_MAX << 1) + +#define SCOUTFS_IOC_SETATTR_MORE _IOW(SCOUTFS_IOCTL_MAGIC, 10, \ + struct scoutfs_ioctl_setattr_more) + #endif