* [PATCH 1/4] btrfs-progs: mkfs/rootdir: use a helper to read from source fs
2026-03-30 7:09 [PATCH 0/4] btrfs-progs: mkfs/rootdir: cleanup and new fiemap based prealloc detection Qu Wenruo
@ 2026-03-30 7:09 ` Qu Wenruo
2026-03-30 7:09 ` [PATCH 2/4] btrfs-progs: mkfs/rootdir: extract compressed write path Qu Wenruo
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Qu Wenruo @ 2026-03-30 7:09 UTC (permalink / raw)
To: linux-btrfs
We have 3 pread() call sites that are doing a simple loop to read data
from the source fs.
They have similar and duplicated handling, extract a simple helper,
read_from_source() to handle them all.
Signed-off-by: Qu Wenruo <wqu@suse.com>
---
mkfs/rootdir.c | 85 +++++++++++++++++++++++---------------------------
1 file changed, 39 insertions(+), 46 deletions(-)
diff --git a/mkfs/rootdir.c b/mkfs/rootdir.c
index ed0fcfccd42a..f7712d75aa6f 100644
--- a/mkfs/rootdir.c
+++ b/mkfs/rootdir.c
@@ -780,6 +780,29 @@ static int do_reflink_write(struct btrfs_fs_info *info,
return 0;
}
+static int read_from_source(const struct btrfs_fs_info *fs_info,
+ const char *path, int fd,
+ char *buf, u64 filepos, u32 length)
+{
+ u32 cur = 0;
+
+ UASSERT(IS_ALIGNED(filepos, fs_info->sectorsize));
+ UASSERT(length <= MAX_EXTENT_SIZE);
+
+ while (cur < length) {
+ ssize_t ret;
+
+ ret = pread(fd, buf, length - cur, filepos + cur);
+ if (ret < 0) {
+ error("cannot read %s at offset %llu length %u: %m",
+ path, filepos + cur, length - cur);
+ return -errno;
+ }
+ cur += ret;
+ }
+ return length;
+}
+
static int add_file_item_extent(struct btrfs_trans_handle *trans,
struct btrfs_root *root,
struct btrfs_inode_item *btrfs_inode,
@@ -789,7 +812,7 @@ static int add_file_item_extent(struct btrfs_trans_handle *trans,
{
int ret;
u32 sectorsize = root->fs_info->sectorsize;
- u64 bytes_read, first_block, to_read, to_write;
+ u64 first_block, to_read, to_write;
struct btrfs_key key;
struct btrfs_file_extent_item stack_fi = { 0 };
u64 buf_size;
@@ -840,24 +863,11 @@ static int add_file_item_extent(struct btrfs_trans_handle *trans,
buf_size = do_comp ? BTRFS_MAX_COMPRESSED : MAX_EXTENT_SIZE;
to_read = min_t(u64, file_pos + buf_size, next) - file_pos;
- bytes_read = 0;
-
- while (bytes_read < to_read) {
- ssize_t ret_read;
-
- ret_read = pread(source->fd, source->buf + bytes_read,
- to_read - bytes_read, file_pos + bytes_read);
- if (ret_read < 0) {
- error("cannot read %s at offset %llu length %llu: %m",
- source->path_name, file_pos + bytes_read,
- to_read - bytes_read);
- return -errno;
- }
-
- bytes_read += ret_read;
- }
-
- if (bytes_read <= sectorsize)
+ ret = read_from_source(root->fs_info, source->path_name, source->fd,
+ source->buf, file_pos, to_read);
+ if (ret < 0)
+ return ret;
+ if (to_read <= sectorsize)
do_comp = false;
if (do_comp) {
@@ -866,21 +876,20 @@ static int add_file_item_extent(struct btrfs_trans_handle *trans,
switch (g_compression) {
case BTRFS_COMPRESS_ZLIB:
comp_ret = zlib_compress_extent(first_sector, sectorsize,
- source->buf, bytes_read,
+ source->buf, to_read,
source->comp_buf);
break;
#if COMPRESSION_LZO
case BTRFS_COMPRESS_LZO:
comp_ret = lzo_compress_extent(sectorsize, source->buf,
- bytes_read,
- source->comp_buf,
+ to_read, source->comp_buf,
source->wrkmem);
break;
#endif
#if COMPRESSION_ZSTD
case BTRFS_COMPRESS_ZSTD:
comp_ret = zstd_compress_extent(first_sector, sectorsize,
- source->buf, bytes_read,
+ source->buf, to_read,
source->comp_buf);
break;
#endif
@@ -905,24 +914,10 @@ static int add_file_item_extent(struct btrfs_trans_handle *trans,
buf_size = MAX_EXTENT_SIZE;
to_read = min_t(u64, file_pos + buf_size, next) - file_pos;
-
- while (bytes_read < to_read) {
- ssize_t ret_read;
-
- ret_read = pread(source->fd,
- source->buf + bytes_read,
- to_read - bytes_read,
- file_pos + bytes_read);
- if (ret_read < 0) {
- error("cannot read %s at offset %llu length %llu: %m",
- source->path_name,
- file_pos + bytes_read,
- to_read - bytes_read);
- return -errno;
- }
-
- bytes_read += ret_read;
- }
+ ret = read_from_source(root->fs_info, source->path_name, source->fd,
+ source->buf, file_pos, to_read);
+ if (ret < 0)
+ return ret;
}
}
@@ -1301,7 +1296,6 @@ static int add_file_items(struct btrfs_trans_handle *trans,
{
struct btrfs_fs_info *fs_info = trans->fs_info;
int ret = -1;
- ssize_t ret_read;
u32 sectorsize = fs_info->sectorsize;
u64 file_pos = 0;
char *buf = NULL, *comp_buf = NULL, *wrkmem = NULL;
@@ -1340,10 +1334,9 @@ static int add_file_items(struct btrfs_trans_handle *trans,
goto end;
}
- ret_read = pread(fd, buffer, st->st_size, 0);
- if (ret_read == -1) {
- error("cannot read %s at offset %u length %zu: %m",
- path_name, 0, st->st_size);
+ ret = read_from_source(fs_info, path_name, fd, buffer,
+ 0, st->st_size);
+ if (ret < 0) {
free(buffer);
goto end;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 2/4] btrfs-progs: mkfs/rootdir: extract compressed write path
2026-03-30 7:09 [PATCH 0/4] btrfs-progs: mkfs/rootdir: cleanup and new fiemap based prealloc detection Qu Wenruo
2026-03-30 7:09 ` [PATCH 1/4] btrfs-progs: mkfs/rootdir: use a helper to read from source fs Qu Wenruo
@ 2026-03-30 7:09 ` Qu Wenruo
2026-03-30 7:09 ` [PATCH 3/4] btrfs-progs: mkfs/rootdir: use fiemap to do prealloc detection Qu Wenruo
2026-03-30 7:09 ` [PATCH 4/4] btrfs-progs: mkfs-tests: add a new test case for fiemap based detection Qu Wenruo
3 siblings, 0 replies; 5+ messages in thread
From: Qu Wenruo @ 2026-03-30 7:09 UTC (permalink / raw)
To: linux-btrfs
Currently add_file_item_extent() have several different
optimizations/handlings:
- Hole detection
Which happens before any writes.
Thus brings the minimal impact to the remaining methods.
- Compressed write
- Reflink from source fs
- Regular read/writes
The last 3 share the same extent reservation, but with quite some extra
handling.
E.g. for compressed writes if the compression failed, we need to reset
the buffer size and fallback to regular read/writes.
This makes the code much harder to read, and the shared code is minimal,
only sharing the same btrfs_reserve_extent() and
insert_reserved_file_extent() calls.
Extract compressed write into its dedicated helper so that the fallback
logic is much easier to understand.
Signed-off-by: Qu Wenruo <wqu@suse.com>
---
mkfs/rootdir.c | 193 ++++++++++++++++++++++++++++---------------------
1 file changed, 112 insertions(+), 81 deletions(-)
diff --git a/mkfs/rootdir.c b/mkfs/rootdir.c
index f7712d75aa6f..cc80ef67683f 100644
--- a/mkfs/rootdir.c
+++ b/mkfs/rootdir.c
@@ -803,6 +803,102 @@ static int read_from_source(const struct btrfs_fs_info *fs_info,
return length;
}
+/*
+ * Return >0 for the number of bytes read from @source and submittted as
+ * compressed write.
+ * Return <0 for errors, including non-fatal ones, e.g. -E2BIG if compression
+ * ratio is bad.
+ */
+static int try_compressed_write(struct btrfs_trans_handle *trans,
+ struct btrfs_root *root,
+ struct btrfs_inode_item *btrfs_inode,
+ u64 objectid,
+ const struct source_descriptor *source,
+ u64 filepos, u32 length)
+{
+ struct btrfs_fs_info *fs_info = root->fs_info;
+ struct btrfs_file_extent_item stack_fi = { 0 };
+ struct btrfs_key key;
+ const u32 blocksize = fs_info->sectorsize;
+ const bool first_sector = !(btrfs_stack_inode_flags(btrfs_inode) &
+ BTRFS_INODE_COMPRESS);
+ u64 inode_flags = btrfs_stack_inode_flags(btrfs_inode);
+ u64 sb_flags = btrfs_super_incompat_flags(fs_info->super_copy);
+ u32 to_write;
+ ssize_t comp_ret;
+ int ret;
+
+ UASSERT(length > 0);
+ length = min_t(u32, length, BTRFS_MAX_COMPRESSED);
+ if (length <= root->fs_info->sectorsize)
+ return -E2BIG;
+ ret = read_from_source(root->fs_info, source->path_name, source->fd,
+ source->buf, filepos, length);
+ if (ret < 0)
+ return ret;
+ switch (g_compression) {
+ case BTRFS_COMPRESS_ZLIB:
+ comp_ret = zlib_compress_extent(first_sector, blocksize,
+ source->buf, length,
+ source->comp_buf);
+ break;
+#if COMPRESSION_LZO
+ case BTRFS_COMPRESS_LZO:
+ sb_flags |= BTRFS_FEATURE_INCOMPAT_COMPRESS_LZO;
+ comp_ret = lzo_compress_extent(first_sector, source->buf,
+ length, source->comp_buf,
+ source->wrkmem);
+ break;
+#endif
+#if COMPRESSION_ZSTD
+ case BTRFS_COMPRESS_ZSTD:
+ sb_flags |= BTRFS_FEATURE_INCOMPAT_COMPRESS_ZSTD;
+ comp_ret = zstd_compress_extent(first_sector, blocksize,
+ source->buf, length,
+ source->comp_buf);
+ break;
+#endif
+ default:
+ comp_ret = -EINVAL;
+ break;
+ }
+ if (comp_ret < 0)
+ return comp_ret;
+
+ to_write = round_up(comp_ret, blocksize);
+ memset(source->comp_buf + comp_ret, 0, to_write - comp_ret);
+ inode_flags |= BTRFS_INODE_COMPRESS;
+ btrfs_set_stack_inode_flags(btrfs_inode, inode_flags);
+ btrfs_set_super_incompat_flags(fs_info->super_copy, sb_flags);
+
+ ret = btrfs_reserve_extent(trans, root, to_write, 0, 0, (u64)-1, &key, 1);
+ if (ret < 0)
+ return ret;
+ ret = write_data_to_disk(fs_info, source->comp_buf, key.objectid, to_write);
+ if (ret < 0)
+ return ret;
+ for (unsigned int i = 0; i < to_write / blocksize; i++) {
+ ret = btrfs_csum_file_block(trans, key.objectid + (i * blocksize),
+ BTRFS_EXTENT_CSUM_OBJECTID,
+ root->fs_info->csum_type,
+ source->comp_buf + (i * blocksize));
+ if (ret)
+ return ret;
+ }
+ btrfs_set_stack_file_extent_type(&stack_fi, BTRFS_FILE_EXTENT_REG);
+ btrfs_set_stack_file_extent_disk_bytenr(&stack_fi, key.objectid);
+ btrfs_set_stack_file_extent_disk_num_bytes(&stack_fi, to_write);
+ btrfs_set_stack_file_extent_num_bytes(&stack_fi, round_up(length, blocksize));
+ btrfs_set_stack_file_extent_ram_bytes(&stack_fi, round_up(length, blocksize));
+ btrfs_set_stack_file_extent_compression(&stack_fi, g_compression);
+
+ ret = insert_reserved_file_extent(trans, root, objectid, btrfs_inode,
+ filepos, &stack_fi);
+ if (ret < 0)
+ return ret;
+ return length;
+}
+
static int add_file_item_extent(struct btrfs_trans_handle *trans,
struct btrfs_root *root,
struct btrfs_inode_item *btrfs_inode,
@@ -819,7 +915,6 @@ static int add_file_item_extent(struct btrfs_trans_handle *trans,
char *write_buf;
bool do_comp = g_compression != BTRFS_COMPRESS_NONE;
bool datasum = true;
- ssize_t comp_ret;
u64 flags = btrfs_stack_inode_flags(btrfs_inode);
off64_t next;
@@ -861,92 +956,28 @@ static int add_file_item_extent(struct btrfs_trans_handle *trans,
if (next == (off64_t)-1 || !IS_ALIGNED(next, sectorsize) || next > source->size)
next = source->size;
- buf_size = do_comp ? BTRFS_MAX_COMPRESSED : MAX_EXTENT_SIZE;
+ if (do_comp && next - file_pos > sectorsize) {
+ ret = try_compressed_write(trans, root, btrfs_inode, objectid,
+ source, file_pos, next - file_pos);
+ if (ret > 0)
+ return ret;
+ if (ret < 0 && ret != -E2BIG)
+ return ret;
+ flags |= BTRFS_INODE_NOCOMPRESS;
+ btrfs_set_stack_inode_flags(btrfs_inode, flags);
+ /* Fallback to other methods. */
+ }
+
+ buf_size = MAX_EXTENT_SIZE;
to_read = min_t(u64, file_pos + buf_size, next) - file_pos;
ret = read_from_source(root->fs_info, source->path_name, source->fd,
source->buf, file_pos, to_read);
if (ret < 0)
return ret;
- if (to_read <= sectorsize)
- do_comp = false;
- if (do_comp) {
- bool first_sector = !(flags & BTRFS_INODE_COMPRESS);
-
- switch (g_compression) {
- case BTRFS_COMPRESS_ZLIB:
- comp_ret = zlib_compress_extent(first_sector, sectorsize,
- source->buf, to_read,
- source->comp_buf);
- break;
-#if COMPRESSION_LZO
- case BTRFS_COMPRESS_LZO:
- comp_ret = lzo_compress_extent(sectorsize, source->buf,
- to_read, source->comp_buf,
- source->wrkmem);
- break;
-#endif
-#if COMPRESSION_ZSTD
- case BTRFS_COMPRESS_ZSTD:
- comp_ret = zstd_compress_extent(first_sector, sectorsize,
- source->buf, to_read,
- source->comp_buf);
- break;
-#endif
- default:
- comp_ret = -EINVAL;
- break;
- }
-
- /*
- * If the function returned -E2BIG, the extent is incompressible.
- * If this is the first sector, add the nocompress flag,
- * increase the buffer size, and read the rest of the extent.
- */
- if (comp_ret == -E2BIG)
- do_comp = false;
- else if (comp_ret < 0)
- return comp_ret;
-
- if (comp_ret == -E2BIG && first_sector) {
- flags |= BTRFS_INODE_NOCOMPRESS;
- btrfs_set_stack_inode_flags(btrfs_inode, flags);
-
- buf_size = MAX_EXTENT_SIZE;
- to_read = min_t(u64, file_pos + buf_size, next) - file_pos;
- ret = read_from_source(root->fs_info, source->path_name, source->fd,
- source->buf, file_pos, to_read);
- if (ret < 0)
- return ret;
- }
- }
-
- if (do_comp) {
- u64 features;
-
- to_write = round_up(comp_ret, sectorsize);
- write_buf = source->comp_buf;
- memset(write_buf + comp_ret, 0, to_write - comp_ret);
-
- flags |= BTRFS_INODE_COMPRESS;
- btrfs_set_stack_inode_flags(btrfs_inode, flags);
-
- if (g_compression == BTRFS_COMPRESS_ZSTD) {
- features = btrfs_super_incompat_flags(trans->fs_info->super_copy);
- features |= BTRFS_FEATURE_INCOMPAT_COMPRESS_ZSTD;
- btrfs_set_super_incompat_flags(trans->fs_info->super_copy,
- features);
- } else if (g_compression == BTRFS_COMPRESS_LZO) {
- features = btrfs_super_incompat_flags(trans->fs_info->super_copy);
- features |= BTRFS_FEATURE_INCOMPAT_COMPRESS_LZO;
- btrfs_set_super_incompat_flags(trans->fs_info->super_copy,
- features);
- }
- } else {
- to_write = round_up(to_read, sectorsize);
- write_buf = source->buf;
- memset(write_buf + to_read, 0, to_write - to_read);
- }
+ to_write = round_up(to_read, sectorsize);
+ write_buf = source->buf;
+ memset(write_buf + to_read, 0, to_write - to_read);
ret = btrfs_reserve_extent(trans, root, to_write, 0, 0,
(u64)-1, &key, 1);
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 3/4] btrfs-progs: mkfs/rootdir: use fiemap to do prealloc detection
2026-03-30 7:09 [PATCH 0/4] btrfs-progs: mkfs/rootdir: cleanup and new fiemap based prealloc detection Qu Wenruo
2026-03-30 7:09 ` [PATCH 1/4] btrfs-progs: mkfs/rootdir: use a helper to read from source fs Qu Wenruo
2026-03-30 7:09 ` [PATCH 2/4] btrfs-progs: mkfs/rootdir: extract compressed write path Qu Wenruo
@ 2026-03-30 7:09 ` Qu Wenruo
2026-03-30 7:09 ` [PATCH 4/4] btrfs-progs: mkfs-tests: add a new test case for fiemap based detection Qu Wenruo
3 siblings, 0 replies; 5+ messages in thread
From: Qu Wenruo @ 2026-03-30 7:09 UTC (permalink / raw)
To: linux-btrfs
Introduce a new fiemap ioctl based hole/prealloc detection, which has
the following features:
- Preallocated extent detection
- Hole detection
This allows the resulted image to reflect the source rootdir better,
with proper preallocated extents.
However not all major fses support fiemap, e.g. tmpfs doesn't support
fiemap.
So we still need the existing SEEK_DATA/SEEK_HOLE based solution as a
fallback.
Furthermore, SEEK_DATA/SEEK_HOLE will treat preallocated space as a
hole, thus we have to attempt fiemap before SEEK_DATA/SEEK_HOLE.
Finally there is function defined but not implemented,
btrfs_insert_hole_extent(), remove it and implement a local version
insert_hole_extent() inside rootdir.c to simplify the hole insertion.
Signed-off-by: Qu Wenruo <wqu@suse.com>
---
kernel-shared/file-item.h | 3 -
mkfs/rootdir.c | 138 +++++++++++++++++++++++++++++++++++---
2 files changed, 129 insertions(+), 12 deletions(-)
diff --git a/kernel-shared/file-item.h b/kernel-shared/file-item.h
index 4f24fcb18026..7b721c10c82e 100644
--- a/kernel-shared/file-item.h
+++ b/kernel-shared/file-item.h
@@ -54,9 +54,6 @@ static inline u32 btrfs_file_extent_calc_inline_size(u32 datasize)
int btrfs_del_csums(struct btrfs_trans_handle *trans,
struct btrfs_root *root, u64 bytenr, u64 len);
int btrfs_lookup_bio_sums(struct btrfs_bio *bbio);
-int btrfs_insert_hole_extent(struct btrfs_trans_handle *trans,
- struct btrfs_root *root, u64 objectid, u64 pos,
- u64 num_bytes);
int btrfs_lookup_file_extent(struct btrfs_trans_handle *trans,
struct btrfs_root *root,
struct btrfs_path *path, u64 objectid,
diff --git a/mkfs/rootdir.c b/mkfs/rootdir.c
index cc80ef67683f..4c3b4e423385 100644
--- a/mkfs/rootdir.c
+++ b/mkfs/rootdir.c
@@ -36,6 +36,7 @@
#if COMPRESSION_LZO
#include <lzo/lzo1x.h>
#endif
+#include <linux/fiemap.h>
#include "kernel-lib/sizes.h"
#include "kernel-shared/accessors.h"
#include "kernel-shared/uapi/btrfs_tree.h"
@@ -697,11 +698,13 @@ out:
struct source_descriptor {
int fd;
+ u32 blocksize;
char *buf;
u64 size;
const char *path_name;
char *comp_buf;
char *wrkmem;
+ bool no_fiemap;
};
static int do_reflink_write(struct btrfs_fs_info *info,
@@ -899,11 +902,123 @@ static int try_compressed_write(struct btrfs_trans_handle *trans,
return length;
}
+/*
+ * Return 0 if the hole extent is inserted (or nothing to be done for no-holes feature).
+ * Return <0 for errors.
+ */
+static int insert_hole_extent(struct btrfs_trans_handle *trans, struct btrfs_root *root,
+ u64 ino, u64 filepos, u64 length)
+{
+ struct btrfs_file_extent_item stack_fi = { 0 };
+ const u32 blocksize = root->fs_info->sectorsize;
+
+ UASSERT(IS_ALIGNED(filepos, blocksize));
+ UASSERT(IS_ALIGNED(length, blocksize));
+ UASSERT(length < INT_MAX);
+
+ btrfs_set_stack_file_extent_num_bytes(&stack_fi, 0);
+ btrfs_set_stack_file_extent_ram_bytes(&stack_fi, 0);
+ return btrfs_insert_file_extent(trans, root, ino, filepos, &stack_fi);
+}
+
+/*
+ * Return 0 if we are unable to use fiemap result to handle any range.
+ * Return >0 for the length of preallocated/hole space we have inserted.
+ * Return <0 for critical errors.
+ */
+static int try_fiemap(struct btrfs_trans_handle *trans,
+ struct btrfs_root *root,
+ struct btrfs_inode_item *btrfs_inode,
+ u64 objectid,
+ struct source_descriptor *source,
+ u64 filepos)
+{
+ struct btrfs_fs_info *fs_info = root->fs_info;
+ struct btrfs_file_extent_item stack_fi = { 0 };
+ struct btrfs_key key;
+ const u32 blocksize = fs_info->sectorsize;
+ struct fiemap *fiemap;
+ struct fiemap_extent *extent;
+ u64 length = round_up(source->size, blocksize) - filepos;
+ int ret;
+
+ UASSERT(IS_ALIGNED(filepos, blocksize));
+ length = min_t(u64, length, MAX_EXTENT_SIZE);
+
+ if (blocksize != source->blocksize || source->no_fiemap)
+ return 0;
+ fiemap = malloc(sizeof(struct fiemap) + sizeof(struct fiemap_extent));
+ if (!fiemap)
+ return -ENOMEM;
+
+ fiemap->fm_flags = FIEMAP_FLAG_SYNC;
+ fiemap->fm_extent_count = 1;
+ fiemap->fm_mapped_extents = 0;
+ fiemap->fm_start = filepos;
+ fiemap->fm_length = length;
+
+ ret = ioctl(source->fd, FS_IOC_FIEMAP, (unsigned long)fiemap);
+ if (ret < 0) {
+ source->no_fiemap = true;
+ free(fiemap);
+ return 0;
+ }
+ /* This is no more non-hole extent beyond @filepos. */
+ if (fiemap->fm_mapped_extents < 1) {
+ free(fiemap);
+ ret = insert_hole_extent(trans, root, objectid, filepos, length);
+ if (ret < 0)
+ return ret;
+ return length;
+ }
+ /*
+ * We really only care about the first returned extent which covers
+ * our block.
+ */
+ extent = &fiemap->fm_extents[0];
+
+ /*
+ * Returned range is beyond our @filepos. This means a hole
+ * between @filepos and @fe_logical.
+ */
+ if (extent->fe_logical > filepos) {
+ length = extent->fe_logical - filepos;
+ free(fiemap);
+ ret = insert_hole_extent(trans, root, objectid, filepos, length);
+ if (ret < 0)
+ return ret;
+ return length;
+ }
+
+ if (!(extent->fe_flags & FIEMAP_EXTENT_UNWRITTEN)) {
+ free(fiemap);
+ return 0;
+ }
+
+ length = extent->fe_logical + extent->fe_length - filepos;
+ free(fiemap);
+ ret = btrfs_reserve_extent(trans, root, length, 0, 0, (u64)-1, &key, 1);
+ if (ret)
+ return ret;
+
+ btrfs_set_stack_inode_flags(btrfs_inode,
+ btrfs_stack_inode_flags(btrfs_inode) | BTRFS_INODE_PREALLOC);
+ btrfs_set_stack_file_extent_type(&stack_fi, BTRFS_FILE_EXTENT_PREALLOC);
+ btrfs_set_stack_file_extent_disk_bytenr(&stack_fi, key.objectid);
+ btrfs_set_stack_file_extent_disk_num_bytes(&stack_fi, length);
+ btrfs_set_stack_file_extent_ram_bytes(&stack_fi, length);
+ btrfs_set_stack_file_extent_num_bytes(&stack_fi, length);
+ ret = insert_reserved_file_extent(trans, root, objectid, btrfs_inode, filepos, &stack_fi);
+ if (ret < 0)
+ return ret;
+ return length;
+}
+
static int add_file_item_extent(struct btrfs_trans_handle *trans,
struct btrfs_root *root,
struct btrfs_inode_item *btrfs_inode,
u64 objectid,
- const struct source_descriptor *source,
+ struct source_descriptor *source,
u64 file_pos)
{
int ret;
@@ -916,7 +1031,7 @@ static int add_file_item_extent(struct btrfs_trans_handle *trans,
bool do_comp = g_compression != BTRFS_COMPRESS_NONE;
bool datasum = true;
u64 flags = btrfs_stack_inode_flags(btrfs_inode);
- off64_t next;
+ u64 next = file_pos;
if (g_do_reflink || flags & BTRFS_INODE_NOCOMPRESS)
do_comp = false;
@@ -926,6 +1041,14 @@ static int add_file_item_extent(struct btrfs_trans_handle *trans,
do_comp = false;
}
+ /*
+ * Try prealloc before hole detection, as preallocated space is also treated
+ * as hole by SEEK_DATA.
+ */
+ ret = try_fiemap(trans, root, btrfs_inode, objectid, source, file_pos);
+ if (ret)
+ return ret;
+
next = lseek64(source->fd, file_pos, SEEK_DATA);
/* The current offset is inside a hole to the next of the file. */
if (next == (off64_t)-1 && errno == ENXIO)
@@ -937,14 +1060,9 @@ static int add_file_item_extent(struct btrfs_trans_handle *trans,
*/
const u64 length = min_t(u64, next - file_pos, SZ_1G);
- btrfs_set_stack_file_extent_num_bytes(&stack_fi, length);
- btrfs_set_stack_file_extent_ram_bytes(&stack_fi, length);
- ret = btrfs_insert_file_extent(trans, root, objectid, file_pos, &stack_fi);
- if (ret < 0) {
- error("cannot insert hole for range [%llu, %llu)",
- file_pos, file_pos + length);
+ ret = insert_hole_extent(trans, root, objectid, file_pos, length);
+ if (ret < 0)
return ret;
- }
return length;
}
@@ -1469,9 +1587,11 @@ static int add_file_items(struct btrfs_trans_handle *trans,
source.fd = fd;
source.buf = buf;
source.size = st->st_size;
+ source.blocksize = st->st_blksize;
source.path_name = path_name;
source.comp_buf = comp_buf;
source.wrkmem = wrkmem;
+ source.no_fiemap = false;
while (file_pos < st->st_size) {
ret = add_file_item_extent(trans, root, btrfs_inode, objectid,
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* [PATCH 4/4] btrfs-progs: mkfs-tests: add a new test case for fiemap based detection
2026-03-30 7:09 [PATCH 0/4] btrfs-progs: mkfs/rootdir: cleanup and new fiemap based prealloc detection Qu Wenruo
` (2 preceding siblings ...)
2026-03-30 7:09 ` [PATCH 3/4] btrfs-progs: mkfs/rootdir: use fiemap to do prealloc detection Qu Wenruo
@ 2026-03-30 7:09 ` Qu Wenruo
3 siblings, 0 replies; 5+ messages in thread
From: Qu Wenruo @ 2026-03-30 7:09 UTC (permalink / raw)
To: linux-btrfs
The new test case will create an inode on btrfs, with the following
layout:
- [0, 16K): hole
- [16K, 32K): regular
- [32K, 64K): hole
- [64K, 80K): prealloc
- [80K, 84K): regular
Using fiemap based detection, we should be able to create a btrfs with
exactly the same layout.
Signed-off-by: Qu Wenruo <wqu@suse.com>
---
tests/mkfs-tests/042-fiemap-detection/test.sh | 76 +++++++++++++++++++
1 file changed, 76 insertions(+)
create mode 100755 tests/mkfs-tests/042-fiemap-detection/test.sh
diff --git a/tests/mkfs-tests/042-fiemap-detection/test.sh b/tests/mkfs-tests/042-fiemap-detection/test.sh
new file mode 100755
index 000000000000..e652791565ce
--- /dev/null
+++ b/tests/mkfs-tests/042-fiemap-detection/test.sh
@@ -0,0 +1,76 @@
+#!/bin/bash
+# Test basic hole detection features
+
+source "$TEST_TOP/common" || exit
+
+check_prereq mkfs.btrfs
+check_prereq btrfs
+check_global_prereq xfs_io
+
+if ! [ -f "/sys/fs/btrfs/features/supported_sectorsizes" ]; then
+ _not_run "kernel support for different block sizes missing"
+fi
+
+setup_root_helper
+
+# tmpdir is normally inside /tmp, which can be tmpfs on a lot of distro.
+# Unfortunately tmpfs doesn't support fiemap ioctl.
+# So we need another btrfs as rootdir.
+setup_loopdevs 2
+prepare_loopdevs
+
+real_dev=${loopdevs[1]}
+rootdir_dev=${loopdevs[2]}
+
+blocksize_supported=false
+for bs in $(cat /sys/fs/btrfs/features/supported_sectorsizes); do
+ if [ "$bs" == "4096" ]; then
+ blocksize_supported=true
+ fi
+done
+
+if [ "$blocksize_supported" != "true" ]; then
+ _not_run "kernel support for mounting blocksize 4096 is missing"
+fi
+
+run_check $SUDO_HELPER "$TOP/mkfs.btrfs" -f -s 4k -O no-holes "$rootdir_dev"
+run_check $SUDO_HELPER mount "$rootdir_dev" "$TEST_MNT"
+
+# Create an inode with holes, preallocated and regular file extents.
+run_check $SUDO_HELPER xfs_io -f \
+ -c "pwrite 16k 16k" -c "falloc 64k 16k" -c "pwrite 80k 4k" -c sync \
+ "$TEST_MNT/foobar"
+
+run_check $SUDO_HELPER "$TOP/mkfs.btrfs" -f --rootdir "$TEST_MNT" "$real_dev"
+run_check $SUDO_HELPER umount "$TEST_MNT"
+
+run_check $SUDO_HELPER "$TOP/btrfs" check "$real_dev"
+
+# Check the dump-tree of each extent at filepos 16K, 64K and 80K.
+run_check $SUDO_HELPER "$TOP/btrfs" inspect-internal dump-tree -t 5 "$real_dev"
+
+if ! "$TOP/btrfs" ins dump-tree -t 5 "$real_dev" |\
+ grep -A1 "EXTENT_DATA 16384" | grep -q "type 1 (regular)"; then
+ _fail "data extent at 16K is not regular"
+fi
+
+if ! "$TOP/btrfs" ins dump-tree -t 5 "$real_dev" |\
+ grep -A1 "EXTENT_DATA 65536" | grep -q "type 2 (prealloc)"; then
+ _fail "data extent at 64K is not preallocated"
+fi
+
+if ! "$TOP/btrfs" ins dump-tree -t 5 "$real_dev" |\
+ grep -A1 "EXTENT_DATA 81920" | grep -q "type 1 (regular)"; then
+ _fail "data extent at 80K is not regular"
+fi
+
+# Since we have no-holes features, the extents at 0K, 32K should not exist
+if "$TOP/btrfs" ins dump-tree -t 5 "$real_dev" | grep -q "EXTENT_DATA 0"; then
+ _fail "data extent at 0 is not a hole"
+fi
+
+if "$TOP/btrfs" ins dump-tree -t 5 "$real_dev" | grep -q "EXTENT_DATA 32768"; then
+ _fail "data extent at 0 is not a hole"
+fi
+
+cleanup_loopdevs
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread