diff --git a/kmod/src/filerw.c b/kmod/src/filerw.c index daa975df..727737cd 100644 --- a/kmod/src/filerw.c +++ b/kmod/src/filerw.c @@ -178,31 +178,28 @@ static void return_file_block(struct super_block *sb, u64 blkno) } /* - * Free mapped extents whose entire contents are past the new - * specified size. The caller holds a transaction. + * Free extents whose blocks fall inside the specified blocks. The + * caller holds a transaction. * - * This is the low level extent item truncate code. - * Callers manage higher order truncation and orphan cleanup. + * If 'release' is given then blocks are freed inside i_size but the + * extent items are left behind and their _OFFLINE flag is set. * - * XXX probably should be a range + * This is the low level extent item truncate code. Callers manage + * higher order truncation and orphan cleanup. */ -int scoutfs_truncate_extent_items(struct super_block *sb, u64 ino, u64 size) +int scoutfs_truncate_extent_items(struct super_block *sb, u64 ino, u64 iblock, + u64 len, bool offline) { struct scoutfs_btree_root *meta = SCOUTFS_META(sb); struct scoutfs_extent extent; struct scoutfs_btree_val val; struct scoutfs_key key; struct scoutfs_key first; - u64 iblock; - u64 len; - u64 loff; u64 seq; int ret; - iblock = DIV_ROUND_UP(size, SCOUTFS_BLOCK_SIZE); - - scoutfs_set_key(&first, ino, SCOUTFS_EXTENT_KEY, 0); - scoutfs_set_key(&key, ino, SCOUTFS_EXTENT_KEY, ~0ULL); + scoutfs_set_key(&first, ino, SCOUTFS_EXTENT_KEY, iblock); + scoutfs_set_key(&key, ino, SCOUTFS_EXTENT_KEY, iblock + len - 1); trace_printk("iblock %llu\n", iblock); @@ -218,28 +215,43 @@ int scoutfs_truncate_extent_items(struct super_block *sb, u64 ino, u64 size) break; } - loff = le64_to_cpu(key.offset); len = le64_to_cpu(extent.len); - if (WARN_ON_ONCE(len != 1)) { ret = -EIO; break; } - if ((loff + len) <= iblock) + /* XXX corruption: offline and allocation are exclusive */ + if (!!extent.blkno == + !!(extent.flags & SCOUTFS_EXTENT_FLAG_OFFLINE)) { + ret = -EIO; break; + } + + if (offline && (extent.flags & SCOUTFS_EXTENT_FLAG_OFFLINE)) + continue; /* make sure we can delete the extent after freeing */ - ret = scoutfs_btree_dirty(sb, meta, &key); - if (ret) - break; + if (extent.blkno) { + ret = scoutfs_btree_dirty(sb, meta, &key); + if (ret) + break; - ret = scoutfs_buddy_free(sb, cpu_to_le64(seq), - le64_to_cpu(extent.blkno), 0); - if (ret) - break; + ret = scoutfs_buddy_free(sb, cpu_to_le64(seq), + le64_to_cpu(extent.blkno), 0); + if (ret) + break; + } - scoutfs_btree_delete(sb, meta, &key); + if (offline) { + extent.blkno = 0; + extent.flags |= SCOUTFS_EXTENT_FLAG_OFFLINE; + scoutfs_btree_update(sb, meta, &key, &val); + } else { + ret = scoutfs_btree_delete(sb, meta, &key); + if (ret) + break; + } /* XXX sync transaction if it's enormous */ scoutfs_dec_key(&key); @@ -286,8 +298,12 @@ static int contig_mapped_blocks(struct inode *inode, u64 iblock, u64 *blkno) ret = scoutfs_btree_lookup(sb, meta, &key, &val); if (ret == sizeof(extent)) { - *blkno = le64_to_cpu(extent.blkno); - ret = min_t(u64, le64_to_cpu(extent.len), INT_MAX); + if (extent.flags & SCOUTFS_EXTENT_FLAG_OFFLINE) { + ret = 0; + } else { + *blkno = le64_to_cpu(extent.blkno); + ret = min_t(u64, le64_to_cpu(extent.len), INT_MAX); + } } else if (ret >= 0) { /* XXX corruption */ ret = -EIO; @@ -377,6 +393,7 @@ static int map_writable_block(struct inode *inode, u64 iblock, u64 *blkno_ret) extent.blkno = cpu_to_le64(new_blkno); extent.len = cpu_to_le64(1); + extent.flags &= ~SCOUTFS_EXTENT_FLAG_OFFLINE; /* dirtying guarantees success */ err = scoutfs_btree_update(sb, meta, &key, &val); diff --git a/kmod/src/filerw.h b/kmod/src/filerw.h index de4972d3..f5924d71 100644 --- a/kmod/src/filerw.h +++ b/kmod/src/filerw.h @@ -5,6 +5,7 @@ extern const struct address_space_operations scoutfs_file_aops; extern const struct file_operations scoutfs_file_fops; void scoutfs_filerw_free_alloc(struct super_block *sb); -int scoutfs_truncate_extent_items(struct super_block *sb, u64 ino, u64 size); +int scoutfs_truncate_extent_items(struct super_block *sb, u64 ino, u64 iblock, + u64 len, bool offline); #endif diff --git a/kmod/src/format.h b/kmod/src/format.h index 528b2e5d..8c7bb7a1 100644 --- a/kmod/src/format.h +++ b/kmod/src/format.h @@ -299,6 +299,8 @@ struct scoutfs_extent { __u8 flags; } __packed; +#define SCOUTFS_EXTENT_FLAG_OFFLINE (1 << 0) + /* * link backrefs give us a way to find all the hard links that refer * to a target inode. They're stored at an offset determined by an diff --git a/kmod/src/inode.c b/kmod/src/inode.c index aabb0910..0d98d8bf 100644 --- a/kmod/src/inode.c +++ b/kmod/src/inode.c @@ -454,7 +454,7 @@ static int __delete_inode(struct super_block *sb, struct scoutfs_key *key, if (S_ISLNK(mode)) ret = scoutfs_symlink_drop(sb, ino); else if (S_ISREG(mode)) - ret = scoutfs_truncate_extent_items(sb, ino, 0); + ret = scoutfs_truncate_extent_items(sb, ino, 0, ~0ULL, false); if (ret) goto out; diff --git a/kmod/src/ioctl.c b/kmod/src/ioctl.c index a4fbd013..3144f940 100644 --- a/kmod/src/ioctl.c +++ b/kmod/src/ioctl.c @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include "format.h" #include "btree.h" @@ -25,6 +27,8 @@ #include "ioctl.h" #include "super.h" #include "inode.h" +#include "trans.h" +#include "filerw.h" /* * Find all the inodes that have had keys of a given type modified since @@ -290,6 +294,85 @@ static long scoutfs_ioc_data_version(struct file *file, unsigned long arg) return 0; } +/* + * The caller has a version of the data available in the given byte + * range in an external archive. As long as the data version still + * matches we free the blocks fully contained in the range and mark them + * offline. Attempts to use the blocks in the future will trigger + * recall from the archive. + * + * XXX permissions? + * XXX a lot of this could be generic file write prep + */ +static long scoutfs_ioc_release(struct file *file, unsigned long arg) +{ + struct inode *inode = file_inode(file); + struct super_block *sb = inode->i_sb; + struct scoutfs_ioctl_release args; + loff_t start; + loff_t end_inc; + u64 iblock; + u64 end_block; + u64 len; + int ret; + + if (copy_from_user(&args, (void __user *)arg, sizeof(args))) + return -EFAULT; + + if (args.count == 0) + return 0; + if ((args.offset + args.count) < args.offset) + return -EINVAL; + + start = round_up(args.offset, SCOUTFS_BLOCK_SIZE); + end_inc = round_down(args.offset + args.count, SCOUTFS_BLOCK_SIZE) - 1; + if (end_inc > start) + return 0; + + iblock = start >> SCOUTFS_BLOCK_SHIFT; + end_block = end_inc >> SCOUTFS_BLOCK_SHIFT; + len = end_block - iblock + 1; + + ret = mnt_want_write_file(file); + if (ret) + return ret; + + mutex_lock(&inode->i_mutex); + + if (!S_ISREG(inode->i_mode)) { + ret = -EINVAL; + goto out; + } + + if (!(file->f_mode & FMODE_WRITE)) { + ret = -EINVAL; + goto out; + } + + if (scoutfs_inode_get_data_version(inode) != args.data_version) { + ret = -ESTALE; + goto out; + } + + inode_dio_wait(inode); + + /* drop all clean and dirty cached blocks in the range */ + truncate_inode_pages_range(&inode->i_data, start, end_inc); + + ret = scoutfs_hold_trans(sb); + if (ret) + goto out; + + ret = scoutfs_truncate_extent_items(sb, scoutfs_ino(inode), + iblock, len, true); + scoutfs_release_trans(sb); +out: + mutex_unlock(&inode->i_mutex); + mnt_drop_write_file(file); + + return ret; +} + long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { switch (cmd) { @@ -305,6 +388,8 @@ long scoutfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) return scoutfs_ioc_inodes_since(file, arg, SCOUTFS_EXTENT_KEY); case SCOUTFS_IOC_DATA_VERSION: return scoutfs_ioc_data_version(file, arg); + case SCOUTFS_IOC_RELEASE: + return scoutfs_ioc_release(file, arg); } return -ENOTTY; diff --git a/kmod/src/ioctl.h b/kmod/src/ioctl.h index 964ccd92..1be28b9b 100644 --- a/kmod/src/ioctl.h +++ b/kmod/src/ioctl.h @@ -58,4 +58,13 @@ struct scoutfs_ioctl_find_xattr { #define SCOUTFS_IOC_DATA_VERSION _IOW(SCOUTFS_IOCTL_MAGIC, 6, u64) +struct scoutfs_ioctl_release { + __u64 offset; + __u64 count; + __u64 data_version; +} __packed; + +#define SCOUTFS_IOC_RELEASE _IOW(SCOUTFS_IOCTL_MAGIC, 7, \ + struct scoutfs_ioctl_release) + #endif