* [PATCH v2] xfs: avoid false ENOSPC for fallocate ranges with real extents
@ 2026-08-03 8:56 Huiwen He
0 siblings, 0 replies; only message in thread
From: Huiwen He @ 2026-08-03 8:56 UTC (permalink / raw)
To: cem; +Cc: linux-xfs, linux-kernel, pc, stfrench
From: Huiwen He <hehuiwen@kylinos.cn>
Fallocate can incorrectly return ENOSPC when the requested range
contains real extents. This was observed when extending EOF over a
range previously allocated with KEEP_SIZE.
For example, on an XFS filesystem with 7G total capacity:
$ df -h .
Filesystem Size Used Avail Use% Mounted on
/dev/loop0 7.0G 169M 6.8G 3% /mnt/test
$ xfs_io -f -c "falloc -k 0 4G" file
$ xfs_io -c "falloc 0 4G" file
fallocate: No space left on device
The first fallocate has already allocated the whole 4G range and leaves
about 2.8G free. The second fallocate should only extend EOF without
allocating more data blocks.
However, xfs_alloc_file_space() reserves space before xfs_bmapi_write()
checks the existing mappings. The second 4G reservation therefore
fails with ENOSPC.
The same problem can occur in the unshare path. Unaligned ZERO_RANGE
can also include real extents because its rounded preallocation covers
partial edge blocks left allocated after hole punching.
Fix this by walking the existing mappings first. Skip written and
unwritten real extents, and call xfs_alloc_file_space() only for holes
or delayed-allocation extents. Factor this into
xfs_falloc_allocate_space(), shared by the zero-range, allocate-range,
and unshare-range paths.
After this change, the EOF extension, unshare, and ZERO_RANGE commands
below succeed:
$ xfs_io -f -c "falloc -k 0 4G" file
# EOF extension succeeds.
$ xfs_io -c "falloc 0 4G" file
# Unshare succeeds.
$ xfs_io -c "falloc -u 0 4G" file
$ xfs_io -f -c "pwrite -S 0x5a 0 4K" allocated
$ dd if=/dev/zero of=filler bs=4K status=none
dd: error writing 'filler': No space left on device
# Unaligned ZERO_RANGE succeeds.
$ xfs_io -c "fzero 1 4094" allocated
Reported-by: Paulo Alcantara <pc@manguebit.org>
Link: https://lore.kernel.org/linux-cifs/97c0ca61c2bed2d2559b32b7ed967121@manguebit.org
Signed-off-by: Huiwen He <hehuiwen@kylinos.cn>
---
Changes since v1:
- Use xfs_falloc_allocate_space() in xfs_falloc_zero_range() to avoid
false ENOSPC for real edge extents in unaligned ZERO_RANGE requests.
- Add an unaligned ZERO_RANGE test to the commit message.
Link to v1:
https://lore.kernel.org/linux-xfs/20260802151754.50459-1-huiwen.he@linux.dev/
fs/xfs/xfs_file.c | 59 ++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 56 insertions(+), 3 deletions(-)
diff --git a/fs/xfs/xfs_file.c b/fs/xfs/xfs_file.c
index 845a97c9b063..17e6316c58db 100644
--- a/fs/xfs/xfs_file.c
+++ b/fs/xfs/xfs_file.c
@@ -1368,6 +1368,59 @@ xfs_falloc_force_zero(
return XFS_TEST_ERROR(ip->i_mount, XFS_ERRTAG_FORCE_ZERO_RANGE);
}
+/*
+ * Allocate only mappings that are not already backed by physical blocks.
+ * This avoids reserving data space for real extents before
+ * xfs_bmapi_write() discovers the existing mappings.
+ */
+static int
+xfs_falloc_allocate_space(
+ struct xfs_inode *ip,
+ loff_t offset,
+ loff_t len)
+{
+ struct xfs_mount *mp = ip->i_mount;
+ struct xfs_bmbt_irec imap;
+ xfs_fileoff_t start_fsb = XFS_B_TO_FSBT(mp, offset);
+ xfs_fileoff_t end_fsb = XFS_B_TO_FSB(mp, offset + len);
+ unsigned int lock_mode;
+ int error = 0;
+
+ xfs_assert_ilocked(ip, XFS_IOLOCK_EXCL | XFS_MMAPLOCK_EXCL);
+
+ lock_mode = xfs_ilock_data_map_shared(ip);
+ while (start_fsb < end_fsb) {
+ xfs_filblks_t count_fsb = end_fsb - start_fsb;
+ int nimaps = 1;
+
+ error = xfs_bmapi_read(ip, start_fsb, count_fsb, &imap,
+ &nimaps, 0);
+ if (error)
+ break;
+ if (XFS_IS_CORRUPT(mp, nimaps != 1 ||
+ imap.br_startoff != start_fsb ||
+ !imap.br_blockcount ||
+ imap.br_blockcount > count_fsb)) {
+ error = -EFSCORRUPTED;
+ break;
+ }
+
+ start_fsb += imap.br_blockcount;
+ if (xfs_bmap_is_real_extent(&imap))
+ continue;
+
+ xfs_iunlock(ip, lock_mode);
+ error = xfs_alloc_file_space(ip,
+ XFS_FSB_TO_B(mp, imap.br_startoff),
+ XFS_FSB_TO_B(mp, imap.br_blockcount));
+ if (error)
+ return error;
+ lock_mode = xfs_ilock_data_map_shared(ip);
+ }
+ xfs_iunlock(ip, lock_mode);
+ return error;
+}
+
/*
* Punch a hole and prealloc the range. We use a hole punch rather than
* unwritten extent conversion for two reasons:
@@ -1406,7 +1459,7 @@ xfs_falloc_zero_range(
len = round_up(offset + len, blksize) -
round_down(offset, blksize);
offset = round_down(offset, blksize);
- error = xfs_alloc_file_space(ip, offset, len);
+ error = xfs_falloc_allocate_space(ip, offset, len);
}
if (error)
return error;
@@ -1432,7 +1485,7 @@ xfs_falloc_unshare_range(
if (error)
return error;
- error = xfs_alloc_file_space(XFS_I(inode), offset, len);
+ error = xfs_falloc_allocate_space(XFS_I(inode), offset, len);
if (error)
return error;
return xfs_falloc_setsize(file, new_size);
@@ -1460,7 +1513,7 @@ xfs_falloc_allocate_range(
if (error)
return error;
- error = xfs_alloc_file_space(XFS_I(inode), offset, len);
+ error = xfs_falloc_allocate_space(XFS_I(inode), offset, len);
if (error)
return error;
return xfs_falloc_setsize(file, new_size);
--
2.43.0
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2026-08-03 8:57 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-03 8:56 [PATCH v2] xfs: avoid false ENOSPC for fallocate ranges with real extents Huiwen He
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox