[PATCH v3 0/8] U-boot: fs: add generic unaligned read offset handling

[CHANGELOG] v3: - Fix an error that we always return 0 actread bytes for unsupported fses For unsupported fses, we should also populate @total_read. Or we will just read the data but still return 0 for actually bytes.
Now it pass all test_fs* cases.
v2 - Fix a linkage error where (U64 % U32) is called without proper helper Fix it with U64 & (U32 - 1), as the U32 value (@blocksize) should always be power of 2, thus (@blocksize - 1) is the mask we want to calculate the offset inside the block.
Above change only affects the 4th patch, everything else is not touched.
RFC->v1: - More (manual) testing Unfortunately, in the latest master (75967970850a), the fs-tests.sh always seems to hang at preparing the fs image.
Thus still has to do manual testing, tested btrfs, ext4 and fat, with aligned and unaligned read, also added soft link read, all looks fine here.
Extra testing is still appreciated.
- Two more btrfs specific bug fixes All exposed during manual tests
- Remove the tailing unaligned block handling In fact, all fses can easily handle such case, just a min() call is enough.
- Remove the support for sandboxfs Since it's using read() calls, really no need to do block alignment check.
- Enhanced blocksize check Ensure the returned blocksize is not only non-error, but also non-zero.
Qu Wenruo (8): fs: fat: unexport file_fat_read_at() fs: btrfs: fix a bug which no data get read if the length is not 0 fs: btrfs: fix a crash if specified range is beyond file size fs: btrfs: move the unaligned read code to _fs_read() for btrfs fs: ext4: rely on _fs_read() to handle leading unaligned block read fs: fat: rely on higher layer to get block aligned read range fs: ubifs: rely on higher layer to do unaligned read fs: erofs: add unaligned read range handling
fs/btrfs/btrfs.c | 33 ++++++++--- fs/btrfs/inode.c | 89 +++-------------------------- fs/erofs/internal.h | 1 + fs/erofs/super.c | 6 ++ fs/ext4/ext4fs.c | 22 +++++++ fs/fat/fat.c | 17 +++++- fs/fs.c | 130 +++++++++++++++++++++++++++++++++++++++--- fs/ubifs/ubifs.c | 13 +++-- include/btrfs.h | 1 + include/erofs.h | 1 + include/ext4fs.h | 1 + include/fat.h | 3 +- include/ubifs_uboot.h | 1 + 13 files changed, 212 insertions(+), 106 deletions(-)

That function is only utilized inside fat driver, unexport it.
Signed-off-by: Qu Wenruo wqu@suse.com --- fs/fat/fat.c | 4 ++-- include/fat.h | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/fs/fat/fat.c b/fs/fat/fat.c index df9ea2c028fc..dcceccbcee0a 100644 --- a/fs/fat/fat.c +++ b/fs/fat/fat.c @@ -1243,8 +1243,8 @@ out_free_itr: return ret; }
-int file_fat_read_at(const char *filename, loff_t pos, void *buffer, - loff_t maxsize, loff_t *actread) +static int file_fat_read_at(const char *filename, loff_t pos, void *buffer, + loff_t maxsize, loff_t *actread) { fsdata fsdata; fat_itr *itr; diff --git a/include/fat.h b/include/fat.h index bd8e450b33a3..a9756fb4cd1b 100644 --- a/include/fat.h +++ b/include/fat.h @@ -200,8 +200,6 @@ static inline u32 sect_to_clust(fsdata *fsdata, int sect) int file_fat_detectfs(void); int fat_exists(const char *filename); int fat_size(const char *filename, loff_t *size); -int file_fat_read_at(const char *filename, loff_t pos, void *buffer, - loff_t maxsize, loff_t *actread); int file_fat_read(const char *filename, void *buffer, int maxsize); int fat_set_blk_dev(struct blk_desc *rbdd, struct disk_partition *info); int fat_register_device(struct blk_desc *dev_desc, int part_no);

[BUG] When testing with unaligned read, if a specific length is passed in, btrfs driver will read out nothing:
=> load host 0 $kernel_addr_r 5k_file 0x1000 0 0 bytes read in 0 ms
But if no length is passed in, it works fine, even if we pass a non-zero length:
=> load host 0 $kernel_addr_r 5k_file 0 0x1000 1024 bytes read in 0 ms
[CAUSE] In btrfs_read() if we have a larger size than our file, we will try to truncate it using the file size.
However the real file size is not initialized if @len is not zero, thus we always truncate our length to 0, and cause the problem.
[FIX] Fix it by just always do the file size check.
In fact btrfs_size() always follow soft link, thus it will return the real file size correctly.
Signed-off-by: Qu Wenruo wqu@suse.com --- fs/btrfs/btrfs.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/fs/btrfs/btrfs.c b/fs/btrfs/btrfs.c index 4cdbbbe3d066..309cd595d37d 100644 --- a/fs/btrfs/btrfs.c +++ b/fs/btrfs/btrfs.c @@ -246,16 +246,17 @@ int btrfs_read(const char *file, void *buf, loff_t offset, loff_t len, return -EINVAL; }
- if (!len) { - ret = btrfs_size(file, &real_size); - if (ret < 0) { - error("Failed to get inode size: %s", file); - return ret; - } - len = real_size; + ret = btrfs_size(file, &real_size); + if (ret < 0) { + error("Failed to get inode size: %s", file); + return ret; }
- if (len > real_size - offset) + /* + * If the length is 0 (meaning read the whole file) or the range is + * beyond file size, truncate it to the end of the file. + */ + if (!len || len > real_size - offset) len = real_size - offset;
ret = btrfs_file_read(root, ino, offset, len, buf);

[BUG] When try to read a range beyond file size, btrfs driver will cause crash/segfault:
=> load host 0 $kernel_addr_r 5k_file 0 0x2000 SEGFAULT
[CAUSE] In btrfs_read(), if @len is 0, we will truncated it to file end, but if file end is beyond our file size, this truncation will underflow @len, making it -3K in this case.
And later that @len is used to memzero the output buffer, resulting above crash.
[FIX] Just error out if @offset is already beyond our file size.
Now it will fail properly with correct error message:
=> load host 0 $kernel_addr_r 5m_origin 0 0x2000 BTRFS: Read range beyond file size, offset 8192 file size 5120
Failed to load '5m_origin'
Signed-off-by: Qu Wenruo wqu@suse.com --- fs/btrfs/btrfs.c | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/fs/btrfs/btrfs.c b/fs/btrfs/btrfs.c index 309cd595d37d..74a992fa012d 100644 --- a/fs/btrfs/btrfs.c +++ b/fs/btrfs/btrfs.c @@ -252,6 +252,12 @@ int btrfs_read(const char *file, void *buf, loff_t offset, loff_t len, return ret; }
+ if (offset >= real_size) { + error("Read range beyond file size, offset %llu file size %llu", + offset, real_size); + return -EINVAL; + } + /* * If the length is 0 (meaning read the whole file) or the range is * beyond file size, truncate it to the end of the file.

Unlike FUSE or kernel, U-boot filesystem code makes the underly fs code to handle the unaligned read (aka, read range is not aligned to fs block size).
This makes underlying fs code harder to implement, as they have to handle unaligned read all by themselves.
This patch will change the behavior, starting from btrfs, by moving the unaligned read code into _fs_read().
The idea is pretty simple, if we have an unaligned read request, we handle it in the following steps:
1. Grab the blocksize of the fs
2. Read the leading unaligned range We will read the block that @offset is in, and copy the requested part into buf.
The the block we read covers the whole range, we just call it a day.
3. Read the remaining part The tailing part may be unaligned, but all fses handles the tailing part much easier than the leading unaligned part.
As they just need to do a min(extent_size, start + len - cur) to calculate the real read size.
In fact, for most file reading, the file size is not aligned and we need to handle the tailing part anyway.
There is a btrfs specific cleanup involved:
- In btrfs_file_read(), merge the tailing unaligned read into the main loop. Just reuse the existing read length calculation is enough.
- Remove read_and_truncate_page() call Since there is no explicit leading/tailing unaligned read anymore.
This has been tested with a proper randomly populated btrfs file, then tried in sandbox mode with different aligned and unaligned range and compare the output with md5sum.
Cc: Marek Behun marek.behun@nic.cz Cc: linux-btrfs@vger.kernel.org Signed-off-by: Qu Wenruo wqu@suse.com --- fs/btrfs/btrfs.c | 10 ++++ fs/btrfs/inode.c | 89 +++----------------------------- fs/fs.c | 130 ++++++++++++++++++++++++++++++++++++++++++++--- include/btrfs.h | 1 + 4 files changed, 141 insertions(+), 89 deletions(-)
diff --git a/fs/btrfs/btrfs.c b/fs/btrfs/btrfs.c index 74a992fa012d..ac0e972d0249 100644 --- a/fs/btrfs/btrfs.c +++ b/fs/btrfs/btrfs.c @@ -234,6 +234,10 @@ int btrfs_read(const char *file, void *buf, loff_t offset, loff_t len, int ret;
ASSERT(fs_info); + + /* Higher layer has ensures it never pass unaligned offset in. */ + ASSERT(IS_ALIGNED(offset, fs_info->sectorsize)); + ret = btrfs_lookup_path(fs_info->fs_root, BTRFS_FIRST_FREE_OBJECTID, file, &root, &ino, &type, 40); if (ret < 0) { @@ -275,6 +279,12 @@ int btrfs_read(const char *file, void *buf, loff_t offset, loff_t len, return 0; }
+int btrfs_get_blocksize(const char *filename) +{ + ASSERT(current_fs_info); + return current_fs_info->sectorsize; +} + void btrfs_close(void) { if (current_fs_info) { diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 40025662f250..f12be46f6262 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -617,44 +617,6 @@ check_next: return 1; }
-static int read_and_truncate_page(struct btrfs_path *path, - struct btrfs_file_extent_item *fi, - int start, int len, char *dest) -{ - struct extent_buffer *leaf = path->nodes[0]; - struct btrfs_fs_info *fs_info = leaf->fs_info; - u64 aligned_start = round_down(start, fs_info->sectorsize); - u8 extent_type; - char *buf; - int page_off = start - aligned_start; - int page_len = fs_info->sectorsize - page_off; - int ret; - - ASSERT(start + len <= aligned_start + fs_info->sectorsize); - buf = malloc_cache_aligned(fs_info->sectorsize); - if (!buf) - return -ENOMEM; - - extent_type = btrfs_file_extent_type(leaf, fi); - if (extent_type == BTRFS_FILE_EXTENT_INLINE) { - ret = btrfs_read_extent_inline(path, fi, buf); - memcpy(dest, buf + page_off, min(page_len, ret)); - free(buf); - return len; - } - - ret = btrfs_read_extent_reg(path, fi, - round_down(start, fs_info->sectorsize), - fs_info->sectorsize, buf); - if (ret < 0) { - free(buf); - return ret; - } - memcpy(dest, buf + page_off, page_len); - free(buf); - return len; -} - int btrfs_file_read(struct btrfs_root *root, u64 ino, u64 file_offset, u64 len, char *dest) { @@ -663,7 +625,6 @@ int btrfs_file_read(struct btrfs_root *root, u64 ino, u64 file_offset, u64 len, struct btrfs_path path; struct btrfs_key key; u64 aligned_start = round_down(file_offset, fs_info->sectorsize); - u64 aligned_end = round_down(file_offset + len, fs_info->sectorsize); u64 next_offset; u64 cur = aligned_start; int ret = 0; @@ -673,34 +634,14 @@ int btrfs_file_read(struct btrfs_root *root, u64 ino, u64 file_offset, u64 len, /* Set the whole dest all zero, so we won't need to bother holes */ memset(dest, 0, len);
- /* Read out the leading unaligned part */ - if (aligned_start != file_offset) { - ret = lookup_data_extent(root, &path, ino, aligned_start, - &next_offset); - if (ret < 0) - goto out; - if (ret == 0) { - /* Read the unaligned part out*/ - fi = btrfs_item_ptr(path.nodes[0], path.slots[0], - struct btrfs_file_extent_item); - ret = read_and_truncate_page(&path, fi, file_offset, - round_up(file_offset, fs_info->sectorsize) - - file_offset, dest); - if (ret < 0) - goto out; - cur += fs_info->sectorsize; - } else { - /* The whole file is a hole */ - if (!next_offset) { - memset(dest, 0, len); - return len; - } - cur = next_offset; - } - } + /* + * Ensured by higher layer, which should have already handled the + * first unaligned sector. + */ + ASSERT(aligned_start == file_offset);
/* Read the aligned part */ - while (cur < aligned_end) { + while (cur < file_offset + len) { u64 extent_num_bytes; u8 type;
@@ -743,27 +684,13 @@ int btrfs_file_read(struct btrfs_root *root, u64 ino, u64 file_offset, u64 len, extent_num_bytes = btrfs_file_extent_num_bytes(path.nodes[0], fi); ret = btrfs_read_extent_reg(&path, fi, cur, - min(extent_num_bytes, aligned_end - cur), + min(extent_num_bytes, file_offset + len - cur), dest + cur - file_offset); if (ret < 0) goto out; - cur += min(extent_num_bytes, aligned_end - cur); + cur += min(extent_num_bytes, file_offset + len - cur); }
- /* Read the tailing unaligned part*/ - if (file_offset + len != aligned_end) { - btrfs_release_path(&path); - ret = lookup_data_extent(root, &path, ino, aligned_end, - &next_offset); - /* <0 is error, >0 means no extent */ - if (ret) - goto out; - fi = btrfs_item_ptr(path.nodes[0], path.slots[0], - struct btrfs_file_extent_item); - ret = read_and_truncate_page(&path, fi, aligned_end, - file_offset + len - aligned_end, - dest + aligned_end - file_offset); - } out: btrfs_release_path(&path); if (ret < 0) diff --git a/fs/fs.c b/fs/fs.c index 8324b4a22f20..6d43d616de4b 100644 --- a/fs/fs.c +++ b/fs/fs.c @@ -29,6 +29,7 @@ #include <efi_loader.h> #include <squashfs.h> #include <erofs.h> +#include <memalign.h>
DECLARE_GLOBAL_DATA_PTR;
@@ -140,6 +141,11 @@ static inline int fs_mkdir_unsupported(const char *dirname) return -1; }
+static inline int fs_get_blocksize_unsupported(const char *filename) +{ + return -1; +} + struct fstype_info { int fstype; char *name; @@ -159,6 +165,14 @@ struct fstype_info { int (*size)(const char *filename, loff_t *size); int (*read)(const char *filename, void *buf, loff_t offset, loff_t len, loff_t *actread); + /* + * Report the minimal data blocksize the fs supprts. + * + * This is used to handle unaligned read offset. + * If not supported, read() will handle the unaligned offset all by + * itself. + */ + int (*get_blocksize)(const char *filename); int (*write)(const char *filename, void *buf, loff_t offset, loff_t len, loff_t *actwrite); void (*close)(void); @@ -194,6 +208,7 @@ static struct fstype_info fstypes[] = { .exists = fat_exists, .size = fat_size, .read = fat_read_file, + .get_blocksize = fs_get_blocksize_unsupported, #if CONFIG_IS_ENABLED(FAT_WRITE) .write = file_fat_write, .unlink = fat_unlink, @@ -222,6 +237,7 @@ static struct fstype_info fstypes[] = { .exists = ext4fs_exists, .size = ext4fs_size, .read = ext4_read_file, + .get_blocksize = fs_get_blocksize_unsupported, #ifdef CONFIG_CMD_EXT4_WRITE .write = ext4_write_file, .ln = ext4fs_create_link, @@ -246,6 +262,11 @@ static struct fstype_info fstypes[] = { .exists = sandbox_fs_exists, .size = sandbox_fs_size, .read = fs_read_sandbox, + /* + * Sandbox doesn't need to bother blocksize, as its + * os_read() can handle unaligned range without any problem. + */ + .get_blocksize = fs_get_blocksize_unsupported, .write = fs_write_sandbox, .uuid = fs_uuid_unsupported, .opendir = fs_opendir_unsupported, @@ -265,6 +286,12 @@ static struct fstype_info fstypes[] = { .exists = fs_exists_unsupported, .size = smh_fs_size, .read = smh_fs_read, + /* + * Semihost doesn't need to bother blocksize, as it is using + * read() system calls, and can handle unaligned range without + * any problem. + */ + .get_blocksize = fs_get_blocksize_unsupported, .write = smh_fs_write, .uuid = fs_uuid_unsupported, .opendir = fs_opendir_unsupported, @@ -285,6 +312,7 @@ static struct fstype_info fstypes[] = { .exists = ubifs_exists, .size = ubifs_size, .read = ubifs_read, + .get_blocksize = fs_get_blocksize_unsupported, .write = fs_write_unsupported, .uuid = fs_uuid_unsupported, .opendir = fs_opendir_unsupported, @@ -306,6 +334,7 @@ static struct fstype_info fstypes[] = { .exists = btrfs_exists, .size = btrfs_size, .read = btrfs_read, + .get_blocksize = btrfs_get_blocksize, .write = fs_write_unsupported, .uuid = btrfs_uuid, .opendir = fs_opendir_unsupported, @@ -325,6 +354,7 @@ static struct fstype_info fstypes[] = { .readdir = sqfs_readdir, .ls = fs_ls_generic, .read = sqfs_read, + .get_blocksize = fs_get_blocksize_unsupported, .size = sqfs_size, .close = sqfs_close, .closedir = sqfs_closedir, @@ -346,6 +376,7 @@ static struct fstype_info fstypes[] = { .readdir = erofs_readdir, .ls = fs_ls_generic, .read = erofs_read, + .get_blocksize = fs_get_blocksize_unsupported, .size = erofs_size, .close = erofs_close, .closedir = erofs_closedir, @@ -367,6 +398,7 @@ static struct fstype_info fstypes[] = { .exists = fs_exists_unsupported, .size = fs_size_unsupported, .read = fs_read_unsupported, + .get_blocksize = fs_get_blocksize_unsupported, .write = fs_write_unsupported, .uuid = fs_uuid_unsupported, .opendir = fs_opendir_unsupported, @@ -580,7 +612,11 @@ static int _fs_read(const char *filename, ulong addr, loff_t offset, loff_t len, { struct fstype_info *info = fs_get_info(fs_type); void *buf; + int blocksize; int ret; + loff_t cur = offset; + loff_t bytes_read = 0; + loff_t total_read = 0;
#ifdef CONFIG_LMB if (do_lmb_check) { @@ -590,19 +626,97 @@ static int _fs_read(const char *filename, ulong addr, loff_t offset, loff_t len, } #endif
+ blocksize = info->get_blocksize(filename); /* - * We don't actually know how many bytes are being read, since len==0 - * means read the whole file. + * The fs doesn't report its blocksize, let its read() to handle + * the unaligned read. + */ + if (blocksize < 0) { + buf = map_sysmem(addr, len); + ret = info->read(filename, buf, offset, len, &total_read); + + /* If we requested a specific number of bytes, check we got it */ + if (ret == 0 && len && total_read != len) + log_debug("** %s shorter than offset + len **\n", filename); + goto out; + } + + if (unlikely(blocksize == 0)) { + log_err("invalid blocksize 0 found\n"); + return -EINVAL; + } + + /* + * @len can be 0, meaning read the whole file. + * And we can not rely on info->size(), as some fses doesn't resolve + * softlinks to their final destinations. */ buf = map_sysmem(addr, len); - ret = info->read(filename, buf, offset, len, actread); - unmap_sysmem(buf);
- /* If we requested a specific number of bytes, check we got it */ - if (ret == 0 && len && *actread != len) - log_debug("** %s shorter than offset + len **\n", filename); - fs_close(); + /* Unaligned read offset, handle the unaligned read here. */ + if (!IS_ALIGNED(offset, blocksize)) { + void *block_buf; + const int offset_in_block = offset & (blocksize - 1); + int copy_len; + + block_buf = malloc_cache_aligned(blocksize); + if (!block_buf) { + log_err("** Unable to alloc memory for one block **\n"); + return -ENOMEM; + } + memset(block_buf, 0, blocksize); + + cur = round_down(offset, blocksize); + ret = info->read(filename, block_buf, cur, blocksize, + &bytes_read); + if (ret < 0) { + log_err("** Failed to read %s at offset %llu, %d **\n", + filename, cur, ret); + free(block_buf); + goto out; + } + if (bytes_read <= offset_in_block) { + log_err("** Offset %llu is beyond file size of %s **\n", + offset, filename); + free(block_buf); + ret = -EIO; + goto out; + } + + copy_len = min_t(int, blocksize, bytes_read) - offset_in_block; + memcpy(buf, block_buf + offset_in_block, copy_len); + free(block_buf); + total_read += copy_len; + + /* + * A short read on the block, or we have already covered the + * whole read range, just call it a day. + */ + if (bytes_read < blocksize || + (len && offset + len <= cur + blocksize)) + goto out; + + cur += blocksize; + if (len) + len -= copy_len; + } + + ret = info->read(filename, buf + total_read, cur, len, &bytes_read); + if (ret < 0) { + log_err("** failed to read %s off %llu len %llu, %d **\n", + filename, cur, len, ret); + goto out; + } + if (len && bytes_read < len) + log_debug("** %s short read, off %llu len %llu actual read %llu **\n", + filename, cur, len, bytes_read); + total_read += bytes_read;
+out: + unmap_sysmem(buf); + fs_close(); + if (!ret) + *actread = total_read; return ret; }
diff --git a/include/btrfs.h b/include/btrfs.h index 2d73add18e09..a2d709273b53 100644 --- a/include/btrfs.h +++ b/include/btrfs.h @@ -17,6 +17,7 @@ int btrfs_ls(const char *); int btrfs_exists(const char *); int btrfs_size(const char *, loff_t *); int btrfs_read(const char *, void *, loff_t, loff_t, loff_t *); +int btrfs_get_blocksize(const char *); void btrfs_close(void); int btrfs_uuid(char *); void btrfs_list_subvols(void);

Just add ext4_get_blocksize() and a new assert() in ext4fs_read_file().
Signed-off-by: Qu Wenruo wqu@suse.com --- Several cleanup candicate:
1. ext_fs->blksz is never populated, thus it's always 0 We can not easily grab blocksize just like btrfs in this case.
Thus we have to go the same calculation in ext4fs_read_file().
2. can not easily cleanup @skipfirst in ext4fs_read_file() It seems to screw up with soft link read. --- fs/ext4/ext4fs.c | 22 ++++++++++++++++++++++ fs/fs.c | 2 +- include/ext4fs.h | 1 + 3 files changed, 24 insertions(+), 1 deletion(-)
diff --git a/fs/ext4/ext4fs.c b/fs/ext4/ext4fs.c index 4c89152ce4ad..b0568e77a895 100644 --- a/fs/ext4/ext4fs.c +++ b/fs/ext4/ext4fs.c @@ -69,6 +69,8 @@ int ext4fs_read_file(struct ext2fs_node *node, loff_t pos, short status; struct ext_block_cache cache;
+ assert(IS_ALIGNED(pos, blocksize)); + ext_cache_init(&cache);
/* Adjust len so it we can't read past the end of the file. */ @@ -259,6 +261,26 @@ int ext4_read_file(const char *filename, void *buf, loff_t offset, loff_t len, return ext4fs_read(buf, offset, len, len_read); }
+int ext4_get_blocksize(const char *filename) +{ + struct ext_filesystem *fs; + int log2blksz; + int log2_fs_blocksize; + loff_t file_len; + int ret; + + ret = ext4fs_open(filename, &file_len); + if (ret < 0) { + printf("** File not found %s **\n", filename); + return -1; + } + fs = get_fs(); + log2blksz = fs->dev_desc->log2blksz; + log2_fs_blocksize = LOG2_BLOCK_SIZE(ext4fs_file->data) - log2blksz; + + return (1 << (log2_fs_blocksize + log2blksz)); +} + int ext4fs_uuid(char *uuid_str) { if (ext4fs_root == NULL) diff --git a/fs/fs.c b/fs/fs.c index 6d43d616de4b..b26cc8e2e840 100644 --- a/fs/fs.c +++ b/fs/fs.c @@ -237,7 +237,7 @@ static struct fstype_info fstypes[] = { .exists = ext4fs_exists, .size = ext4fs_size, .read = ext4_read_file, - .get_blocksize = fs_get_blocksize_unsupported, + .get_blocksize = ext4_get_blocksize, #ifdef CONFIG_CMD_EXT4_WRITE .write = ext4_write_file, .ln = ext4fs_create_link, diff --git a/include/ext4fs.h b/include/ext4fs.h index cb5d9cc0a5c0..0f4cf32dcc2a 100644 --- a/include/ext4fs.h +++ b/include/ext4fs.h @@ -161,6 +161,7 @@ int ext4fs_probe(struct blk_desc *fs_dev_desc, struct disk_partition *fs_partition); int ext4_read_file(const char *filename, void *buf, loff_t offset, loff_t len, loff_t *actread); +int ext4_get_blocksize(const char *filename); int ext4_read_superblock(char *buffer); int ext4fs_uuid(char *uuid_str); void ext_cache_init(struct ext_block_cache *cache);

Just implement fat_get_blocksize() for fat, so that fat_read_file() always get a block aligned read range.
Unfortunately I'm not experienced enough to cleanup the fat code, thus further cleanup is appreciated.
Cc: Tom Rini trini@konsulko.com Signed-off-by: Qu Wenruo wqu@suse.com --- fs/fat/fat.c | 13 +++++++++++++ fs/fs.c | 2 +- include/fat.h | 1 + 3 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/fs/fat/fat.c b/fs/fat/fat.c index dcceccbcee0a..e13035e8e6d1 100644 --- a/fs/fat/fat.c +++ b/fs/fat/fat.c @@ -1299,6 +1299,19 @@ int fat_read_file(const char *filename, void *buf, loff_t offset, loff_t len, return ret; }
+int fat_get_blocksize(const char *filename) +{ + fsdata fsdata = {0}; + int ret; + + ret = get_fs_info(&fsdata); + if (ret) + return ret; + + free(fsdata.fatbuf); + return fsdata.sect_size; +} + typedef struct { struct fs_dir_stream parent; struct fs_dirent dirent; diff --git a/fs/fs.c b/fs/fs.c index b26cc8e2e840..ea4325cd0b00 100644 --- a/fs/fs.c +++ b/fs/fs.c @@ -208,7 +208,7 @@ static struct fstype_info fstypes[] = { .exists = fat_exists, .size = fat_size, .read = fat_read_file, - .get_blocksize = fs_get_blocksize_unsupported, + .get_blocksize = fat_get_blocksize, #if CONFIG_IS_ENABLED(FAT_WRITE) .write = file_fat_write, .unlink = fat_unlink, diff --git a/include/fat.h b/include/fat.h index a9756fb4cd1b..c03a2bebecef 100644 --- a/include/fat.h +++ b/include/fat.h @@ -201,6 +201,7 @@ int file_fat_detectfs(void); int fat_exists(const char *filename); int fat_size(const char *filename, loff_t *size); int file_fat_read(const char *filename, void *buffer, int maxsize); +int fat_get_blocksize(const char *filename); int fat_set_blk_dev(struct blk_desc *rbdd, struct disk_partition *info); int fat_register_device(struct blk_desc *dev_desc, int part_no);

Currently ubifs doesn't support unaligned read offset, thanks to the recent _fs_read() work to handle unaligned read, we only need to implement ubifs_get_blocksize() to take advantage of it.
Now ubifs can do unaligned read without any problem.
Signed-off-by: Qu Wenruo wqu@suse.com --- Unfortunately I can not test ubifs, as enabling UBI would cause compile failure due to missing of <asm/atomic.h> header. --- fs/fs.c | 2 +- fs/ubifs/ubifs.c | 13 ++++++++----- include/ubifs_uboot.h | 1 + 3 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/fs/fs.c b/fs/fs.c index ea4325cd0b00..43c7128bcfc5 100644 --- a/fs/fs.c +++ b/fs/fs.c @@ -312,7 +312,7 @@ static struct fstype_info fstypes[] = { .exists = ubifs_exists, .size = ubifs_size, .read = ubifs_read, - .get_blocksize = fs_get_blocksize_unsupported, + .get_blocksize = ubifs_get_blocksize, .write = fs_write_unsupported, .uuid = fs_uuid_unsupported, .opendir = fs_opendir_unsupported, diff --git a/fs/ubifs/ubifs.c b/fs/ubifs/ubifs.c index d3026e310168..a8ab556dd376 100644 --- a/fs/ubifs/ubifs.c +++ b/fs/ubifs/ubifs.c @@ -846,11 +846,9 @@ int ubifs_read(const char *filename, void *buf, loff_t offset,
*actread = 0;
- if (offset & (PAGE_SIZE - 1)) { - printf("ubifs: Error offset must be a multiple of %d\n", - PAGE_SIZE); - return -1; - } + /* Higher layer should ensure it always pass page aligned range. */ + assert(IS_ALIGNED(offset, PAGE_SIZE)); + assert(IS_ALIGNED(size, PAGE_SIZE));
c->ubi = ubi_open_volume(c->vi.ubi_num, c->vi.vol_id, UBI_READONLY); /* ubifs_findfile will resolve symlinks, so we know that we get @@ -920,6 +918,11 @@ out: return err; }
+int ubifs_get_blocksize(const char *filename) +{ + return PAGE_SIZE; +} + void ubifs_close(void) { } diff --git a/include/ubifs_uboot.h b/include/ubifs_uboot.h index b025779d59ff..bcd21715314a 100644 --- a/include/ubifs_uboot.h +++ b/include/ubifs_uboot.h @@ -29,6 +29,7 @@ int ubifs_exists(const char *filename); int ubifs_size(const char *filename, loff_t *size); int ubifs_read(const char *filename, void *buf, loff_t offset, loff_t size, loff_t *actread); +int ubifs_get_blocksize(const char *filename); void ubifs_close(void);
#endif /* __UBIFS_UBOOT_H__ */

I'm not an expert on erofs, but my quick glance didn't expose any special handling on unaligned range, thus I think the U-boot erofs driver doesn't really support unaligned read range.
This patch will add erofs_get_blocksize() so erofs can benefit from the generic unaligned read support.
Cc: Huang Jianan jnhuang95@gmail.com Cc: linux-erofs@lists.ozlabs.org Signed-off-by: Qu Wenruo wqu@suse.com --- fs/erofs/internal.h | 1 + fs/erofs/super.c | 6 ++++++ fs/fs.c | 2 +- include/erofs.h | 1 + 4 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h index 4af7c91560cc..d368a6481bf1 100644 --- a/fs/erofs/internal.h +++ b/fs/erofs/internal.h @@ -83,6 +83,7 @@ struct erofs_sb_info { u16 available_compr_algs; u16 lz4_max_distance; u32 checksum; + u32 blocksize; u16 extra_devices; union { u16 devt_slotoff; /* used for mkfs */ diff --git a/fs/erofs/super.c b/fs/erofs/super.c index 8277d9b53fb3..82625da59001 100644 --- a/fs/erofs/super.c +++ b/fs/erofs/super.c @@ -99,7 +99,13 @@ int erofs_read_superblock(void)
sbi.build_time = le64_to_cpu(dsb->build_time); sbi.build_time_nsec = le32_to_cpu(dsb->build_time_nsec); + sbi.blocksize = 1 << blkszbits;
memcpy(&sbi.uuid, dsb->uuid, sizeof(dsb->uuid)); return erofs_init_devices(&sbi, dsb); } + +int erofs_get_blocksize(const char *filename) +{ + return sbi.blocksize; +} diff --git a/fs/fs.c b/fs/fs.c index 43c7128bcfc5..2ac43c05fcd8 100644 --- a/fs/fs.c +++ b/fs/fs.c @@ -376,7 +376,7 @@ static struct fstype_info fstypes[] = { .readdir = erofs_readdir, .ls = fs_ls_generic, .read = erofs_read, - .get_blocksize = fs_get_blocksize_unsupported, + .get_blocksize = erofs_get_blocksize, .size = erofs_size, .close = erofs_close, .closedir = erofs_closedir, diff --git a/include/erofs.h b/include/erofs.h index 1fbe82bf72cb..18bd6807c538 100644 --- a/include/erofs.h +++ b/include/erofs.h @@ -10,6 +10,7 @@ int erofs_probe(struct blk_desc *fs_dev_desc, struct disk_partition *fs_partition); int erofs_read(const char *filename, void *buf, loff_t offset, loff_t len, loff_t *actread); +int erofs_get_blocksize(const char *filename); int erofs_size(const char *filename, loff_t *size); int erofs_exists(const char *filename); void erofs_close(void);

On Mon, Aug 15, 2022 at 07:45:11PM +0800, Qu Wenruo wrote:
[CHANGELOG] v3:
Fix an error that we always return 0 actread bytes for unsupported fses For unsupported fses, we should also populate @total_read. Or we will just read the data but still return 0 for actually bytes.
Now it pass all test_fs* cases.
We're failing squashfs still, sorry: https://source.denx.de/u-boot/u-boot/-/jobs/486819#L399
participants (2)
-
Qu Wenruo
-
Tom Rini