mirror of
https://github.com/versity/scoutfs.git
synced 2026-08-19 21:56:49 +00:00
Add offline extent flag and release ioctl
Add the _OFFLINE flag to indicate offline extents. The release ioctl frees extents within the release range and sets their _OFFLINE flag if the data_version still matches. We tweak the existing truncate item function just a bit to support making extents offline. We make it take an explicit range of blocks to remove instead of just giving it the size and it learns to mark extents offline and update them instead of always deleting them. Reads from offline extents return zeros like reading from a sparse region (later it will trigger demand staging) and writing to offline extents clears the offline flag (later only staging can do that). Signed-off-by: Zach Brown <zab@versity.com> Reviewed-by: Mark Fasheh <mfasheh@versity.com>
This commit is contained in:
+43
-26
@@ -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);
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/uio.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mount.h>
|
||||
#include <linux/mm.h>
|
||||
|
||||
#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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user