From: Darrick J. Wong <djwong@xxxxxxxxxx> Add a function to dump an inode's extent map for debugging purposes. This helped debug a problem with generic/299 failing on 1k fsblock filesystems: --- a/tests/generic/299.out 2025-07-15 14:45:15.030113607 -0700 +++ b/tests/generic/299.out.bad 2025-07-16 19:33:50.889344998 -0700 @@ -3,3 +3,4 @@ QA output created by 299 Run fio with random aio-dio pattern Start fallocate/truncate loop +fio: io_u error on file /opt/direct_aio.0.0: Input/output error: write offset=2602827776, buflen=131072 (The cause of this was misuse of the libext2fs extent code) Signed-off-by: "Darrick J. Wong" <djwong@xxxxxxxxxx> --- misc/fuse2fs.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ misc/fuse4fs.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) diff --git a/misc/fuse2fs.c b/misc/fuse2fs.c index 1dda9c45cb5089..4a9fda62f99bc2 100644 --- a/misc/fuse2fs.c +++ b/misc/fuse2fs.c @@ -578,6 +578,74 @@ static inline int fuse2fs_iomap_enabled(const struct fuse2fs *ff) # define fuse2fs_iomap_enabled(...) (0) #endif +static inline void fuse2fs_dump_extents(struct fuse2fs *ff, ext2_ino_t ino, + struct ext2_inode_large *inode, + const char *why) +{ + ext2_filsys fs = ff->fs; + unsigned int nr = 0; + blk64_t blockcount = 0; + struct ext2_inode_large xinode; + struct ext2fs_extent extent; + ext2_extent_handle_t extents; + int op = EXT2_EXTENT_ROOT; + errcode_t retval; + + if (!inode) { + inode = &xinode; + + retval = fuse2fs_read_inode(fs, ino, inode); + if (retval) { + com_err(__func__, retval, _("reading ino %u"), ino); + return; + } + } + + if (!(inode->i_flags & EXT4_EXTENTS_FL)) + return; + + printf("%s: %s ino=%u isize %llu iblocks %llu\n", __func__, why, ino, + EXT2_I_SIZE(inode), + (ext2fs_get_stat_i_blocks(fs, EXT2_INODE(inode)) * 512) / + fs->blocksize); + fflush(stdout); + + retval = ext2fs_extent_open(fs, ino, &extents); + if (retval) { + com_err(__func__, retval, _("opening extents of ino \"%u\""), + ino); + return; + } + + while ((retval = ext2fs_extent_get(extents, op, &extent)) == 0) { + op = EXT2_EXTENT_NEXT; + + if (extent.e_flags & EXT2_EXTENT_FLAGS_SECOND_VISIT) + continue; + + printf("[%u]: %s ino=%u lblk 0x%llx pblk 0x%llx len 0x%x flags 0x%x\n", + nr++, why, ino, extent.e_lblk, extent.e_pblk, + extent.e_len, extent.e_flags); + fflush(stdout); + if (extent.e_flags & EXT2_EXTENT_FLAGS_LEAF) + blockcount += extent.e_len; + else + blockcount++; + } + if (retval == EXT2_ET_EXTENT_NO_NEXT) + retval = 0; + if (retval) { + com_err(__func__, retval, ("getting extents of ino %u"), + ino); + } + if (inode->i_file_acl) + blockcount++; + printf("%s: %s sum(e_len) %llu\n", __func__, why, blockcount); + fflush(stdout); + + ext2fs_extent_free(extents); +} + static void get_now(struct timespec *now) { #ifdef CLOCK_REALTIME @@ -5433,6 +5501,11 @@ static int op_iomap_begin(const char *path, uint64_t nodeid, uint64_t attr_ino, (unsigned long long)read->length, read->type); + /* Not filling even the first byte will make the kernel unhappy. */ + if (ff->debug && (read->offset > pos || + read->offset + read->length <= pos)) + fuse2fs_dump_extents(ff, attr_ino, &inode, "BAD DATA"); + out_unlock: fuse2fs_finish(ff, ret); return ret; diff --git a/misc/fuse4fs.c b/misc/fuse4fs.c index 2aa7ab646592e9..0ac5de90498dac 100644 --- a/misc/fuse4fs.c +++ b/misc/fuse4fs.c @@ -730,6 +730,74 @@ static inline int fuse4fs_iomap_enabled(const struct fuse4fs *ff) # define fuse4fs_iomap_enabled(...) (0) #endif +static inline void fuse4fs_dump_extents(struct fuse4fs *ff, ext2_ino_t ino, + struct ext2_inode_large *inode, + const char *why) +{ + ext2_filsys fs = ff->fs; + unsigned int nr = 0; + blk64_t blockcount = 0; + struct ext2_inode_large xinode; + struct ext2fs_extent extent; + ext2_extent_handle_t extents; + int op = EXT2_EXTENT_ROOT; + errcode_t retval; + + if (!inode) { + inode = &xinode; + + retval = fuse4fs_read_inode(fs, ino, inode); + if (retval) { + com_err(__func__, retval, _("reading ino %u"), ino); + return; + } + } + + if (!(inode->i_flags & EXT4_EXTENTS_FL)) + return; + + printf("%s: %s ino=%u isize %llu iblocks %llu\n", __func__, why, ino, + EXT2_I_SIZE(inode), + (ext2fs_get_stat_i_blocks(fs, EXT2_INODE(inode)) * 512) / + fs->blocksize); + fflush(stdout); + + retval = ext2fs_extent_open(fs, ino, &extents); + if (retval) { + com_err(__func__, retval, _("opening extents of ino \"%u\""), + ino); + return; + } + + while ((retval = ext2fs_extent_get(extents, op, &extent)) == 0) { + op = EXT2_EXTENT_NEXT; + + if (extent.e_flags & EXT2_EXTENT_FLAGS_SECOND_VISIT) + continue; + + printf("[%u]: %s ino=%u lblk 0x%llx pblk 0x%llx len 0x%x flags 0x%x\n", + nr++, why, ino, extent.e_lblk, extent.e_pblk, + extent.e_len, extent.e_flags); + fflush(stdout); + if (extent.e_flags & EXT2_EXTENT_FLAGS_LEAF) + blockcount += extent.e_len; + else + blockcount++; + } + if (retval == EXT2_ET_EXTENT_NO_NEXT) + retval = 0; + if (retval) { + com_err(__func__, retval, ("getting extents of ino %u"), + ino); + } + if (inode->i_file_acl) + blockcount++; + printf("%s: %s sum(e_len) %llu\n", __func__, why, blockcount); + fflush(stdout); + + ext2fs_extent_free(extents); +} + static void get_now(struct timespec *now) { #ifdef CLOCK_REALTIME @@ -5839,6 +5907,11 @@ static void op_iomap_begin(fuse_req_t req, fuse_ino_t fino, uint64_t dontcare, read.type, read.flags); + /* Not filling even the first byte will make the kernel unhappy. */ + if (ff->debug && (read.offset > pos || + read.offset + read.length <= pos)) + fuse4fs_dump_extents(ff, ino, &inode, "BAD DATA"); + out_unlock: fuse4fs_finish(ff, ret); if (ret)