From be25e37c8b607a11779acadd9cb7dab6dc316bb9 Mon Sep 17 00:00:00 2001 From: Chris Kirby Date: Wed, 14 Jan 2026 14:47:34 -0600 Subject: [PATCH 1/5] Change the looping logic in run-tests.sh If a set of tests is provided, loop over the entire set the requested number of times. Start and stop any requested tracing across the set boundary. Signed-off-by: Chris Kirby --- tests/run-tests.sh | 162 +++++++++++++++++++++++---------------------- 1 file changed, 84 insertions(+), 78 deletions(-) diff --git a/tests/run-tests.sh b/tests/run-tests.sh index 56598cbc..005ce924 100755 --- a/tests/run-tests.sh +++ b/tests/run-tests.sh @@ -90,7 +90,7 @@ done # set some T_ defaults T_TRACE_DUMP="0" -T_TRACE_PRINTK="0" +T_TRACE_PRINTK="" T_PORT_START="19700" T_LOOP_ITER="1" @@ -137,6 +137,9 @@ while true; do test -n "$2" || die "-l must have a nr iterations argument" test "$2" -eq "$2" 2>/dev/null || die "-l argument must be an integer" T_LOOP_ITER="$2" + + # when looping, break after first failure + T_ABORT="1" shift ;; -M) @@ -399,31 +402,44 @@ if [ -n "$T_INSMOD" ]; then cmd insmod "$T_MODULE" fi -if [ -n "$T_TRACE_MULT" ]; then -# orig_trace_size=$(cat /sys/kernel/debug/tracing/buffer_size_kb) - orig_trace_size=1408 - mult_trace_size=$((orig_trace_size * T_TRACE_MULT)) - msg "increasing trace buffer size from $orig_trace_size KiB to $mult_trace_size KiB" - echo $mult_trace_size > /sys/kernel/debug/tracing/buffer_size_kb -fi +start_tracing() { + if [ -n "$T_TRACE_MULT" ]; then + orig_trace_size=1408 + mult_trace_size=$((orig_trace_size * T_TRACE_MULT)) + msg "increasing trace buffer size from $orig_trace_size KiB to $mult_trace_size KiB" + echo $mult_trace_size > /sys/kernel/debug/tracing/buffer_size_kb + fi -nr_globs=${#T_TRACE_GLOB[@]} -if [ $nr_globs -gt 0 ]; then - echo 0 > /sys/kernel/debug/tracing/events/scoutfs/enable + nr_globs=${#T_TRACE_GLOB[@]} + if [ $nr_globs -gt 0 ]; then + echo 0 > /sys/kernel/debug/tracing/events/scoutfs/enable - for g in "${T_TRACE_GLOB[@]}"; do - for e in /sys/kernel/debug/tracing/events/scoutfs/$g/enable; do - if test -w "$e"; then - echo 1 > "$e" - else - die "-t glob '$g' matched no scoutfs events" - fi + for g in "${T_TRACE_GLOB[@]}"; do + for e in /sys/kernel/debug/tracing/events/scoutfs/$g/enable; do + if test -w "$e"; then + echo 1 > "$e" + else + die "-t glob '$g' matched no scoutfs events" + fi + done done - done - nr_events=$(cat /sys/kernel/debug/tracing/set_event | wc -l) - msg "enabled $nr_events trace events from $nr_globs -t globs" -fi + nr_events=$(cat /sys/kernel/debug/tracing/set_event | wc -l) + msg "enabled $nr_events trace events from $nr_globs -t globs" + fi +} + +stop_tracing() { + if [ -n "$T_TRACE_GLOB" -o -n "$T_TRACE_PRINTK" ]; then + msg "saving traces and disabling tracing" + echo 0 > /sys/kernel/debug/tracing/events/scoutfs/enable + echo 0 > /sys/kernel/debug/tracing/options/trace_printk + cat /sys/kernel/debug/tracing/trace | gzip > "$T_RESULTS/traces.gz" + if [ -n "$orig_trace_size" ]; then + echo $orig_trace_size > /sys/kernel/debug/tracing/buffer_size_kb + fi + fi +} if [ -n "$T_TRACE_PRINTK" ]; then echo "$T_TRACE_PRINTK" > /sys/kernel/debug/tracing/options/trace_printk @@ -603,24 +619,26 @@ passed=0 skipped=0 failed=0 skipped_permitted=0 -for t in $tests; do - # tests has basenames from sequence, get path and name - t="tests/$t" - test_name=$(basename "$t" | sed -e 's/.sh$//') +for iter in $(seq 1 $T_LOOP_ITER); do - # get stats from previous pass - last="$T_RESULTS/last-passed-test-stats" - stats=$(grep -s "^$test_name " "$last" | cut -d " " -f 2-) - test -n "$stats" && stats="last: $stats" - printf " %-30s $stats" "$test_name" + start_tracing - # mark in dmesg as to what test we are running - echo "run scoutfs test $test_name" > /dev/kmsg + for t in $tests; do + # tests has basenames from sequence, get path and name + t="tests/$t" + test_name=$(basename "$t" | sed -e 's/.sh$//') - # let the test get at its extra files - T_EXTRA="$T_TESTS/extra/$test_name" + # get stats from previous pass + last="$T_RESULTS/last-passed-test-stats" + stats=$(grep -s "^$test_name " "$last" | cut -d " " -f 2-) + test -n "$stats" && stats="last: $stats" + printf " %-30s $stats" "$test_name" - for iter in $(seq 1 $T_LOOP_ITER); do + # mark in dmesg as to what test we are running + echo "run scoutfs test $test_name" > /dev/kmsg + + # let the test get at its extra files + T_EXTRA="$T_TESTS/extra/$test_name" # create a temporary dir and file path for the test T_TMPDIR="$T_RESULTS/tmp/$test_name" @@ -710,55 +728,43 @@ for t in $tests; do sts=$T_FAIL_STATUS fi - # stop looping if we didn't pass - if [ "$sts" != "$T_PASS_STATUS" ]; then - break; + # show and record the result of the test + if [ "$sts" == "$T_PASS_STATUS" ]; then + echo " passed: $stats" + ((passed++)) + # save stats for passed test + grep -s -v "^$test_name " "$last" > "$last.tmp" + echo "$test_name $stats" >> "$last.tmp" + mv -f "$last.tmp" "$last" + elif [ "$sts" == "$T_SKIP_PERMITTED_STATUS" ]; then + echo " [ skipped (permitted): $message ]" + echo "$test_name skipped (permitted) $message " >> "$T_RESULTS/skip.log" + ((skipped_permitted++)) + elif [ "$sts" == "$T_SKIP_STATUS" ]; then + echo " [ skipped: $message ]" + echo "$test_name $message" >> "$T_RESULTS/skip.log" + ((skipped++)) + elif [ "$sts" == "$T_FAIL_STATUS" ]; then + echo " [ failed: $message ]" + echo "$test_name $message" >> "$T_RESULTS/fail.log" + ((failed++)) + + if [ -n "$T_ABORT" ]; then + stop_tracing + die "aborting after first failure" + fi fi + + # record results for TAP format output + t_tap_progress $test_name $sts + ((testcount++)) done - # show and record the result of the test - if [ "$sts" == "$T_PASS_STATUS" ]; then - echo " passed: $stats" - ((passed++)) - # save stats for passed test - grep -s -v "^$test_name " "$last" > "$last.tmp" - echo "$test_name $stats" >> "$last.tmp" - mv -f "$last.tmp" "$last" - elif [ "$sts" == "$T_SKIP_PERMITTED_STATUS" ]; then - echo " [ skipped (permitted): $message ]" - echo "$test_name skipped (permitted) $message " >> "$T_RESULTS/skip.log" - ((skipped_permitted++)) - elif [ "$sts" == "$T_SKIP_STATUS" ]; then - echo " [ skipped: $message ]" - echo "$test_name $message" >> "$T_RESULTS/skip.log" - ((skipped++)) - elif [ "$sts" == "$T_FAIL_STATUS" ]; then - echo " [ failed: $message ]" - echo "$test_name $message" >> "$T_RESULTS/fail.log" - ((failed++)) - - test -n "$T_ABORT" && die "aborting after first failure" - fi - - # record results for TAP format output - t_tap_progress $test_name $sts - ((testcount++)) - + stop_tracing done msg "all tests run: $passed passed, $skipped skipped, $skipped_permitted skipped (permitted), $failed failed" - -if [ -n "$T_TRACE_GLOB" -o -n "$T_TRACE_PRINTK" ]; then - msg "saving traces and disabling tracing" - echo 0 > /sys/kernel/debug/tracing/events/scoutfs/enable - echo 0 > /sys/kernel/debug/tracing/options/trace_printk - cat /sys/kernel/debug/tracing/trace > "$T_RESULTS/traces" - if [ -n "$orig_trace_size" ]; then - echo $orig_trace_size > /sys/kernel/debug/tracing/buffer_size_kb - fi -fi - if [ "$skipped" == 0 -a "$failed" == 0 ]; then msg "all tests passed" unmount_all From 0b2aef4f59eb6e24317e717ffed444be76103ae4 Mon Sep 17 00:00:00 2001 From: Chris Kirby Date: Mon, 29 Jun 2026 09:49:28 -0500 Subject: [PATCH 2/5] 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. Add KC_USE_IOMAP_FOR_IO and only build the iomap R/W pieces for EL9.5 and higher. We support iomap for fiemap and llseek on EL8 and EL9.4. Signed-off-by: Chris Kirby --- kmod/src/Makefile | 1 + kmod/src/Makefile.kernelcompat | 10 + kmod/src/data.c | 457 +++++++++--------- kmod/src/data.h | 20 + kmod/src/file.c | 642 ++++++++++++++++++++++++-- kmod/src/inode.c | 5 +- kmod/src/inode.h | 1 + kmod/src/ioctl.c | 4 +- kmod/src/iomap.c | 316 +++++++++++++ kmod/src/iomap.h | 8 + kmod/src/scoutfs_trace.h | 34 ++ kmod/src/trans.c | 1 - tests/extra/xfstests/expected-results | 22 +- tests/extra/xfstests/local.exclude | 3 + tests/src/mmap_stress.c | 2 +- 15 files changed, 1231 insertions(+), 295 deletions(-) create mode 100644 kmod/src/iomap.c create mode 100644 kmod/src/iomap.h diff --git a/kmod/src/Makefile b/kmod/src/Makefile index fa632aa1..7fd73191 100644 --- a/kmod/src/Makefile +++ b/kmod/src/Makefile @@ -25,6 +25,7 @@ scoutfs-y += \ forest.o \ inode.o \ ioctl.o \ + iomap.o \ item.o \ kernelcompat.o \ lock.o \ diff --git a/kmod/src/Makefile.kernelcompat b/kmod/src/Makefile.kernelcompat index 9c8bd9cb..88b97f89 100644 --- a/kmod/src/Makefile.kernelcompat +++ b/kmod/src/Makefile.kernelcompat @@ -222,3 +222,13 @@ endif ifneq (,$(shell grep 'define timer_container_of' include/linux/timer.h)) ccflags-y += -DKC_TIMER_CONTAINER_OF endif + +# +# v6.4-rc1-4f80818b4a58c +# +# iomap needs iterator nofault support in the read/write paths. We can still +# use iomap for fiemap and llseek though. +# +ifneq (,$(shell grep 'copy_page_to_iter_nofault' include/linux/uio.h)) +ccflags-y += -DKC_USE_IOMAP_FOR_IO +endif diff --git a/kmod/src/data.c b/kmod/src/data.c index 1d3168f6..bdacc8b0 100644 --- a/kmod/src/data.c +++ b/kmod/src/data.c @@ -21,9 +21,11 @@ #include #include #include +#include #include #include #include +#include #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,18 @@ 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); + bool caller_has_sem; int ret; - down_read(&si->extent_sem); + caller_has_sem = scoutfs_per_task_get(&si->pt_extent_sem) != NULL; + + if (!caller_has_sem) + down_read(&si->extent_sem); + ret = scoutfs_get_block(inode, iblock, bh, create); - up_read(&si->extent_sem); + + if (!caller_has_sem) + up_read(&si->extent_sem); return ret; } @@ -706,44 +711,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 +768,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 +779,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 +803,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 +893,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 +906,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 +934,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 +948,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 +1123,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 +1174,39 @@ out: return ret; } +#ifdef KC_USE_IOMAP_FOR_IO +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; +} +#endif + /* * 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 +1504,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 +1636,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; @@ -1827,6 +1775,7 @@ int scoutfs_data_wait_check(struct inode *inode, loff_t pos, loff_t len, DECLARE_DATA_WAIT_ROOT(sb, rt); DECLARE_DATA_WAITQ(inode, wq); struct scoutfs_extent ext = {0,}; + bool caller_has_sem; u64 iblock; u64 last_block; u64 on; @@ -1845,7 +1794,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 +1802,14 @@ int scoutfs_data_wait_check(struct inode *inode, loff_t pos, loff_t len, } } - down_read(&si->extent_sem); + caller_has_sem = scoutfs_per_task_get(&si->pt_extent_sem) != NULL; + if (!caller_has_sem) + 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 +1842,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 (!caller_has_sem) + up_read(&si->extent_sem); out: trace_scoutfs_data_wait_check(sb, ino, pos, len, sef, op, &ext, ret); @@ -2168,40 +2120,77 @@ 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); +#ifdef KC_USE_IOMAP_FOR_IO + bool caller_has_sem; +#endif loff_t pos; int err; vm_fault_t ret = VM_FAULT_SIGBUS; + 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; } +#ifdef KC_USE_IOMAP_FOR_IO + caller_has_sem = scoutfs_per_task_get(&si->pt_extent_sem) != NULL; + if (!caller_has_sem) { + down_read(&si->extent_sem); + if (!scoutfs_per_task_add_excl(&si->pt_extent_sem, &pt_sem, + &caller_has_sem)) + WARN_ON_ONCE(true); + } +#endif + ret = filemap_fault(vmf); +#ifdef KC_USE_IOMAP_FOR_IO + if (!caller_has_sem) { + up_read(&si->extent_sem); + scoutfs_per_task_del(&si->pt_extent_sem, &pt_sem); + } +#endif + 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 +2218,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 +2242,9 @@ const struct file_operations scoutfs_file_fops = { .fsync = scoutfs_file_fsync, .llseek = scoutfs_file_llseek, .fallocate = scoutfs_fallocate, +#ifdef KC_USE_IOMAP_FOR_IO + .fadvise = scoutfs_fadvise, +#endif }; void scoutfs_data_init_btrees(struct super_block *sb, diff --git a/kmod/src/data.h b/kmod/src/data.h index 8b2f5f60..d1e701ec 100644 --- a/kmod/src/data.h +++ b/kmod/src/data.h @@ -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); diff --git a/kmod/src/file.c b/kmod/src/file.c index 0f2e7b83..730a6625 100644 --- a/kmod/src/file.c +++ b/kmod/src/file.c @@ -18,6 +18,7 @@ #include #include #include +#include #include "format.h" #include "super.h" @@ -29,6 +30,544 @@ #include "per_task.h" #include "omap.h" #include "quota.h" +#include "iomap.h" +#include "trans.h" +#include "msg.h" + +#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); + 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); + locked = false; + window_size -= fault_in_iov_iter_writeable(to, window_size); + if (window_size != 0) + goto retry; + } + +out: + if (locked) { + scoutfs_per_task_del(&si->pt_extent_sem, &pt_extent_ent); + up_read(&si->extent_sem); + } + + 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; + size_t prev_count = 0; + size_t window_size = 0; + size_t read = 0; + ssize_t ret; + + if (!scoutfs_should_use_dio(iocb, to)) { + iocb->ki_flags &= ~IOCB_DIRECT; + return scoutfs_file_buffered_read(iocb, to); + } + +retry: + pagefault_disable(); + to->nofault = true; + ret = iomap_dio_rw(iocb, to, &scoutfs_iomap_ops, NULL, IOMAP_DIO_PARTIAL, read); + to->nofault = false; + 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)) { + window_size -= fault_in_iov_iter_writeable(to, window_size); + if (window_size != 0) + goto retry; + } + +out: + 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); + bool inode_locked; + ssize_t ret; + + /* IOCB_NOWAIT is only for direct I/O */ + if (!is_dio && nowait) + return -EOPNOTSUPP; + +retry: + scoutfs_inode_lock = NULL; + inode_locked = false; + + if (is_dio) { + if (nowait) { + if (!inode_trylock_shared(inode)) { + ret = -EAGAIN; + goto out; + } + lock_flags |= SCOUTFS_LKF_NONBLOCK; + } else { + inode_lock_shared(inode); + } + inode_dio_begin(inode); + inode_locked = true; + } else { + /* protect checked extents from release */ + inode_lock(inode); + inode_dio_begin(inode); + inode_unlock(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 (inode_locked) + inode_unlock_shared(inode); + + if (scoutfs_data_wait_found(&dw)) { + ret = scoutfs_data_wait(inode, &dw); + if (ret == 0) + goto retry; + } + + return ret; +} + +static int lock_for_iomap_write(struct inode *inode, struct list_head *ind_locks, + struct scoutfs_lock *scoutfs_inode_lock, + bool nowait) +{ + struct super_block *sb = inode->i_sb; + 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; + } + 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, + size_t written) +{ + struct super_block *sb = inode->i_sb; + + if (written > 0) + scoutfs_do_write_end(inode, scoutfs_inode_lock, ind_locks); + + scoutfs_release_trans(sb); + scoutfs_inode_index_unlock(sb, ind_locks); +} + +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); + 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, 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, 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, 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); + 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, 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, written); + locked = false; + goto retry; + } + +out: + if (locked) { + unlock_for_iomap_write(inode, scoutfs_inode_lock, &ind_locks, 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 < 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 */ + 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)) { + ret = scoutfs_data_wait(inode, &dw); + if (ret == 0) + goto retry; + } + + if (is_sync) + iocb->ki_flags |= IOCB_DSYNC; + + return ret; +} + +#else ssize_t scoutfs_file_read_iter(struct kiocb *iocb, struct iov_iter *to) { @@ -139,6 +678,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 +765,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; -} diff --git a/kmod/src/inode.c b/kmod/src/inode.c index 9f6b55f7..6db15554 100644 --- a/kmod/src/inode.c +++ b/kmod/src/inode.c @@ -95,6 +95,7 @@ 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_extent_sem); atomic64_set(&si->data_waitq.changed, 0); init_waitqueue_head(&si->data_waitq.waitq); init_rwsem(&si->xattr_rwsem); @@ -398,7 +399,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) diff --git a/kmod/src/inode.h b/kmod/src/inode.h index 2c390908..9120afa8 100644 --- a/kmod/src/inode.h +++ b/kmod/src/inode.h @@ -51,6 +51,7 @@ 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_extent_sem; struct scoutfs_data_waitq data_waitq; struct rw_semaphore xattr_rwsem; struct list_head writeback_entry; diff --git a/kmod/src/ioctl.c b/kmod/src/ioctl.c index 903033be..6da71d95 100644 --- a/kmod/src/ioctl.c +++ b/kmod/src/ioctl.c @@ -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); diff --git a/kmod/src/iomap.c b/kmod/src/iomap.c new file mode 100644 index 00000000..3113cd93 --- /dev/null +++ b/kmod/src/iomap.c @@ -0,0 +1,316 @@ +/* + * 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 +#include +#include +#include + +#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; + +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); + WARN_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; +} + +const struct iomap_ops scoutfs_iomap_report_ops = { + .iomap_begin = scoutfs_iomap_begin_report, +}; + +#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); + struct super_block *sb = inode->i_sb; + const u64 ino = scoutfs_ino(inode); + struct scoutfs_lock *data_lock = NULL; + struct scoutfs_extent ext; + struct data_ext_args args; + u64 iblock; + bool write_locked = false; + int ret = 0; + + WARN_ON_ONCE(flags & ~KNOWN_FLAGS); + + iblock = offset >> SCOUTFS_BLOCK_SM_SHIFT; + + /* make sure caller holds a cluster lock */ + data_lock = scoutfs_per_task_get(&si->pt_data_lock); + WARN_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; + + if (ext.start > iblock) + memset(&ext, 0, sizeof(ext)); + + /* 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; + } + /* 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 { + down_read(&si->extent_sem); + + ret = scoutfs_ext_next(sb, &data_ext_ops, &args, iblock, 1, &ext); + + up_read(&si->extent_sem); + + 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 scoutfs_extent ext; + struct data_ext_args args; + u64 blks_remaining; + u64 start_blk; + 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; + + data_lock = scoutfs_per_task_get(&si->pt_data_lock); + WARN_ON(!data_lock); + + 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 */ + start_blk = 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 */ + blks_remaining = (end_blk - start_blk) + 1; + + while (blks_remaining > 0) { + ret = scoutfs_ext_next(sb, &data_ext_ops, &args, start_blk, + 1, &ext); + + /* + * The extent with the original starting block(s) has to exist. + * It's possible that it has been split by page_mkwrite + * converting one or more of the blocks from unwritten to + * written while we weren't holding the extent_sem in the gap + * between iomap_begin and iomap_end. The starting block + * still has to be contained within the extent we found. + * + * But we hold the vfs inode write lock, so we don't have to + * worry about truncate removing the extent(s) that cover our + * iomap. + */ + BUG_ON(ret); + BUG_ON(start_blk < ext.start); + + un.start = start_blk; + un.len = min_t(u64, blks_remaining, + (ext.len - (un.start - ext.start))); + un.map = (iomap->addr >> SCOUTFS_BLOCK_SM_SHIFT) + + (un.start - (iomap->offset >> SCOUTFS_BLOCK_SM_SHIFT)); + + WARN_ON(un.map != (ext.map + (un.start - ext.start))); + + un.flags = ext.flags & ~SEF_UNWRITTEN; + + ret = scoutfs_ext_set(sb, &data_ext_ops, &args, un.start, un.len, + un.map, un.flags); + WARN_ON(ret); /* uh-oh, we already wrote those blocks */ + + start_blk += un.len; + blks_remaining -= un.len; + } + } + + 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 diff --git a/kmod/src/iomap.h b/kmod/src/iomap.h new file mode 100644 index 00000000..3d4c8547 --- /dev/null +++ b/kmod/src/iomap.h @@ -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 diff --git a/kmod/src/scoutfs_trace.h b/kmod/src/scoutfs_trace.h index c7157352..90fbc0aa 100644 --- a/kmod/src/scoutfs_trace.h +++ b/kmod/src/scoutfs_trace.h @@ -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 */ diff --git a/kmod/src/trans.c b/kmod/src/trans.c index d131bfa1..630f97e0 100644 --- a/kmod/src/trans.c +++ b/kmod/src/trans.c @@ -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; diff --git a/tests/extra/xfstests/expected-results b/tests/extra/xfstests/expected-results index c4032ca9..83153603 100644 --- a/tests/extra/xfstests/expected-results +++ b/tests/extra/xfstests/expected-results @@ -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 diff --git a/tests/extra/xfstests/local.exclude b/tests/extra/xfstests/local.exclude index fa274227..f71dc604 100644 --- a/tests/extra/xfstests/local.exclude +++ b/tests/extra/xfstests/local.exclude @@ -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 diff --git a/tests/src/mmap_stress.c b/tests/src/mmap_stress.c index 4ec2220b..37494142 100644 --- a/tests/src/mmap_stress.c +++ b/tests/src/mmap_stress.c @@ -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; From 1d560a0ee042962296e582cd49aaa78f412d6769 Mon Sep 17 00:00:00 2001 From: Chris Kirby Date: Mon, 29 Jun 2026 14:28:46 -0700 Subject: [PATCH 3/5] Handle xfstests results with and without direct I/O Use the same check as we do in Makefile.kernelcompat (look for copy_page_to_iter_nofault in the kernel uio.h header file) to determine if we should use the no DIO version of expected xfstests results. Signed-off-by: Chris Kirby --- tests/extra/xfstests/expected-results | 12 - tests/extra/xfstests/expected-results.nodio | 876 ++++++++++++++++++++ tests/funcs/filter.sh | 5 + tests/tests/xfstests.sh | 9 +- 4 files changed, 889 insertions(+), 13 deletions(-) create mode 100644 tests/extra/xfstests/expected-results.nodio diff --git a/tests/extra/xfstests/expected-results b/tests/extra/xfstests/expected-results index 83153603..0d75feee 100644 --- a/tests/extra/xfstests/expected-results +++ b/tests/extra/xfstests/expected-results @@ -537,8 +537,6 @@ generic/082 generic/096 generic/110 generic/111 -generic/113 -generic/114 generic/115 generic/116 generic/118 @@ -595,7 +593,6 @@ generic/194 generic/195 generic/196 generic/197 -generic/198 generic/199 generic/200 generic/201 @@ -603,10 +600,6 @@ generic/202 generic/203 generic/205 generic/206 -generic/207 -generic/210 -generic/211 -generic/212 generic/216 generic/217 generic/218 @@ -619,7 +612,6 @@ generic/229 generic/230 generic/235 generic/238 -generic/240 generic/244 generic/250 generic/252 @@ -722,14 +714,12 @@ generic/439 generic/440 generic/446 generic/449 -generic/451 generic/453 generic/454 generic/456 generic/458 generic/462 generic/463 -generic/465 generic/466 generic/468 generic/469 @@ -761,7 +751,6 @@ generic/528 generic/530 generic/536 generic/537 -generic/538 generic/539 generic/540 generic/541 @@ -790,7 +779,6 @@ generic/581 generic/582 generic/583 generic/584 -generic/586 generic/587 generic/588 generic/592 diff --git a/tests/extra/xfstests/expected-results.nodio b/tests/extra/xfstests/expected-results.nodio new file mode 100644 index 00000000..8e3a5eec --- /dev/null +++ b/tests/extra/xfstests/expected-results.nodio @@ -0,0 +1,876 @@ +Ran: +generic/001 +generic/002 +generic/004 +generic/005 +generic/006 +generic/007 +generic/008 +generic/009 +generic/011 +generic/012 +generic/013 +generic/014 +generic/015 +generic/016 +generic/018 +generic/020 +generic/021 +generic/022 +generic/023 +generic/024 +generic/025 +generic/026 +generic/028 +generic/029 +generic/030 +generic/031 +generic/032 +generic/033 +generic/034 +generic/035 +generic/037 +generic/039 +generic/040 +generic/041 +generic/050 +generic/052 +generic/053 +generic/056 +generic/057 +generic/058 +generic/059 +generic/060 +generic/061 +generic/062 +generic/063 +generic/064 +generic/065 +generic/066 +generic/067 +generic/069 +generic/070 +generic/071 +generic/073 +generic/076 +generic/078 +generic/079 +generic/080 +generic/081 +generic/082 +generic/084 +generic/086 +generic/087 +generic/088 +generic/090 +generic/091 +generic/092 +generic/096 +generic/097 +generic/098 +generic/099 +generic/101 +generic/104 +generic/105 +generic/106 +generic/107 +generic/110 +generic/111 +generic/113 +generic/114 +generic/115 +generic/116 +generic/117 +generic/118 +generic/119 +generic/120 +generic/121 +generic/122 +generic/123 +generic/124 +generic/126 +generic/128 +generic/129 +generic/130 +generic/131 +generic/134 +generic/135 +generic/136 +generic/138 +generic/139 +generic/140 +generic/141 +generic/142 +generic/143 +generic/144 +generic/145 +generic/146 +generic/147 +generic/148 +generic/149 +generic/150 +generic/151 +generic/152 +generic/153 +generic/154 +generic/155 +generic/156 +generic/157 +generic/158 +generic/159 +generic/160 +generic/161 +generic/162 +generic/163 +generic/169 +generic/171 +generic/172 +generic/173 +generic/174 +generic/177 +generic/178 +generic/179 +generic/180 +generic/181 +generic/182 +generic/183 +generic/184 +generic/185 +generic/188 +generic/189 +generic/190 +generic/191 +generic/193 +generic/194 +generic/195 +generic/196 +generic/197 +generic/198 +generic/199 +generic/200 +generic/201 +generic/202 +generic/203 +generic/205 +generic/206 +generic/207 +generic/210 +generic/211 +generic/212 +generic/214 +generic/215 +generic/216 +generic/217 +generic/218 +generic/219 +generic/220 +generic/221 +generic/222 +generic/223 +generic/227 +generic/228 +generic/229 +generic/230 +generic/235 +generic/236 +generic/237 +generic/238 +generic/240 +generic/244 +generic/245 +generic/246 +generic/247 +generic/248 +generic/249 +generic/250 +generic/252 +generic/253 +generic/254 +generic/255 +generic/256 +generic/257 +generic/258 +generic/259 +generic/260 +generic/261 +generic/262 +generic/263 +generic/264 +generic/265 +generic/266 +generic/267 +generic/268 +generic/271 +generic/272 +generic/276 +generic/277 +generic/278 +generic/279 +generic/281 +generic/282 +generic/283 +generic/284 +generic/286 +generic/287 +generic/288 +generic/289 +generic/290 +generic/291 +generic/292 +generic/293 +generic/294 +generic/295 +generic/296 +generic/301 +generic/302 +generic/303 +generic/304 +generic/305 +generic/306 +generic/307 +generic/308 +generic/309 +generic/312 +generic/313 +generic/314 +generic/315 +generic/316 +generic/317 +generic/319 +generic/322 +generic/324 +generic/325 +generic/326 +generic/327 +generic/328 +generic/329 +generic/330 +generic/331 +generic/332 +generic/335 +generic/336 +generic/337 +generic/341 +generic/342 +generic/343 +generic/346 +generic/348 +generic/353 +generic/355 +generic/358 +generic/359 +generic/360 +generic/361 +generic/362 +generic/363 +generic/364 +generic/365 +generic/366 +generic/367 +generic/368 +generic/369 +generic/370 +generic/371 +generic/372 +generic/373 +generic/374 +generic/375 +generic/376 +generic/377 +generic/378 +generic/379 +generic/380 +generic/381 +generic/382 +generic/383 +generic/384 +generic/385 +generic/386 +generic/389 +generic/391 +generic/392 +generic/393 +generic/394 +generic/395 +generic/396 +generic/397 +generic/398 +generic/400 +generic/401 +generic/402 +generic/403 +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/426 +generic/427 +generic/428 +generic/436 +generic/437 +generic/439 +generic/440 +generic/443 +generic/445 +generic/446 +generic/448 +generic/449 +generic/450 +generic/451 +generic/452 +generic/453 +generic/454 +generic/456 +generic/458 +generic/460 +generic/462 +generic/463 +generic/465 +generic/466 +generic/468 +generic/469 +generic/470 +generic/471 +generic/474 +generic/477 +generic/478 +generic/479 +generic/480 +generic/481 +generic/483 +generic/485 +generic/486 +generic/487 +generic/488 +generic/489 +generic/490 +generic/491 +generic/492 +generic/498 +generic/499 +generic/501 +generic/502 +generic/503 +generic/504 +generic/505 +generic/506 +generic/507 +generic/508 +generic/509 +generic/510 +generic/511 +generic/512 +generic/513 +generic/514 +generic/515 +generic/516 +generic/517 +generic/518 +generic/519 +generic/520 +generic/523 +generic/524 +generic/525 +generic/526 +generic/527 +generic/528 +generic/529 +generic/530 +generic/531 +generic/533 +generic/534 +generic/535 +generic/536 +generic/537 +generic/538 +generic/539 +generic/540 +generic/541 +generic/542 +generic/543 +generic/544 +generic/545 +generic/546 +generic/547 +generic/548 +generic/549 +generic/550 +generic/552 +generic/553 +generic/555 +generic/556 +generic/557 +generic/566 +generic/567 +generic/571 +generic/572 +generic/573 +generic/574 +generic/575 +generic/576 +generic/577 +generic/578 +generic/580 +generic/581 +generic/582 +generic/583 +generic/584 +generic/586 +generic/587 +generic/588 +generic/592 +generic/593 +generic/594 +generic/595 +generic/596 +generic/597 +generic/598 +generic/599 +generic/600 +generic/601 +generic/602 +generic/603 +generic/604 +generic/605 +generic/606 +generic/607 +generic/608 +generic/609 +generic/610 +generic/611 +generic/612 +generic/613 +generic/614 +generic/618 +generic/621 +generic/623 +generic/624 +generic/625 +generic/626 +generic/628 +generic/629 +generic/630 +generic/632 +generic/634 +generic/635 +generic/637 +generic/638 +generic/639 +generic/640 +generic/644 +generic/645 +generic/646 +generic/647 +generic/651 +generic/652 +generic/653 +generic/654 +generic/655 +generic/657 +generic/658 +generic/659 +generic/660 +generic/661 +generic/662 +generic/663 +generic/664 +generic/665 +generic/666 +generic/667 +generic/668 +generic/669 +generic/673 +generic/674 +generic/675 +generic/676 +generic/677 +generic/678 +generic/679 +generic/680 +generic/681 +generic/682 +generic/683 +generic/684 +generic/685 +generic/686 +generic/687 +generic/688 +generic/689 +shared/002 +shared/032 +Not +run: +generic/008 +generic/009 +generic/012 +generic/015 +generic/016 +generic/018 +generic/021 +generic/022 +generic/025 +generic/026 +generic/031 +generic/033 +generic/050 +generic/052 +generic/058 +generic/059 +generic/060 +generic/061 +generic/063 +generic/064 +generic/078 +generic/079 +generic/081 +generic/082 +generic/091 +generic/096 +generic/110 +generic/111 +generic/113 +generic/114 +generic/115 +generic/116 +generic/118 +generic/119 +generic/121 +generic/122 +generic/123 +generic/128 +generic/130 +generic/134 +generic/135 +generic/136 +generic/138 +generic/139 +generic/140 +generic/142 +generic/143 +generic/144 +generic/145 +generic/146 +generic/147 +generic/148 +generic/149 +generic/150 +generic/151 +generic/152 +generic/153 +generic/154 +generic/155 +generic/156 +generic/157 +generic/158 +generic/159 +generic/160 +generic/161 +generic/162 +generic/163 +generic/171 +generic/172 +generic/173 +generic/174 +generic/177 +generic/178 +generic/179 +generic/180 +generic/181 +generic/182 +generic/183 +generic/185 +generic/188 +generic/189 +generic/190 +generic/191 +generic/193 +generic/194 +generic/195 +generic/196 +generic/197 +generic/198 +generic/199 +generic/200 +generic/201 +generic/202 +generic/203 +generic/205 +generic/206 +generic/207 +generic/210 +generic/211 +generic/212 +generic/214 +generic/216 +generic/217 +generic/218 +generic/219 +generic/220 +generic/222 +generic/223 +generic/227 +generic/229 +generic/230 +generic/235 +generic/238 +generic/240 +generic/244 +generic/250 +generic/252 +generic/253 +generic/254 +generic/255 +generic/256 +generic/259 +generic/260 +generic/261 +generic/262 +generic/263 +generic/264 +generic/265 +generic/266 +generic/267 +generic/268 +generic/271 +generic/272 +generic/276 +generic/277 +generic/278 +generic/279 +generic/281 +generic/282 +generic/283 +generic/284 +generic/287 +generic/288 +generic/289 +generic/290 +generic/291 +generic/292 +generic/293 +generic/295 +generic/296 +generic/301 +generic/302 +generic/303 +generic/304 +generic/305 +generic/312 +generic/314 +generic/316 +generic/317 +generic/324 +generic/326 +generic/327 +generic/328 +generic/329 +generic/330 +generic/331 +generic/332 +generic/353 +generic/355 +generic/358 +generic/359 +generic/361 +generic/362 +generic/363 +generic/364 +generic/365 +generic/366 +generic/367 +generic/368 +generic/369 +generic/370 +generic/371 +generic/372 +generic/373 +generic/374 +generic/378 +generic/379 +generic/380 +generic/381 +generic/382 +generic/383 +generic/384 +generic/385 +generic/386 +generic/391 +generic/392 +generic/395 +generic/396 +generic/397 +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 +generic/439 +generic/440 +generic/446 +generic/449 +generic/450 +generic/451 +generic/453 +generic/454 +generic/456 +generic/458 +generic/462 +generic/463 +generic/465 +generic/466 +generic/468 +generic/469 +generic/470 +generic/471 +generic/474 +generic/485 +generic/487 +generic/488 +generic/491 +generic/492 +generic/499 +generic/501 +generic/503 +generic/505 +generic/506 +generic/507 +generic/508 +generic/511 +generic/513 +generic/514 +generic/515 +generic/516 +generic/517 +generic/518 +generic/519 +generic/520 +generic/528 +generic/530 +generic/536 +generic/537 +generic/538 +generic/539 +generic/540 +generic/541 +generic/542 +generic/543 +generic/544 +generic/545 +generic/546 +generic/548 +generic/549 +generic/550 +generic/552 +generic/553 +generic/555 +generic/556 +generic/566 +generic/567 +generic/572 +generic/573 +generic/574 +generic/575 +generic/576 +generic/577 +generic/578 +generic/580 +generic/581 +generic/582 +generic/583 +generic/584 +generic/586 +generic/587 +generic/588 +generic/592 +generic/593 +generic/594 +generic/595 +generic/596 +generic/597 +generic/598 +generic/599 +generic/600 +generic/601 +generic/602 +generic/603 +generic/605 +generic/606 +generic/607 +generic/608 +generic/609 +generic/610 +generic/612 +generic/613 +generic/621 +generic/623 +generic/624 +generic/625 +generic/626 +generic/628 +generic/629 +generic/630 +generic/635 +generic/644 +generic/645 +generic/646 +generic/647 +generic/651 +generic/652 +generic/653 +generic/654 +generic/655 +generic/657 +generic/658 +generic/659 +generic/660 +generic/661 +generic/662 +generic/663 +generic/664 +generic/665 +generic/666 +generic/667 +generic/668 +generic/669 +generic/673 +generic/674 +generic/675 +generic/677 +generic/678 +generic/679 +generic/680 +generic/681 +generic/682 +generic/683 +generic/684 +generic/685 +generic/686 +generic/687 +generic/688 +generic/689 +shared/002 +shared/032 +Passed all 509 tests diff --git a/tests/funcs/filter.sh b/tests/funcs/filter.sh index 3d89e855..831702e3 100644 --- a/tests/funcs/filter.sh +++ b/tests/funcs/filter.sh @@ -231,6 +231,11 @@ t_filter_dmesg() # lockdep or kasan warnings can cause this re="$re|Disabling lock debugging due to kernel taint" + # mixing mmap and direct I/O can generate a warning + re="$re|Page cache invalidation failure on direct I/O. Possible data corruption due to collision with buffered I/O!" + re="$re|File: /mnt/test.* PID:.* Comm: dd" + re="$re|dio_warn_stale_pagecache*" + egrep -v "($re)" | \ ignore_harmless_unwind_kasan_stack_oob | \ ignore_harmless_xfs_lockdep_warning diff --git a/tests/tests/xfstests.sh b/tests/tests/xfstests.sh index efc22cc6..5b45baf0 100644 --- a/tests/tests/xfstests.sh +++ b/tests/tests/xfstests.sh @@ -116,7 +116,14 @@ awk ' grep -E "^(Ran|Not run|Failures):" "$T_TMPDIR/results" | fmt -w 1 > "$T_TMPDIR/results.fmt" grep -E "^(Passed|Failed).*tests$" "$T_TMPDIR/results" >> "$T_TMPDIR/results.fmt" -diff -u "$T_EXTRA/expected-results" "$T_TMPDIR/results.fmt" > "$T_TMPDIR/results.diff" +if grep -q copy_page_to_iter_nofault /lib/modules/$(uname -r)/build/include/linux/uio.h +then + diff -u "$T_EXTRA/expected-results" "$T_TMPDIR/results.fmt" > "$T_TMPDIR/results.diff" +else + # no direct I/O + diff -u "$T_EXTRA/expected-results.nodio" "$T_TMPDIR/results.fmt" > "$T_TMPDIR/results.diff" +fi + if [ -s "$T_TMPDIR/results.diff" ]; then echo "tests that were skipped/run differed from expected:" cat "$T_TMPDIR/results.diff" From b2e5024888451512676c8576f1d7a5ab463559fa Mon Sep 17 00:00:00 2001 From: Chris Kirby Date: Mon, 13 Jul 2026 12:05:17 -0500 Subject: [PATCH 4/5] Fix lock ordering in fallocate and truncate In scoutfs_fallocate() and scoutfs_data_truncate_items(), the extent_sem and inode index lock were acquired in reverse order from other code paths. Swap that so everything is consistent. The ordering should now be as follows: VFS inode_lock cluster lock inode index lock extent_sem Signed-off-by: Chris Kirby --- kmod/src/data.c | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/kmod/src/data.c b/kmod/src/data.c index bdacc8b0..c5e00ede 100644 --- a/kmod/src/data.c +++ b/kmod/src/data.c @@ -320,10 +320,8 @@ int scoutfs_data_truncate_items(struct super_block *sb, struct inode *inode, if (WARN_ON_ONCE(last < iblock)) return -EINVAL; - if (inode) { + if (inode) si = SCOUTFS_I(inode); - down_write(&si->extent_sem); - } while (iblock <= last) { if (inode) @@ -333,10 +331,12 @@ int scoutfs_data_truncate_items(struct super_block *sb, struct inode *inode, if (ret) break; - if (inode) + if (inode) { + down_write(&si->extent_sem); ret = scoutfs_dirty_inode_item(inode, lock); - else + } else { ret = 0; + } if (ret == 0) ret = truncate_extents(sb, inode, ino, iblock, last, @@ -345,8 +345,10 @@ int scoutfs_data_truncate_items(struct super_block *sb, struct inode *inode, if (inode) scoutfs_update_inode_item(inode, lock, &ind_locks); scoutfs_release_trans(sb); - if (inode) + if (inode) { + up_write(&si->extent_sem); scoutfs_inode_index_unlock(sb, &ind_locks); + } if (ret <= 0) break; @@ -355,9 +357,6 @@ int scoutfs_data_truncate_items(struct super_block *sb, struct inode *inode, ret = 0; } - if (si) - up_write(&si->extent_sem); - return ret; } @@ -1111,13 +1110,11 @@ long scoutfs_fallocate(struct file *file, int mode, loff_t offset, loff_t len) inode_dio_wait(inode); - down_write(&si->extent_sem); - if (!(mode & FALLOC_FL_KEEP_SIZE) && (offset + len > i_size_read(inode))) { ret = inode_newsize_ok(inode, offset + len); if (ret) - goto out_extent; + goto out_mutex; } iblock = offset >> SCOUTFS_BLOCK_SM_SHIFT; @@ -1127,11 +1124,13 @@ long scoutfs_fallocate(struct file *file, int mode, loff_t offset, loff_t len) ret = scoutfs_quota_check_data(sb, inode); if (ret) - goto out_extent; + goto out_mutex; ret = scoutfs_inode_index_lock_hold(inode, &ind_locks, false, true); if (ret) - goto out_extent; + goto out_mutex; + + down_write(&si->extent_sem); ret = fallocate_extents(sb, inode, iblock, last, lock); @@ -1148,6 +1147,7 @@ long scoutfs_fallocate(struct file *file, int mode, loff_t offset, loff_t len) if (ret >= 0) scoutfs_update_inode_item(inode, lock, &ind_locks); scoutfs_release_trans(sb); + up_write(&si->extent_sem); scoutfs_inode_index_unlock(sb, &ind_locks); /* txn couldn't meet the request. Let's try with a new txn */ @@ -1157,14 +1157,12 @@ long scoutfs_fallocate(struct file *file, int mode, loff_t offset, loff_t len) } if (ret <= 0) - goto out_extent; + goto out_mutex; iblock += ret; ret = 0; } -out_extent: - up_write(&si->extent_sem); out_mutex: scoutfs_unlock(sb, lock, SCOUTFS_LOCK_WRITE); inode_unlock(inode); From f6e476d926b232c122a4937846143dab267fa956 Mon Sep 17 00:00:00 2001 From: Chris Kirby Date: Sat, 18 Jul 2026 13:07:12 -0500 Subject: [PATCH 5/5] Fix race in the mmap test The mmap test kicks off a background xfs_io job, then does a scoutfs stage operation in the foreground and sleeps for one second. Usually that's enough time for the background job to complete, but very rarely it's not. This manifests as data corruption when we try to read too soon. But we can also see from the jobs command output that there's still a job running. Use an explicit wait instead of the "sleep 1". Signed-off-by: Chris Kirby --- tests/golden/mmap | 2 -- tests/tests/mmap.sh | 8 ++------ 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/tests/golden/mmap b/tests/golden/mmap index 8d5a058e..33083c5c 100644 --- a/tests/golden/mmap +++ b/tests/golden/mmap @@ -10,14 +10,12 @@ thread 4 complete extents: 1 1 00000200: ea ea ea ea ea ea ea ea ea ea ea ea ea ea ea ea ................ -0 0: offset: 0 length: 2 flags: ..L extents: 1 == mmap write to an offline extent 0: offset: 0 length: 2 flags: O.L extents: 1 1 -0 0: offset: 0 length: 2 flags: ..L extents: 1 00000000 ea ea ea ea ea ea ea ea ea ea ea ea ea ea ea ea |................| diff --git a/tests/tests/mmap.sh b/tests/tests/mmap.sh index 8b617a36..9fb96ca3 100644 --- a/tests/tests/mmap.sh +++ b/tests/tests/mmap.sh @@ -27,9 +27,7 @@ sleep 1 jobs | wc -l scoutfs stage "${F}-stage" "$F" -V "$vers" -o 0 -l 8192 # xfs_io thread will output 16 bytes of read data -sleep 1 -# should be 0 - no more waiting jobs, xfs_io should have exited -jobs | wc -l +wait scoutfs get-fiemap -L "$F" echo "== mmap write to an offline extent" @@ -43,9 +41,7 @@ sleep 1 jobs | wc -l scoutfs stage "${F}-stage" "$F" -V "$vers" -o 0 -l 8192 # no output here from write -sleep 1 -# should be 0 - no more waiting jobs, xfs_io should have exited -jobs | wc -l +wait scoutfs get-fiemap -L "$F" # read back contents to assure write changed the file dd status=none if="$F" bs=1 count=48 skip=512 | hexdump -C