Add iomap support including direct I/O

Switch many code paths to use iomap instead of get_block. This includes
buffered reads and writes, llseek, fiemap, and new support for direct
I/O.

Signed-off-by: Chris Kirby <ckirby@versity.com>
This commit is contained in:
Chris Kirby
2026-06-29 09:49:28 -05:00
parent be25e37c8b
commit 573bbf76d1
16 changed files with 1383 additions and 298 deletions
+1
View File
@@ -25,6 +25,7 @@ scoutfs-y += \
forest.o \
inode.o \
ioctl.o \
iomap.o \
item.o \
kernelcompat.o \
lock.o \
+213 -232
View File
@@ -21,9 +21,11 @@
#include <linux/log2.h>
#include <linux/falloc.h>
#include <linux/fiemap.h>
#include <linux/iomap.h>
#include <linux/writeback.h>
#include <linux/overflow.h>
#include <linux/iversion.h>
#include <linux/fadvise.h>
#include "format.h"
#include "super.h"
@@ -42,6 +44,7 @@
#include "msg.h"
#include "ext.h"
#include "util.h"
#include "iomap.h"
/*
* We want to amortize work done after dirtying the shared transaction
@@ -63,12 +66,6 @@ struct data_info {
#define DECLARE_DATA_INFO(sb, name) \
struct data_info *name = SCOUTFS_SB(sb)->data_info
struct data_ext_args {
u64 ino;
struct inode *inode;
struct scoutfs_lock *lock;
};
static void item_from_extent(struct scoutfs_key *key,
struct scoutfs_data_extent_val *dv, u64 ino,
u64 start, u64 len, u64 map, u8 flags)
@@ -181,7 +178,7 @@ static int data_ext_remove(struct super_block *sb, void *arg, u64 start,
return ret;
}
static struct scoutfs_ext_ops data_ext_ops = {
struct scoutfs_ext_ops data_ext_ops = {
.next = data_ext_next,
.insert = data_ext_insert,
.remove = data_ext_remove,
@@ -386,9 +383,9 @@ static inline u64 ext_last(struct scoutfs_extent *ext)
* reasonable when a file population is known to be large and dense but
* known to be written with non-streaming write patterns.
*/
static int alloc_block(struct super_block *sb, struct inode *inode,
struct scoutfs_extent *ext, u64 iblock,
struct scoutfs_lock *lock)
int scoutfs_data_alloc_block(struct super_block *sb, struct inode *inode,
struct scoutfs_extent *ext, u64 iblock,
struct scoutfs_lock *lock)
{
DECLARE_DATA_INFO(sb, datinf);
struct scoutfs_mount_options opts;
@@ -611,6 +608,7 @@ static int scoutfs_get_block(struct inode *inode, sector_t iblock,
un.len = 1;
un.map = ext.map + (iblock - ext.start);
un.flags = ext.flags & ~(SEF_OFFLINE|SEF_UNWRITTEN);
ret = scoutfs_ext_set(sb, &data_ext_ops, &args,
un.start, un.len, un.map, un.flags);
if (ret == 0) {
@@ -622,7 +620,7 @@ static int scoutfs_get_block(struct inode *inode, sector_t iblock,
/* allocate and map blocks containing our logical block */
if (create && !ext.map) {
ret = alloc_block(sb, inode, &ext, iblock, lock);
ret = scoutfs_data_alloc_block(sb, inode, &ext, iblock, lock);
if (ret == 0)
set_buffer_new(bh);
} else {
@@ -653,11 +651,17 @@ static int scoutfs_get_block_read(struct inode *inode, sector_t iblock,
struct buffer_head *bh, int create)
{
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
void *extent_sem; /* Just a pointer */
int ret;
down_read(&si->extent_sem);
extent_sem = scoutfs_per_task_get(&si->pt_extent_sem);
if (extent_sem == NULL)
down_read(&si->extent_sem);
ret = scoutfs_get_block(inode, iblock, bh, create);
up_read(&si->extent_sem);
if (extent_sem == NULL)
up_read(&si->extent_sem);
return ret;
}
@@ -706,44 +710,55 @@ static int scoutfs_readpage(struct file *file, struct page *page)
struct scoutfs_lock *inode_lock = NULL;
SCOUTFS_DECLARE_PER_TASK_ENTRY(pt_ent);
DECLARE_DATA_WAIT(dw);
bool locked;
int flags;
int ret;
flags = SCOUTFS_LKF_REFRESH_INODE | SCOUTFS_LKF_NONBLOCK;
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_READ, flags, inode,
&inode_lock);
if (ret < 0) {
unlock_page(page);
if (ret == -EAGAIN) {
flags &= ~SCOUTFS_LKF_NONBLOCK;
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_READ, flags,
inode, &inode_lock);
if (ret == 0) {
scoutfs_unlock(sb, inode_lock,
SCOUTFS_LOCK_READ);
ret = AOP_TRUNCATED_PAGE;
}
}
return ret;
}
if (scoutfs_per_task_add_excl(&si->pt_data_lock, &pt_ent, inode_lock)) {
ret = scoutfs_data_wait_check(inode, page_offset(page),
PAGE_SIZE, SEF_OFFLINE,
SCOUTFS_IOC_DWO_READ, &dw,
inode_lock);
if (ret != 0) {
inode_lock = scoutfs_per_task_get(&si->pt_data_lock);
if (!inode_lock) {
flags = SCOUTFS_LKF_REFRESH_INODE | SCOUTFS_LKF_NONBLOCK;
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_READ, flags, inode,
&inode_lock);
if (ret < 0) {
unlock_page(page);
scoutfs_per_task_del(&si->pt_data_lock, &pt_ent);
scoutfs_unlock(sb, inode_lock, SCOUTFS_LOCK_READ);
}
if (ret > 0) {
ret = scoutfs_data_wait(inode, &dw);
if (ret == 0)
ret = AOP_TRUNCATED_PAGE;
}
if (ret != 0)
if (ret == -EAGAIN) {
flags &= ~SCOUTFS_LKF_NONBLOCK;
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_READ, flags,
inode, &inode_lock);
if (ret == 0) {
scoutfs_unlock(sb, inode_lock,
SCOUTFS_LOCK_READ);
ret = AOP_TRUNCATED_PAGE;
}
}
return ret;
}
locked = true;
if (scoutfs_per_task_add_excl(&si->pt_data_lock, &pt_ent, inode_lock)) {
ret = scoutfs_data_wait_check(inode, page_offset(page),
PAGE_SIZE, SEF_OFFLINE,
SCOUTFS_IOC_DWO_READ, &dw,
inode_lock);
if (ret != 0) {
unlock_page(page);
scoutfs_per_task_del(&si->pt_data_lock, &pt_ent);
scoutfs_unlock(sb, inode_lock, SCOUTFS_LOCK_READ);
locked = false;
}
if (ret > 0) {
ret = scoutfs_data_wait(inode, &dw);
if (ret == 0)
ret = AOP_TRUNCATED_PAGE;
}
if (ret != 0)
return ret;
} else {
WARN_ON_ONCE(true);
}
} else {
locked = false;
}
#ifdef KC_MPAGE_READ_FOLIO
@@ -752,8 +767,10 @@ static int scoutfs_readpage(struct file *file, struct page *page)
ret = mpage_readpage(page, scoutfs_get_block_read);
#endif
scoutfs_unlock(sb, inode_lock, SCOUTFS_LOCK_READ);
scoutfs_per_task_del(&si->pt_data_lock, &pt_ent);
if (locked) {
scoutfs_unlock(sb, inode_lock, SCOUTFS_LOCK_READ);
scoutfs_per_task_del(&si->pt_data_lock, &pt_ent);
}
return ret;
}
@@ -761,14 +778,22 @@ static int scoutfs_readpage(struct file *file, struct page *page)
static void scoutfs_readahead(struct readahead_control *rac)
{
struct inode *inode = rac->file->f_inode;
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
struct super_block *sb = inode->i_sb;
struct scoutfs_lock *inode_lock = NULL;
bool found_lock;
int ret;
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_READ,
SCOUTFS_LKF_REFRESH_INODE, inode, &inode_lock);
if (ret)
return;
inode_lock = scoutfs_per_task_get(&si->pt_data_lock);
if (!inode_lock) {
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_READ,
SCOUTFS_LKF_REFRESH_INODE, inode, &inode_lock);
if (ret)
return;
found_lock = false;
} else {
found_lock = true;
}
ret = scoutfs_data_wait_check(inode, readahead_pos(rac),
readahead_length(rac), SEF_OFFLINE,
@@ -777,7 +802,8 @@ static void scoutfs_readahead(struct readahead_control *rac)
if (ret == 0)
mpage_readahead(rac, scoutfs_get_block_read);
scoutfs_unlock(sb, inode_lock, SCOUTFS_LOCK_READ);
if (!found_lock)
scoutfs_unlock(sb, inode_lock, SCOUTFS_LOCK_READ);
}
static int scoutfs_writepage(struct page *page, struct writeback_control *wbc)
@@ -866,8 +892,8 @@ out:
}
/* kinda like __filemap_fdatawrite_range! :P */
static int writepages_sync_none(struct address_space *mapping, loff_t start,
loff_t end)
int scoutfs_writepages_sync_none(struct address_space *mapping,
loff_t start, loff_t end)
{
struct writeback_control wbc = {
.sync_mode = WB_SYNC_NONE,
@@ -879,12 +905,26 @@ static int writepages_sync_none(struct address_space *mapping, loff_t start,
return mapping->a_ops->writepages(mapping, &wbc);
}
void scoutfs_do_write_end(struct inode *inode, struct scoutfs_lock *data_lock,
struct list_head *ind_locks)
{
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
if (!si->staging) {
scoutfs_inode_set_data_seq(inode);
scoutfs_inode_inc_data_version(inode);
}
inode_inc_iversion(inode);
scoutfs_update_inode_item(inode, data_lock, ind_locks);
scoutfs_inode_queue_writeback(inode);
}
static int scoutfs_write_end(struct file *file, struct address_space *mapping,
loff_t pos, unsigned len, unsigned copied,
struct page *page, void *fsdata)
{
struct inode *inode = mapping->host;
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
struct super_block *sb = inode->i_sb;
struct write_begin_data *wbd = fsdata;
int ret;
@@ -893,16 +933,9 @@ static int scoutfs_write_end(struct file *file, struct address_space *mapping,
len, copied);
ret = generic_write_end(file, mapping, pos, len, copied, page, fsdata);
if (ret > 0) {
if (!si->staging) {
scoutfs_inode_set_data_seq(inode);
scoutfs_inode_inc_data_version(inode);
}
if (ret > 0)
scoutfs_do_write_end(inode, wbd->lock, &wbd->ind_locks);
inode_inc_iversion(inode);
scoutfs_update_inode_item(inode, wbd->lock, &wbd->ind_locks);
scoutfs_inode_queue_writeback(inode);
}
scoutfs_release_trans(sb);
scoutfs_inode_index_unlock(sb, &wbd->ind_locks);
kfree(wbd);
@@ -914,18 +947,16 @@ static int scoutfs_write_end(struct file *file, struct address_space *mapping,
* to very long commit latencies with lots of dirty file data.
*
* This hack tries to minimize these writeback latencies while
* keeping concurrent large file strreaming writes from
* keeping concurrent large file streaming writes from
* suffering too terribly. Every N bytes we kick off background
* writbeack on the previous N bytes. By the time transaction
* writeback on the previous N bytes. By the time transaction
* commit comes along it will find that dirty file blocks have
* already been written.
*/
#define BACKGROUND_WRITEBACK_BYTES (16 * 1024 * 1024)
#define BACKGROUND_WRITEBACK_MASK (BACKGROUND_WRITEBACK_BYTES - 1)
if (ret > 0 && ((pos + ret) & BACKGROUND_WRITEBACK_MASK) == 0)
writepages_sync_none(mapping,
pos + ret - BACKGROUND_WRITEBACK_BYTES,
pos + ret - 1);
scoutfs_writepages_sync_none(mapping,
pos + ret - BACKGROUND_WRITEBACK_BYTES,
pos + ret - 1);
return ret;
}
@@ -1091,7 +1122,7 @@ long scoutfs_fallocate(struct file *file, int mode, loff_t offset, loff_t len)
iblock = offset >> SCOUTFS_BLOCK_SM_SHIFT;
last = (offset + len - 1) >> SCOUTFS_BLOCK_SM_SHIFT;
while(iblock <= last) {
while (iblock <= last) {
ret = scoutfs_quota_check_data(sb, inode);
if (ret)
@@ -1142,6 +1173,37 @@ out:
return ret;
}
static int scoutfs_fadvise(struct file *file, loff_t start, loff_t end, int advice)
{
struct inode *inode = file_inode(file);
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
SCOUTFS_DECLARE_PER_TASK_ENTRY(pt_extent_ent);
bool locked = false;
int ret;
/*
* We need to get the extent_sem now, or we'll be out of order with the
* mapping.invalidate_lock.
*/
if (advice == POSIX_FADV_WILLNEED) {
down_read(&si->extent_sem);
locked = true;
if (!scoutfs_per_task_add_excl(&si->pt_extent_sem, &pt_extent_ent,
&pt_extent_ent))
WARN_ON_ONCE(true);
}
ret = generic_fadvise(file, start, end, advice);
if (locked) {
scoutfs_per_task_del(&si->pt_extent_sem, &pt_extent_ent);
up_read(&si->extent_sem);
}
return ret;
}
/*
* A special case of initializing a single large offline extent. This
* chooses not to deal with any existing extents. It can only be used
@@ -1439,7 +1501,6 @@ int scoutfs_data_move_blocks(struct inode *from, u64 from_off,
from_start += len;
}
up_write(&from_si->extent_sem);
up_write(&to_si->extent_sem);
@@ -1572,163 +1633,47 @@ out:
return ret;
}
/*
* This copies to userspace :/
*/
static int fill_extent(struct fiemap_extent_info *fieinfo,
struct scoutfs_extent *ext, u32 fiemap_flags)
{
u32 flags;
if (ext->len == 0)
return 0;
flags = fiemap_flags;
if (ext->flags & SEF_OFFLINE)
flags |= FIEMAP_EXTENT_UNKNOWN;
else if (ext->flags & SEF_UNWRITTEN)
flags |= FIEMAP_EXTENT_UNWRITTEN;
return fiemap_fill_next_extent(fieinfo,
ext->start << SCOUTFS_BLOCK_SM_SHIFT,
ext->map << SCOUTFS_BLOCK_SM_SHIFT,
ext->len << SCOUTFS_BLOCK_SM_SHIFT,
flags);
}
/*
* Return all the file's extents whose blocks overlap with the caller's
* byte region. We set _LAST on the last extent and _UNKNOWN on offline
* extents.
*/
int scoutfs_data_fiemap(struct inode *inode, struct fiemap_extent_info *fieinfo,
u64 start, u64 len)
{
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
struct super_block *sb = inode->i_sb;
const u64 ino = scoutfs_ino(inode);
struct scoutfs_lock *lock = NULL;
struct scoutfs_extent *info = NULL;
struct page *page = NULL;
struct scoutfs_extent ext;
struct scoutfs_extent cur;
struct data_ext_args args;
u32 last_flags;
u64 iblock;
u64 last;
int entries = 0;
SCOUTFS_DECLARE_PER_TASK_ENTRY(pt_ent);
int ret;
int complete = 0;
if (len == 0) {
ret = 0;
ret = -EINVAL;
goto out;
}
ret = fiemap_prep(inode, fieinfo, start, &len, FIEMAP_FLAG_SYNC);
/*
* We don't support xattrs stored in extents. The magic incantation
* to communicate this to the caller is to reset fi_flags to
* FIEMAP_FLAG_XATTR and return -EBADR.
*/
if (fieinfo->fi_flags & FIEMAP_FLAG_XATTR) {
fieinfo->fi_flags = FIEMAP_FLAG_XATTR;
ret = -EBADR;
goto out;
}
inode_lock(inode);
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_READ, 0, inode, &lock);
if (ret)
goto out;
page = alloc_page(GFP_KERNEL);
if (!page) {
ret = -ENOMEM;
goto out;
}
scoutfs_per_task_add_excl(&si->pt_data_lock, &pt_ent, lock);
/* use a dummy extent to track */
memset(&cur, 0, sizeof(cur));
last_flags = 0;
ret = iomap_fiemap(inode, fieinfo, start, len, &scoutfs_iomap_report_ops);
iblock = start >> SCOUTFS_BLOCK_SM_SHIFT;
last = (start + len - 1) >> SCOUTFS_BLOCK_SM_SHIFT;
scoutfs_per_task_del(&si->pt_data_lock, &pt_ent);
scoutfs_unlock(sb, lock, SCOUTFS_LOCK_READ);
args.ino = ino;
args.inode = inode;
/* outer loop */
while (iblock <= last) {
/* lock */
inode_lock(inode);
down_read(&si->extent_sem);
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_READ, 0, inode, &lock);
if (ret) {
up_read(&si->extent_sem);
inode_unlock(inode);
break;
}
args.lock = lock;
/* collect entries */
info = page_address(page);
memset(info, 0, PAGE_SIZE);
while (entries < (PAGE_SIZE / sizeof(struct fiemap_extent)) - 1) {
ret = scoutfs_ext_next(sb, &data_ext_ops, &args,
iblock, 1, &ext);
if (ret < 0) {
if (ret == -ENOENT)
ret = 0;
complete = 1;
last_flags = FIEMAP_EXTENT_LAST;
break;
}
trace_scoutfs_data_fiemap_extent(sb, ino, &ext);
if (ext.start > last) {
/* not setting _LAST, it's for end of file */
ret = 0;
complete = 1;
break;
}
if (scoutfs_ext_can_merge(&cur, &ext)) {
/* merged extents could be greater than input len */
cur.len += ext.len;
} else {
/* fill it */
memcpy(info, &cur, sizeof(cur));
entries++;
info++;
cur = ext;
}
iblock = ext.start + ext.len;
}
/* unlock */
scoutfs_unlock(sb, lock, SCOUTFS_LOCK_READ);
up_read(&si->extent_sem);
inode_unlock(inode);
if (ret)
break;
/* emit entries */
info = page_address(page);
for (; entries > 0; entries--) {
ret = fill_extent(fieinfo, info, 0);
if (ret != 0)
goto out;
info++;
}
if (complete)
break;
}
/* still one left, it's in cur */
if (cur.len)
ret = fill_extent(fieinfo, &cur, last_flags);
inode_unlock(inode);
out:
if (ret == 1)
ret = 0;
if (page)
__free_page(page);
trace_scoutfs_data_fiemap(sb, start, len, ret);
return ret;
@@ -1832,6 +1777,7 @@ int scoutfs_data_wait_check(struct inode *inode, loff_t pos, loff_t len,
u64 on;
u64 off;
loff_t tmp;
void *semlock = NULL;
int ret = 0;
if (len == 0)
@@ -1845,7 +1791,7 @@ int scoutfs_data_wait_check(struct inode *inode, loff_t pos, loff_t len,
goto out;
}
if ((sef & SEF_OFFLINE)) {
if (sef & SEF_OFFLINE) {
scoutfs_inode_get_onoff(inode, &on, &off);
if (off == 0) {
ret = 0;
@@ -1853,12 +1799,14 @@ int scoutfs_data_wait_check(struct inode *inode, loff_t pos, loff_t len,
}
}
down_read(&si->extent_sem);
semlock = scoutfs_per_task_get(&si->pt_extent_sem);
if (semlock == NULL)
down_read(&si->extent_sem);
iblock = pos >> SCOUTFS_BLOCK_SM_SHIFT;
last_block = (pos + len - 1) >> SCOUTFS_BLOCK_SM_SHIFT;
while(iblock <= last_block) {
while (iblock <= last_block) {
ret = scoutfs_ext_next(sb, &data_ext_ops, &args,
iblock, 1, &ext);
if (ret < 0) {
@@ -1891,7 +1839,8 @@ int scoutfs_data_wait_check(struct inode *inode, loff_t pos, loff_t len,
iblock = ext.start + ext.len;
}
up_read(&si->extent_sem);
if (semlock == NULL)
up_read(&si->extent_sem);
out:
trace_scoutfs_data_wait_check(sb, ino, pos, len, sef, op, &ext, ret);
@@ -2168,40 +2117,70 @@ static vm_fault_t scoutfs_data_filemap_fault(struct vm_fault *vmf)
struct super_block *sb = inode->i_sb;
struct scoutfs_lock *inode_lock = NULL;
SCOUTFS_DECLARE_PER_TASK_ENTRY(pt_ent);
SCOUTFS_DECLARE_PER_TASK_ENTRY(pt_sem);
DECLARE_DATA_WAIT(dw);
loff_t pos;
int err;
vm_fault_t ret = VM_FAULT_SIGBUS;
void *semlock;
bool found_lock;
pos = vmf->pgoff;
pos <<= PAGE_SHIFT;
retry:
err = scoutfs_lock_inode(sb, SCOUTFS_LOCK_READ,
SCOUTFS_LKF_REFRESH_INODE, inode, &inode_lock);
if (err < 0)
return vmf_error(err);
inode_lock = scoutfs_per_task_get(&si->pt_data_lock);
if (!inode_lock) {
found_lock = false;
if (scoutfs_per_task_add_excl(&si->pt_data_lock, &pt_ent, inode_lock)) {
/* protect checked extents from stage/release */
atomic_inc(&inode->i_dio_count);
err = scoutfs_lock_inode(sb, SCOUTFS_LOCK_READ,
SCOUTFS_LKF_REFRESH_INODE, inode, &inode_lock);
if (err < 0)
return vmf_error(err);
err = scoutfs_data_wait_check(inode, pos, PAGE_SIZE,
SEF_OFFLINE, SCOUTFS_IOC_DWO_READ,
&dw, inode_lock);
if (err != 0) {
if (err < 0)
ret = vmf_error(err);
goto out;
if (scoutfs_per_task_add_excl(&si->pt_data_lock, &pt_ent, inode_lock)) {
/* protect checked extents from stage/release */
inode_dio_begin(inode);
err = scoutfs_data_wait_check(inode, pos, PAGE_SIZE,
SEF_OFFLINE, SCOUTFS_IOC_DWO_READ,
&dw, inode_lock);
if (err != 0) {
if (err < 0)
ret = vmf_error(err);
goto out;
}
} else {
WARN_ON_ONCE(true);
}
} else {
found_lock = true;
}
semlock = scoutfs_per_task_get(&si->pt_extent_sem);
if (semlock == NULL) {
down_read(&si->extent_sem);
if (!scoutfs_per_task_add_excl(&si->pt_extent_sem, &pt_sem, &semlock))
WARN_ON_ONCE(true);
}
ret = filemap_fault(vmf);
if (semlock == NULL) {
up_read(&si->extent_sem);
scoutfs_per_task_del(&si->pt_extent_sem, &pt_sem);
}
out:
if (scoutfs_per_task_del(&si->pt_data_lock, &pt_ent))
inode_dio_end(inode);
scoutfs_unlock(sb, inode_lock, SCOUTFS_LOCK_READ);
if (!found_lock) {
if (scoutfs_per_task_del(&si->pt_data_lock, &pt_ent))
inode_dio_end(inode);
else
WARN_ON_ONCE(true);
scoutfs_unlock(sb, inode_lock, SCOUTFS_LOCK_READ);
}
if (scoutfs_data_wait_found(&dw)) {
err = scoutfs_data_wait(inode, &dw);
if (err == 0)
@@ -2229,6 +2208,7 @@ static int scoutfs_file_mmap(struct file *file, struct vm_area_struct *vma)
const struct address_space_operations scoutfs_file_aops = {
#ifdef KC_MPAGE_READ_FOLIO
.direct_IO = noop_direct_IO,
.dirty_folio = block_dirty_folio,
.invalidate_folio = block_invalidate_folio,
.read_folio = scoutfs_read_folio,
@@ -2252,6 +2232,7 @@ const struct file_operations scoutfs_file_fops = {
.fsync = scoutfs_file_fsync,
.llseek = scoutfs_file_llseek,
.fallocate = scoutfs_fallocate,
.fadvise = scoutfs_fadvise,
};
void scoutfs_data_init_btrees(struct super_block *sb,
+20
View File
@@ -38,10 +38,30 @@ struct scoutfs_data_wait {
.err = 0, \
}
struct data_ext_args {
u64 ino;
struct inode *inode;
struct scoutfs_lock *lock;
};
extern const struct address_space_operations scoutfs_file_aops;
extern const struct file_operations scoutfs_file_fops;
extern struct scoutfs_ext_ops data_ext_ops;
struct scoutfs_alloc;
struct scoutfs_block_writer;
struct scoutfs_extent;
#define BACKGROUND_WRITEBACK_BYTES (16 * 1024 * 1024)
#define BACKGROUND_WRITEBACK_MASK (BACKGROUND_WRITEBACK_BYTES - 1)
int scoutfs_writepages_sync_none(struct address_space *mapping, loff_t start, loff_t end);
void scoutfs_do_write_end(struct inode *inode, struct scoutfs_lock *data_lock,
struct list_head *ind_locks);
int scoutfs_data_alloc_block(struct super_block *sb, struct inode *inode,
struct scoutfs_extent *ext, u64 iblock,
struct scoutfs_lock *lock);
int scoutfs_get_block_write(struct inode *inode, sector_t iblock, struct buffer_head *bh,
int create);
+2
View File
@@ -348,6 +348,8 @@ int scoutfs_ext_set(struct super_block *sb, struct scoutfs_ext_ops *ops,
if (ret == 0 && ext_overlap(&found, start, len)) {
/* set extent must be entirely within found */
if (!scoutfs_ext_inside(start, len, &found)) {
scoutfs_err(sb, "@@@ ext_set: start %llu, len %llu not fully within found start %llu, len %llu, flags 0x%x",
start, len, found.start, found.len, found.flags);
ret = -EINVAL;
goto out;
}
+740 -38
View File
@@ -18,6 +18,7 @@
#include <linux/mpage.h>
#include <linux/sched.h>
#include <linux/aio.h>
#include <linux/iomap.h>
#include "format.h"
#include "super.h"
@@ -29,6 +30,665 @@
#include "per_task.h"
#include "omap.h"
#include "quota.h"
#include "iomap.h"
#include "trans.h"
#include "msg.h"
static int lock_for_iomap_read(struct inode *inode, bool nowait,
struct scoutfs_per_task_entry *pt_extent_ent)
{
#if 0
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
if (nowait) {
if (!down_read_trylock(&si->extent_sem))
return -EAGAIN;
} else {
down_read(&si->extent_sem);
}
if (!scoutfs_per_task_add_excl(&si->pt_extent_sem, pt_extent_ent, pt_extent_ent))
WARN_ON_ONCE(true);
#endif
return 0;
}
static void unlock_for_iomap_read(struct inode *inode,
struct scoutfs_per_task_entry *pt_extent_ent)
{
#if 0
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
scoutfs_per_task_del(&si->pt_extent_sem, pt_extent_ent);
up_read(&si->extent_sem);
#endif
}
#ifdef KC_USE_IOMAP_FOR_IO
static bool scoutfs_should_use_dio(struct kiocb *iocb, struct iov_iter *iter)
{
/* Current offset must be aligned */
if (iocb->ki_pos & SCOUTFS_BLOCK_SM_MASK)
return false;
if (iov_iter_alignment(iter) & SCOUTFS_BLOCK_SM_MASK)
return false;
return true;
}
/* copied from fs/gfs2/file.c */
static inline bool should_fault_in_pages(struct iov_iter *i,
struct kiocb *iocb,
size_t *prev_count,
size_t *window_size)
{
size_t count = iov_iter_count(i);
size_t size, offs;
if (!count)
return false;
if (!user_backed_iter(i))
return false;
size = PAGE_SIZE;
offs = offset_in_page(iocb->ki_pos);
if (*prev_count != count || !*window_size) {
size_t nr_dirtied;
nr_dirtied = max(current->nr_dirtied_pause -
current->nr_dirtied, 8);
size = min_t(size_t, SZ_1M, nr_dirtied << PAGE_SHIFT);
}
*prev_count = count;
*window_size = size - offs;
return true;
}
static ssize_t scoutfs_file_buffered_read(struct kiocb *iocb, struct iov_iter *to)
{
struct inode *inode = file_inode(iocb->ki_filp);
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
SCOUTFS_DECLARE_PER_TASK_ENTRY(pt_extent_ent);
size_t prev_count = 0;
size_t window_size = 0;
size_t read = 0;
bool locked = false;
ssize_t ret;
pagefault_disable();
iocb->ki_flags |= IOCB_NOIO;
ret = generic_file_read_iter(iocb, to);
iocb->ki_flags &= ~IOCB_NOIO;
pagefault_enable();
if (ret >= 0) {
if (iov_iter_count(to) == 0)
return ret;
read = ret;
} else if (ret != -EFAULT) {
if (ret != -EAGAIN)
return ret;
}
retry:
down_read(&si->extent_sem);
if (!scoutfs_per_task_add_excl(&si->pt_extent_sem, &pt_extent_ent,
&pt_extent_ent))
WARN_ON_ONCE(true);
#if 0
ret = lock_for_iomap_read(inode, false, &pt_extent_ent);
if (ret)
return ret;
#endif
locked = true;
pagefault_disable();
ret = generic_file_read_iter(iocb, to);
pagefault_enable();
if (ret <= 0 && ret != -EFAULT)
goto out;
if (ret > 0)
read += ret;
if (should_fault_in_pages(to, iocb, &prev_count, &window_size)) {
scoutfs_per_task_del(&si->pt_extent_sem, &pt_extent_ent);
up_read(&si->extent_sem);
#if 0
unlock_for_iomap_read(inode, &pt_extent_ent);
#endif
locked = false;
window_size -= fault_in_iov_iter_writeable(to, window_size);
if (window_size != 0)
goto retry;
}
out:
if (locked) {
#if 1
scoutfs_per_task_del(&si->pt_extent_sem, &pt_extent_ent);
up_read(&si->extent_sem);
#else
unlock_for_iomap_read(inode, &pt_extent_ent);
#endif
}
return read ? read : ret;
}
static ssize_t scoutfs_file_direct_read(struct kiocb *iocb, struct iov_iter *to,
bool nowait)
{
struct file *file = iocb->ki_filp;
struct inode *inode = file_inode(file);
SCOUTFS_DECLARE_PER_TASK_ENTRY(pt_extent_ent);
size_t prev_count = 0;
size_t window_size = 0;
size_t read = 0;
bool locked = false;
ssize_t ret;
if (!scoutfs_should_use_dio(iocb, to)) {
iocb->ki_flags &= ~IOCB_DIRECT;
return scoutfs_file_buffered_read(iocb, to);
}
retry:
ret = lock_for_iomap_read(inode, nowait, &pt_extent_ent);
if (ret)
return ret;
locked = true;
pagefault_disable();
to->nofault = true;
ret = iomap_dio_rw(iocb, to, &scoutfs_iomap_ops, NULL, IOMAP_DIO_PARTIAL, 0);
to->nofault = false;
pagefault_enable();
if (ret <= 0 && ret != -EFAULT)
goto out_unlock;
if (ret > 0)
read = ret;
if (should_fault_in_pages(to, iocb, &prev_count, &window_size)) {
unlock_for_iomap_read(inode, &pt_extent_ent);
locked = false;
window_size -= fault_in_iov_iter_writeable(to, window_size);
if (window_size != 0)
goto retry;
}
out_unlock:
if (locked)
unlock_for_iomap_read(inode, &pt_extent_ent);
file_accessed(file);
if (ret < 0)
return ret;
return read;
}
ssize_t scoutfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
struct file *file = iocb->ki_filp;
struct inode *inode = file_inode(file);
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
struct super_block *sb = inode->i_sb;
struct scoutfs_lock *scoutfs_inode_lock;
SCOUTFS_DECLARE_PER_TASK_ENTRY(pt_data_ent);
DECLARE_DATA_WAIT(dw);
int lock_flags = SCOUTFS_LKF_REFRESH_INODE;
bool is_dio = (iocb->ki_flags & IOCB_DIRECT);
bool nowait = (iocb->ki_flags & IOCB_NOWAIT);
ssize_t ret;
/* IOCB_NOWAIT is only for direct I/O */
if (!is_dio && nowait)
return -EOPNOTSUPP;
retry:
scoutfs_inode_lock = NULL;
/* protect checked extents from release */
inode_lock(inode);
atomic_inc(&inode->i_dio_count);
inode_unlock(inode);
if (is_dio) {
if (nowait) {
if (!inode_trylock_shared(inode)) {
ret = -EAGAIN;
goto out;
}
lock_flags |= SCOUTFS_LKF_NONBLOCK;
} else {
inode_lock_shared(inode);
}
}
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_READ,
lock_flags, inode, &scoutfs_inode_lock);
if (ret)
goto out;
if (scoutfs_per_task_add_excl(&si->pt_data_lock, &pt_data_ent,
scoutfs_inode_lock)) {
ret = scoutfs_data_wait_check(inode, iocb->ki_pos, iov_iter_count(to),
SEF_OFFLINE, SCOUTFS_IOC_DWO_READ, &dw,
scoutfs_inode_lock);
if (ret != 0)
goto out;
} else {
WARN_ON_ONCE(true);
}
if (is_dio)
ret = scoutfs_file_direct_read(iocb, to, nowait);
else
ret = scoutfs_file_buffered_read(iocb, to);
out:
inode_dio_end(inode);
if (scoutfs_inode_lock) {
scoutfs_per_task_del(&si->pt_data_lock, &pt_data_ent);
scoutfs_unlock(sb, scoutfs_inode_lock, SCOUTFS_LOCK_READ);
}
if (is_dio)
inode_unlock_shared(inode);
if (scoutfs_data_wait_found(&dw)) {
if (!nowait) {
ret = scoutfs_data_wait(inode, &dw);
if (ret == 0)
goto retry;
} else {
ret = -EAGAIN;
}
}
return ret;
}
/*
* We need to complete all of our locking before we get into the iomap iterator.
* The lock hierarchy isn't well defined, and some paths reverse the ordering of
* the inode index lock and the extent_sem because of how the get_block functions
* are called. So we have to use trylocks and retry as needed, unless nonblocking
* mode was requested.
*/
static int lock_for_iomap_write(struct inode *inode, struct list_head *ind_locks,
struct scoutfs_per_task_entry *pt_inode_ent,
struct scoutfs_per_task_entry *pt_extent_ent,
struct scoutfs_lock *scoutfs_inode_lock,
bool nowait)
{
struct super_block *sb = inode->i_sb;
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
u64 seq;
int ret;
do {
ret = scoutfs_inode_index_start(sb, &seq) ?:
scoutfs_inode_index_prepare(sb, ind_locks, inode, true) ?:
scoutfs_inode_index_try_lock_hold(sb, ind_locks, seq, true);
if (ret < 0)
return ret;
/* A return value > 0 means the seq number changed */
if (ret > 0) {
if (nowait)
return -EAGAIN;
continue;
}
#if 0
if (!down_write_trylock(&si->extent_sem)) {
scoutfs_release_trans(sb);
scoutfs_inode_index_unlock(sb, ind_locks);
if (nowait)
return -EAGAIN;
continue;
}
#endif
scoutfs_per_task_add_excl(&si->pt_inode_locks, pt_inode_ent, ind_locks);
#if 0
/*
* We need to assign a pointer value to the per-task var. Since this
* one is just a marker that we hold the extent_sem, it can be anything
* that's non-NULL. So just use the entry itself.
*/
scoutfs_per_task_add_excl(&si->pt_extent_sem, pt_extent_ent,
pt_extent_ent);
#endif
ret = scoutfs_dirty_inode_item(inode, scoutfs_inode_lock);
break;
} while (true);
return ret;
}
static void unlock_for_iomap_write(struct inode *inode,
struct scoutfs_lock *scoutfs_inode_lock,
struct list_head *ind_locks,
struct scoutfs_per_task_entry *pt_inode_ent,
struct scoutfs_per_task_entry *pt_extent_ent,
size_t written)
{
struct super_block *sb = inode->i_sb;
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
bool t;
if (written > 0)
scoutfs_do_write_end(inode, scoutfs_inode_lock, ind_locks);
scoutfs_release_trans(sb);
t = scoutfs_per_task_del(&si->pt_inode_locks, pt_inode_ent);
BUG_ON(!t);
scoutfs_inode_index_unlock(sb, ind_locks);
#if 0
t = scoutfs_per_task_del(&si->pt_extent_sem, pt_extent_ent);
BUG_ON(!t);
up_write(&si->extent_sem);
#endif
}
static ssize_t scoutfs_file_direct_write(struct kiocb *iocb, struct iov_iter *from,
struct scoutfs_lock *scoutfs_inode_lock)
{
struct file *file = iocb->ki_filp;
struct inode *inode = file_inode(file);
bool nowait = (iocb->ki_flags & IOCB_NOWAIT);
SCOUTFS_DECLARE_PER_TASK_ENTRY(pt_inode_ent);
SCOUTFS_DECLARE_PER_TASK_ENTRY(pt_extent_ent);
LIST_HEAD(ind_locks);
size_t prev_count = 0;
size_t window_size = 0;
size_t written = 0;
ssize_t ret = 0;
bool locked = false;
if (!scoutfs_should_use_dio(iocb, from)) {
iocb->ki_flags &= ~IOCB_DIRECT;
goto out;
}
retry:
ret = lock_for_iomap_write(inode, &ind_locks, &pt_inode_ent, &pt_extent_ent,
scoutfs_inode_lock, nowait);
if (ret < 0)
goto out;
locked = true;
/*
* Due to lock ordering issues, we need to disable page faults while we're
* doing the iomap iterations. Pass IOMAP_DIO_PARTIAL so that the iomap
* code knows it's OK to return a partial result. We will try to fault in
* any needed pages later on.
*/
from->nofault = true;
ret = iomap_dio_rw(iocb, from, &scoutfs_iomap_ops, NULL,
IOMAP_DIO_PARTIAL | IOMAP_DIO_FORCE_WAIT,
written);
from->nofault = false;
if (ret <= 0) {
if (ret == -ENOTBLK)
ret = 0;
if (ret != -EFAULT)
goto out;
}
/* No increment (+=) because iomap returns a cumulative value. */
if (ret > 0)
written = ret;
/*
* We might have skipped some pages that needed to be faulted in. If so, drop
* our locks to avoid deadlock and try to fault them in. Then we can relock
* and try the remaining DIO writes.
*/
if (should_fault_in_pages(from, iocb, &prev_count, &window_size)) {
unlock_for_iomap_write(inode, scoutfs_inode_lock, &ind_locks,
&pt_inode_ent, &pt_extent_ent, written);
locked = false;
window_size -= fault_in_iov_iter_readable(from, window_size);
if (window_size != 0)
goto retry;
}
out:
if (locked) {
unlock_for_iomap_write(inode, scoutfs_inode_lock, &ind_locks,
&pt_inode_ent, &pt_extent_ent, written);
}
return ret < 0 ? ret : written;
}
static ssize_t scoutfs_file_buffered_write(struct kiocb *iocb, struct iov_iter *from,
struct scoutfs_lock *scoutfs_inode_lock)
{
struct file *file = iocb->ki_filp;
struct inode *inode = file_inode(file);
SCOUTFS_DECLARE_PER_TASK_ENTRY(pt_inode_ent);
SCOUTFS_DECLARE_PER_TASK_ENTRY(pt_extent_ent);
LIST_HEAD(ind_locks);
size_t prev_count = 0;
size_t window_size = 0;
size_t orig_count = iov_iter_count(from);
size_t written = 0;
bool locked;
ssize_t ret;
retry:
locked = false;
/*
* Because of lock ordering issues with the page fault code, we need to
* try to manually fault in any pages before acquiring locks. Then we
* disable page faults while doing the iomap iterations.
*/
if (should_fault_in_pages(from, iocb, &prev_count, &window_size)) {
window_size -= fault_in_iov_iter_readable(from, window_size);
if (window_size == 0) {
ret = -EFAULT;
goto out;
}
from->count = min(from->count, window_size);
}
ret = lock_for_iomap_write(inode, &ind_locks, &pt_inode_ent, &pt_extent_ent,
scoutfs_inode_lock, false);
if (ret < 0)
goto out;
locked = true;
pagefault_disable();
ret = iomap_file_buffered_write(iocb, from, &scoutfs_iomap_ops);
pagefault_enable();
/* Accumulate the count of what's been written so far */
if (ret > 0)
written += ret;
if (ret <= 0 && ret != -EFAULT)
goto out;
from->count = orig_count - written;
if (should_fault_in_pages(from, iocb, &prev_count, &window_size)) {
/*
* There are still some pages to be faulted in. Drop our locks and
* try another pass.
*/
unlock_for_iomap_write(inode, scoutfs_inode_lock, &ind_locks,
&pt_inode_ent, &pt_extent_ent, written);
locked = false;
goto retry;
}
out:
if (locked) {
unlock_for_iomap_write(inode, scoutfs_inode_lock, &ind_locks,
&pt_inode_ent, &pt_extent_ent, written);
}
from->count = orig_count - written;
return written ? written : ret;
}
ssize_t scoutfs_file_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
struct file *file = iocb->ki_filp;
struct inode *inode = file_inode(file);
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
struct super_block *sb = inode->i_sb;
struct scoutfs_lock *scoutfs_inode_lock = NULL;
SCOUTFS_DECLARE_PER_TASK_ENTRY(pt_data_ent);
DECLARE_DATA_WAIT(dw);
int lock_flags = SCOUTFS_LKF_REFRESH_INODE;
bool added_pt_data;
bool is_dio = (iocb->ki_flags & IOCB_DIRECT);
bool nowait = (iocb->ki_flags & IOCB_NOWAIT);
bool is_sync = (iocb->ki_flags & IOCB_DSYNC);
ssize_t buffered;
ssize_t ret;
ssize_t ret2;
/* We don't support O_DSYNC */
iocb->ki_flags &= ~IOCB_DSYNC;
/* IOCB_NOWAIT is only for direct I/O */
if (!is_dio && nowait)
return -EOPNOTSUPP;
if (nowait)
lock_flags |= SCOUTFS_LKF_NONBLOCK;
retry:
added_pt_data = false;
if (nowait) {
if (!inode_trylock(inode)) {
return -EAGAIN;
}
} else {
inode_lock(inode);
}
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_WRITE, lock_flags,
inode, &scoutfs_inode_lock);
if (ret == -EAGAIN)
goto out;
if (ret < 0)
goto out;
ret = generic_write_checks(iocb, from);
if (ret <= 0)
goto out;
ret = scoutfs_inode_check_retention(inode);
if (ret < 0)
goto out;
ret = scoutfs_complete_truncate(inode, scoutfs_inode_lock);
if (ret)
goto out;
ret = scoutfs_quota_check_data(sb, inode);
if (ret)
goto out;
if (scoutfs_per_task_add_excl(&si->pt_data_lock, &pt_data_ent,
scoutfs_inode_lock)) {
/* data_version is per inode, whole file must be online */
/* @@@ need to handle nowait here too? */
added_pt_data = true;
ret = scoutfs_data_wait_check(inode, 0, i_size_read(inode), SEF_OFFLINE,
SCOUTFS_IOC_DWO_WRITE, &dw,
scoutfs_inode_lock);
if (ret != 0)
goto out;
}
/* XXX: remove SUID bit */
if (is_dio) {
ret = scoutfs_file_direct_write(iocb, from, scoutfs_inode_lock);
if (ret < 0 || !iov_iter_count(from))
goto out;
buffered = scoutfs_file_buffered_write(iocb, from, scoutfs_inode_lock);
if (buffered <= 0) {
if (ret == 0)
ret = buffered;
goto out;
}
/*
* Ensure all data is persisted. We want the next direct IO read to be
* able to read what was just written. If this fails, just return the
* byte count written by direct I/O, since we don't know if the
* buffered pages made it to disk.
*/
ret2 = generic_write_sync(iocb, buffered);
invalidate_mapping_pages(file->f_mapping,
(iocb->ki_pos - buffered) >> PAGE_SHIFT,
(iocb->ki_pos - 1) >> PAGE_SHIFT);
if (ret == 0 || ret2 >= 0)
ret += ret2;
} else {
ret = scoutfs_file_buffered_write(iocb, from, scoutfs_inode_lock);
if (ret > 0)
ret = generic_write_sync(iocb, ret);
}
out:
if (added_pt_data) {
scoutfs_per_task_del(&si->pt_data_lock, &pt_data_ent);
added_pt_data = false;
}
scoutfs_unlock(sb, scoutfs_inode_lock, SCOUTFS_LOCK_WRITE);
inode_unlock(inode);
if (scoutfs_data_wait_found(&dw)) {
if (!nowait) {
ret = scoutfs_data_wait(inode, &dw);
if (ret == 0)
goto retry;
} else {
ret = -EAGAIN;
}
}
if (is_sync)
iocb->ki_flags |= IOCB_DSYNC;
return ret;
}
#else
ssize_t scoutfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
@@ -38,10 +698,14 @@ ssize_t scoutfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
struct super_block *sb = inode->i_sb;
struct scoutfs_lock *scoutfs_inode_lock = NULL;
SCOUTFS_DECLARE_PER_TASK_ENTRY(pt_ent);
SCOUTFS_DECLARE_PER_TASK_ENTRY(pt_extent_ent);
DECLARE_DATA_WAIT(dw);
bool locked;
int ret;
retry:
locked = false;
/* protect checked extents from release */
inode_lock(inode);
atomic_inc(&inode->i_dio_count);
@@ -52,6 +716,12 @@ retry:
if (ret)
goto out;
ret = lock_for_iomap_read(inode, false, &pt_extent_ent);
if (ret)
return ret;
locked = true;
if (scoutfs_per_task_add_excl(&si->pt_data_lock, &pt_ent, scoutfs_inode_lock)) {
ret = scoutfs_data_wait_check(inode, iocb->ki_pos, iov_iter_count(to), SEF_OFFLINE,
SCOUTFS_IOC_DWO_READ, &dw, scoutfs_inode_lock);
@@ -64,6 +734,11 @@ retry:
ret = generic_file_read_iter(iocb, to);
out:
if (locked) {
unlock_for_iomap_read(inode, &pt_extent_ent);
locked = false;
}
inode_dio_end(inode);
scoutfs_per_task_del(&si->pt_data_lock, &pt_ent);
scoutfs_unlock(sb, scoutfs_inode_lock, SCOUTFS_LOCK_READ);
@@ -139,6 +814,71 @@ out:
return ret;
}
#endif
loff_t scoutfs_file_llseek(struct file *file, loff_t offset, int whence)
{
struct inode *inode = file->f_mapping->host;
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
struct super_block *sb = inode->i_sb;
struct scoutfs_lock *lock = NULL;
SCOUTFS_DECLARE_PER_TASK_ENTRY(pt_ent);
bool ilocked = false;
int ret = 0;
switch (whence) {
case SEEK_END:
case SEEK_DATA:
case SEEK_HOLE:
/*
* These require a lock and inode refresh as they reference i_size.
*/
inode_lock(inode);
ilocked = true;
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_READ,
SCOUTFS_LKF_REFRESH_INODE, inode,
&lock);
if (ret == 0) {
if (!scoutfs_per_task_add_excl(&si->pt_data_lock, &pt_ent, lock))
WARN_ON_ONCE(true);
}
case SEEK_SET:
case SEEK_CUR:
/* No lock required */
break;
default:
ret = -EINVAL;
break;
}
if (ret == 0) {
if (whence == SEEK_DATA) {
offset = iomap_seek_data(inode, offset,
&scoutfs_iomap_report_ops);
} else if (whence == SEEK_HOLE) {
offset = iomap_seek_hole(inode, offset,
&scoutfs_iomap_report_ops);
} else {
offset = generic_file_llseek(file, offset, whence);
}
}
if (ilocked)
inode_unlock(inode);
if (lock) {
scoutfs_per_task_del(&si->pt_data_lock, &pt_ent);
scoutfs_unlock(sb, lock, SCOUTFS_LOCK_READ);
}
/* The iomap functions don't update the file pointer */
if (ret == 0 && offset >= 0)
offset = vfs_setpos(file, offset, sb->s_maxbytes);
return ret ? ret : offset;
}
int scoutfs_permission(KC_VFS_NS_DEF
struct inode *inode, int mask)
{
@@ -161,41 +901,3 @@ int scoutfs_permission(KC_VFS_NS_DEF
return ret;
}
loff_t scoutfs_file_llseek(struct file *file, loff_t offset, int whence)
{
struct inode *inode = file->f_mapping->host;
struct super_block *sb = inode->i_sb;
struct scoutfs_lock *lock = NULL;
int ret = 0;
switch (whence) {
case SEEK_END:
case SEEK_DATA:
case SEEK_HOLE:
/*
* These require a lock and inode refresh as they
* reference i_size.
*
* XXX: SEEK_DATA/SEEK_HOLE can search our extent
* items instead of relying on generic_file_llseek()
* trickery.
*/
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_READ,
SCOUTFS_LKF_REFRESH_INODE, inode,
&lock);
case SEEK_SET:
case SEEK_CUR:
/* No lock required, fall through to the generic helper */
break;
default:
ret = -EINVAL;
}
if (ret == 0)
offset = generic_file_llseek(file, offset, whence);
scoutfs_unlock(sb, lock, SCOUTFS_LOCK_READ);
return ret ? ret : offset;
}
+5 -1
View File
@@ -95,6 +95,8 @@ static void scoutfs_inode_ctor(void *obj)
seqlock_init(&si->seqlock);
si->staging = false;
scoutfs_per_task_init(&si->pt_data_lock);
scoutfs_per_task_init(&si->pt_inode_locks);
scoutfs_per_task_init(&si->pt_extent_sem);
atomic64_set(&si->data_waitq.changed, 0);
init_waitqueue_head(&si->data_waitq.waitq);
init_rwsem(&si->xattr_rwsem);
@@ -398,7 +400,9 @@ static int set_inode_size(struct inode *inode, struct scoutfs_lock *lock,
if (ret)
return ret;
scoutfs_per_task_add(&si->pt_data_lock, &pt_ent, lock);
if (!scoutfs_per_task_add_excl(&si->pt_data_lock, &pt_ent, lock))
WARN_ON_ONCE(true);
ret = block_truncate_page(inode->i_mapping, new_size, scoutfs_get_block_write);
scoutfs_per_task_del(&si->pt_data_lock, &pt_ent);
if (ret < 0)
+2
View File
@@ -51,6 +51,8 @@ struct scoutfs_inode_info {
seqlock_t seqlock;
bool staging; /* holder of i_mutex is staging */
struct scoutfs_per_task pt_data_lock;
struct scoutfs_per_task pt_inode_locks;
struct scoutfs_per_task pt_extent_sem;
struct scoutfs_data_waitq data_waitq;
struct rw_semaphore xattr_rwsem;
struct list_head writeback_entry;
+3 -1
View File
@@ -49,6 +49,7 @@
#include "quota.h"
#include "scoutfs_trace.h"
#include "util.h"
#include "msg.h"
/*
* We make inode index items coherent by locking fixed size regions of
@@ -529,7 +530,8 @@ static long scoutfs_ioc_stage(struct file *file, unsigned long arg)
if (ret)
goto out;
scoutfs_per_task_add(&si->pt_data_lock, &pt_ent, lock);
if (!scoutfs_per_task_add_excl(&si->pt_data_lock, &pt_ent, lock))
WARN_ON_ONCE(true);
isize = i_size_read(inode);
+339
View File
@@ -0,0 +1,339 @@
/*
* Copyright (C) 2026 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 <linux/kernel.h>
#include <linux/fs.h>
#include <linux/pagemap.h>
#include <linux/iomap.h>
#include "format.h"
#include "super.h"
#include "inode.h"
#include "key.h"
#include "counters.h"
#include "scoutfs_trace.h"
#include "item.h"
#include "btree.h"
#include "lock.h"
#include "ext.h"
#include "iomap.h"
#include "msg.h"
#include "trans.h"
#define KNOWN_FLAGS (IOMAP_REPORT|IOMAP_DIRECT|IOMAP_WRITE|IOMAP_NOWAIT)
static void scoutfs_set_iomap(struct inode *inode, struct iomap *iomap,
struct scoutfs_extent *ext, loff_t offset,
u64 iblock, loff_t length)
{
struct super_block *sb = inode->i_sb;
iomap->flags |= IOMAP_F_BUFFER_HEAD;
iomap->bdev = sb->s_bdev;
if (offset + length > i_size_read(inode))
iomap->flags |= IOMAP_F_DIRTY;
if (ext->len == 0 || ext->start > iblock) {
iomap->type = IOMAP_HOLE;
iomap->addr = IOMAP_NULL_ADDR;
iomap->offset = offset;
if (ext->len > 0) {
/* There's a hole at the starting offset */
iomap->length = min_t(loff_t,
(ext->start - iblock) << SCOUTFS_BLOCK_SM_SHIFT,
length);
} else {
/* There's an implicit hole at EOF */
iomap->length = length;
}
goto out;
}
if (ext->flags & SEF_OFFLINE) {
iomap->type = IOMAP_DELALLOC;
iomap->addr = IOMAP_NULL_ADDR;
} else if (ext->flags & SEF_UNWRITTEN) {
iomap->type = IOMAP_UNWRITTEN;
iomap->addr = (u64) ext->map << SCOUTFS_BLOCK_SM_SHIFT;
iomap->flags |= IOMAP_F_NEW;
} else if (ext->map) {
iomap->type = IOMAP_MAPPED;
iomap->addr = (u64) ext->map << SCOUTFS_BLOCK_SM_SHIFT;
} else {
WARN_ON(true); /* holes should've been handled above */
}
iomap->offset = (u64) ext->start << SCOUTFS_BLOCK_SM_SHIFT;
iomap->length = (u64) ext->len << SCOUTFS_BLOCK_SM_SHIFT;
iomap->validity_cookie = scoutfs_inode_meta_seq(inode);
iomap->page_ops = &scoutfs_iomap_page_ops;
out:
trace_scoutfs_set_iomap(sb, scoutfs_ino(inode), iblock, length, iomap->type,
iomap->flags, iomap->offset, iomap->length, ext->map);
}
static int scoutfs_iomap_begin_report(struct inode *inode, loff_t offset, loff_t length,
unsigned int flags, struct iomap *iomap,
struct iomap *srcmap)
{
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
struct super_block *sb = inode->i_sb;
const u64 ino = scoutfs_ino(inode);
struct scoutfs_lock *lock = NULL;
struct scoutfs_extent ext;
struct data_ext_args args;
u64 iblock;
int ret;
WARN_ON_ONCE(flags & ~KNOWN_FLAGS);
WARN_ON(!inode_is_locked(inode));
iblock = offset >> SCOUTFS_BLOCK_SM_SHIFT;
/* make sure caller holds a cluster lock */
lock = scoutfs_per_task_get(&si->pt_data_lock);
BUG_ON(!lock);
args.ino = ino;
args.inode = inode;
args.lock = lock;
memset(&ext, 0, sizeof(ext));
down_read(&si->extent_sem);
ret = scoutfs_ext_next(sb, &data_ext_ops, &args, iblock, 1, &ext);
if (ret == -ENOENT)
ret = 0;
if (ret == 0)
scoutfs_set_iomap(inode, iomap, &ext, offset, iblock, length);
up_read(&si->extent_sem);
return ret;
}
static bool scoutfs_iomap_valid(struct inode *inode, const struct iomap *iomap)
{
/*@@@ what locks do we hold when this is called?*/
return iomap->validity_cookie == scoutfs_inode_meta_seq(inode);
}
const struct iomap_ops scoutfs_iomap_report_ops = {
.iomap_begin = scoutfs_iomap_begin_report,
};
const struct iomap_page_ops scoutfs_iomap_page_ops = {
.iomap_valid = scoutfs_iomap_valid,
};
#ifdef KC_USE_IOMAP_FOR_IO
static int scoutfs_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
unsigned int flags, struct iomap *iomap,
struct iomap *srcmap)
{
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
#if 0
SCOUTFS_DECLARE_PER_TASK_ENTRY(pt_extent_ent);
#endif
struct super_block *sb = inode->i_sb;
const u64 ino = scoutfs_ino(inode);
struct scoutfs_lock *data_lock = NULL;
#if 0
bool nowait = (iomap->flags & IOMAP_NOWAIT);
#endif
struct scoutfs_extent ext;
struct data_ext_args args;
u64 iblock;
bool write_locked = false;
int ret = 0;
WARN_ON_ONCE(flags & ~KNOWN_FLAGS);
#if 0
WARN_ON_ONCE(!rwsem_is_locked(&si->extent_sem));
#endif
iblock = offset >> SCOUTFS_BLOCK_SM_SHIFT;
/* make sure caller holds a cluster lock */
data_lock = scoutfs_per_task_get(&si->pt_data_lock);
BUG_ON(!data_lock);
args.ino = ino;
args.inode = inode;
args.lock = data_lock;
memset(&ext, 0, sizeof(ext));
if (flags & IOMAP_WRITE) {
down_write(&si->extent_sem);
write_locked = true;
ret = scoutfs_ext_next(sb, &data_ext_ops, &args, iblock, 1, &ext);
if (ret == -ENOENT)
ret = 0;
if (ret < 0)
goto out;
/* non-staging callers should have waited on offline blocks */
if (WARN_ON_ONCE(ext.map && (ext.flags & SEF_OFFLINE) && !si->staging)){
ret = -EIO;
goto out;
}
if (!si->staging) {
ret = scoutfs_inode_check_retention(inode);
if (ret < 0)
goto out;
}
if (ext.start > iblock)
memset(&ext, 0, sizeof(ext));
/* No need to allocate space */
if (ext.map) {
trace_scoutfs_data_get_block_found(sb, ino, &ext);
goto out;
}
ret = scoutfs_data_alloc_block(sb, inode, &ext, iblock, data_lock);
if (ret == 0)
iomap->flags |= IOMAP_F_NEW;
} else {
#if 0
ret = lock_for_iomap_read(inode, nowait, &pt_extent_ent);
if (ret)
goto out;
#endif
down_read(&si->extent_sem);
ret = scoutfs_ext_next(sb, &data_ext_ops, &args, iblock, 1, &ext);
up_read(&si->extent_sem);
#if 0
unlock_for_iomap_read(inode, &pt_extent_ent);
#endif
if (ret == -ENOENT)
ret = 0;
if (ext.len)
trace_scoutfs_data_get_block_found(sb, ino, &ext);
}
out:
if (ret == 0)
scoutfs_set_iomap(inode, iomap, &ext, offset, iblock, length);
if (write_locked)
up_write(&si->extent_sem);
return ret;
}
static int scoutfs_iomap_end(struct inode *inode, loff_t offset, loff_t length,
ssize_t written, unsigned flags, struct iomap *iomap)
{
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
struct super_block *sb = inode->i_sb;
struct scoutfs_lock *data_lock = NULL;
struct scoutfs_extent un;
struct data_ext_args args;
u64 end_blk;
int ret = 0;
if (!(flags & IOMAP_WRITE))
return 0;
/*
* If we hit an error during a direct I/O write, tell the iomap layer to fall
* back to buffered I/O by returning the magic value -ENOTBLK.
*/
if ((flags & IOMAP_DIRECT) && written == 0)
return -ENOTBLK;
/*
* If the scoutfs_iomap_valid() check returned false, don't try to do
* anything else with this extent. It could've been modified by another
* thread since we handed it to iomap.
*/
if (iomap->flags & IOMAP_F_STALE)
return 0;
data_lock = scoutfs_per_task_get(&si->pt_data_lock);
BUG_ON(!data_lock);
#if 0
ret = lock_for_iomap_write(inode, &inode_locks, &pt_inode_ent,
&pt_extent_ent, data_lock, nowait);
if (ret)
return ret;
#endif
down_write(&si->extent_sem);
/* convert unwritten to written, could be staging */
if (iomap->type == IOMAP_UNWRITTEN) {
args.ino = scoutfs_ino(inode);
args.inode = inode;
args.lock = data_lock;
/* This is the file block where this write began */
un.start = offset >> SCOUTFS_BLOCK_SM_SHIFT;
/* This is the file block where this write ended */
end_blk = (offset + written - 1) >> SCOUTFS_BLOCK_SM_SHIFT;
/* We wrote this many blocks (rounded up) */
un.len = (end_blk - un.start) + 1;
/* Move the phys map start forward to reflect the start of this write */
un.map = (iomap->addr >> SCOUTFS_BLOCK_SM_SHIFT) +
(un.start - (iomap->offset >> SCOUTFS_BLOCK_SM_SHIFT));
un.flags = 0;
ret = scoutfs_ext_set(sb, &data_ext_ops, &args, un.start, un.len,
un.map, un.flags);
#if 0
BUG_ON(ret); /* uh-oh, we already wrote those blocks */
#else
if (ret) {
scoutfs_err(sb, "@@@ ext_set -> %d, valid %d, flags 0x%x", ret,
scoutfs_iomap_valid(inode, iomap), iomap->flags);
WARN_ON(1);
}
#endif
}
if (offset + written > i_size_read(inode))
i_size_write(inode, offset + written);
up_write(&si->extent_sem);
return ret;
}
const struct iomap_ops scoutfs_iomap_ops = {
.iomap_begin = scoutfs_iomap_begin,
.iomap_end = scoutfs_iomap_end,
};
#endif
+8
View File
@@ -0,0 +1,8 @@
#ifndef _SCOUTFS_IOMAP_H
#define _SCOUTFS_IOMAP_H
extern const struct iomap_ops scoutfs_iomap_report_ops;
extern const struct iomap_ops scoutfs_iomap_ops;
extern const struct iomap_page_ops scoutfs_iomap_page_ops;
#endif
+34
View File
@@ -3269,6 +3269,40 @@ TRACE_EVENT(scoutfs_trigger_fired,
TP_printk(SCSBF" %s", SCSB_TRACE_ARGS, __entry->name)
);
TRACE_EVENT(scoutfs_set_iomap,
TP_PROTO(struct super_block *sb, u64 ino, u64 iblock, loff_t length,
unsigned int type, unsigned int iflags, loff_t ioffset, loff_t ilength,
u64 map),
TP_ARGS(sb, ino, iblock, length, type, iflags, ioffset, ilength, map),
TP_STRUCT__entry(
SCSB_TRACE_FIELDS
__field(u64, ino)
__field(u64, iblock)
__field(loff_t, length)
__field(unsigned int, type)
__field(loff_t, ioffset)
__field(loff_t, ilength)
__field(u64, map)
),
TP_fast_assign(
SCSB_TRACE_ASSIGN(sb);
__entry->ino = ino;
__entry->iblock = iblock;
__entry->length = length;
__entry->type = type;
__entry->ioffset = ioffset;
__entry->ilength = ilength;
__entry->map = map;
),
TP_printk(SCSBF" ino %llu iblock %llu len %llu type %d ioff %llu ilen %llu map %llu", SCSB_TRACE_ARGS,
__entry->ino, __entry->iblock, __entry->length, __entry->type,
__entry->ioffset, __entry->ilength, __entry->map)
);
#endif /* _TRACE_SCOUTFS_H */
/* This part must be outside protection */
-1
View File
@@ -330,7 +330,6 @@ int scoutfs_trans_sync(struct super_block *sb, int wait)
struct write_attempt attempt = { .ret = 0 };
int ret;
if (!wait) {
queue_trans_work(sb);
return 0;
+11 -3
View File
@@ -517,14 +517,22 @@ unlock:
static int scoutfs_xattr_get(struct dentry *dentry, const char *name, void *buffer, size_t size)
{
struct inode *inode = dentry->d_inode;
struct scoutfs_inode_info *si = SCOUTFS_I(inode);
struct super_block *sb = inode->i_sb;
struct scoutfs_lock *lock = NULL;
int ret;
bool found_lock = false;
int ret = 0;
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_READ, 0, inode, &lock);
lock = scoutfs_per_task_get(&si->pt_data_lock);
if (lock) {
found_lock = true;
} else {
ret = scoutfs_lock_inode(sb, SCOUTFS_LOCK_READ, 0, inode, &lock);
}
if (ret == 0) {
ret = scoutfs_xattr_get_locked(inode, name, buffer, size, lock);
scoutfs_unlock(sb, lock, SCOUTFS_LOCK_READ);
if (!found_lock)
scoutfs_unlock(sb, lock, SCOUTFS_LOCK_READ);
}
return ret;
+1 -21
View File
@@ -65,7 +65,6 @@ generic/088
generic/090
generic/091
generic/092
generic/094
generic/096
generic/097
generic/098
@@ -168,7 +167,6 @@ generic/220
generic/221
generic/222
generic/223
generic/225
generic/227
generic/228
generic/229
@@ -430,7 +428,6 @@ generic/584
generic/586
generic/587
generic/588
generic/591
generic/592
generic/593
generic/594
@@ -537,8 +534,6 @@ generic/078
generic/079
generic/081
generic/082
generic/091
generic/094
generic/096
generic/110
generic/111
@@ -552,9 +547,7 @@ generic/121
generic/122
generic/123
generic/128
generic/130
generic/134
generic/135
generic/136
generic/138
generic/139
@@ -614,7 +607,6 @@ generic/207
generic/210
generic/211
generic/212
generic/214
generic/216
generic/217
generic/218
@@ -622,7 +614,6 @@ generic/219
generic/220
generic/222
generic/223
generic/225
generic/227
generic/229
generic/230
@@ -640,7 +631,6 @@ generic/259
generic/260
generic/261
generic/262
generic/263
generic/264
generic/265
generic/266
@@ -709,7 +699,6 @@ generic/383
generic/384
generic/385
generic/386
generic/391
generic/392
generic/395
generic/396
@@ -718,17 +707,14 @@ generic/398
generic/400
generic/402
generic/404
generic/406
generic/407
generic/408
generic/412
generic/413
generic/414
generic/417
generic/419
generic/420
generic/421
generic/422
generic/424
generic/425
generic/427
@@ -736,7 +722,6 @@ generic/439
generic/440
generic/446
generic/449
generic/450
generic/451
generic/453
generic/454
@@ -788,7 +773,6 @@ generic/546
generic/548
generic/549
generic/550
generic/552
generic/553
generic/555
generic/556
@@ -809,7 +793,6 @@ generic/584
generic/586
generic/587
generic/588
generic/591
generic/592
generic/593
generic/594
@@ -826,7 +809,6 @@ generic/605
generic/606
generic/607
generic/608
generic/609
generic/610
generic/612
generic/613
@@ -842,7 +824,6 @@ generic/635
generic/644
generic/645
generic/646
generic/647
generic/651
generic/652
generic/653
@@ -864,7 +845,6 @@ generic/669
generic/673
generic/674
generic/675
generic/677
generic/678
generic/679
generic/680
@@ -879,4 +859,4 @@ generic/688
generic/689
shared/002
shared/032
Passed all 512 tests
Passed all 509 tests
+3
View File
@@ -1,9 +1,11 @@
generic/003 # missing atime update in buffered read
generic/075 # file content mismatch failures (fds, etc)
generic/094 # preallocation generates an unwritten block where a hole is expected
generic/103 # enospc causes trans commit failures
generic/108 # mount fails on failing device?
generic/112 # file content mismatch failures (fds, etc)
generic/213 # enospc causes trans commit failures
generic/225 # preallocation generates an unwritten block where a hole is expected
generic/318 # can't support user namespaces until v5.11
generic/321 # requires selinux enabled for '+' in ls?
generic/338 # BUG_ON update inode error handling
@@ -37,6 +39,7 @@ generic/565 # xfs_io copy_range missing in el7
generic/568 # falloc not resulting in block count increase
generic/569 # swap
generic/570 # swap
generic/591 # do we care if splice() and O_DIRECT works on pipes?
generic/620 # dm-hugedisk
generic/633 # id-mapped mounts missing in el7
generic/636 # swap
+1 -1
View File
@@ -80,7 +80,7 @@ static void *run_test_func(void *ptr)
for (read = 0; read < size;) {
ret = pread(fd, buf, size - read, read);
if (ret < 0) {
perror("pwrite");
perror("pread");
exit(-1);
}
read += ret;