[PATCH 1/4] libxfs: resync libxfs_alloc_file_space interface with the kernel

Darrick J. Wong
2024-12-06T23:50:35Z

From: Darrick J. Wong <djwong@kernel.org>

Make the userspace xfs_alloc_file_space behave (more or less) like the
kernel version, at least as far as the interface goes.

Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
---
include/libxfs.h | 4 +-
libxfs/util.c | 140 ++++++++++++++++++++++++++++++++++++------------------
mkfs/proto.c | 2 -
3 files changed, 96 insertions(+), 50 deletions(-)

diff --git a/include/libxfs.h b/include/libxfs.h
index 348d36be192966..878fefbbde7524 100644
--- a/include/libxfs.h
+++ b/include/libxfs.h
@@ -176,8 +176,8 @@ extern int libxfs_log_header(char *, uuid_t *, int, int, int, xfs_lsn_t,

/* Shared utility routines */

-extern int libxfs_alloc_file_space (struct xfs_inode *, xfs_off_t,
- xfs_off_t, int, int);
+int libxfs_alloc_file_space(struct xfs_inode *ip, xfs_off_t offset,
+ xfs_off_t len, uint32_t bmapi_flags);

/* XXX: this is messy and needs fixing */
#ifndef __LIBXFS_INTERNAL_XFS_H__
diff --git a/libxfs/util.c b/libxfs/util.c
index 97da94506aee01..e5892fc86c3e92 100644
--- a/libxfs/util.c
+++ b/libxfs/util.c
@@ -179,78 +179,124 @@ libxfs_mod_incore_sb(
*/
int
libxfs_alloc_file_space(
- xfs_inode_t *ip,
- xfs_off_t offset,
- xfs_off_t len,
- int alloc_type,
- int attr_flags)
+ struct xfs_inode *ip,
+ xfs_off_t offset,
+ xfs_off_t len,
+ uint32_t bmapi_flags)
{
- xfs_mount_t *mp;
- xfs_off_t count;
- xfs_filblks_t datablocks;
- xfs_filblks_t allocated_fsb;
- xfs_filblks_t allocatesize_fsb;
- xfs_bmbt_irec_t *imapp;
- xfs_bmbt_irec_t imaps[1];
- int reccount;
- uint resblks;
- xfs_fileoff_t startoffset_fsb;
- xfs_trans_t *tp;
- int xfs_bmapi_flags;
- int error;
+ xfs_mount_t *mp = ip->i_mount;
+ xfs_off_t count;
+ xfs_filblks_t allocatesize_fsb;
+ xfs_extlen_t extsz, temp;
+ xfs_fileoff_t startoffset_fsb;
+ xfs_fileoff_t endoffset_fsb;
+ int rt;
+ xfs_trans_t *tp;
+ xfs_bmbt_irec_t imaps[1], *imapp;
+ int error;

if (len <= 0)
return -EINVAL;

+ rt = XFS_IS_REALTIME_INODE(ip);
+ extsz = xfs_get_extsz_hint(ip);
+
count = len;
- error = 0;
imapp = &imaps[0];
- reccount = 1;
- xfs_bmapi_flags = alloc_type ? XFS_BMAPI_PREALLOC : 0;
- mp = ip->i_mount;
- startoffset_fsb = XFS_B_TO_FSBT(mp, offset);
- allocatesize_fsb = XFS_B_TO_FSB(mp, count);
+ startoffset_fsb = XFS_B_TO_FSBT(mp, offset);
+ endoffset_fsb = XFS_B_TO_FSB(mp, offset + count);
+ allocatesize_fsb = endoffset_fsb - startoffset_fsb;

- /* allocate file space until done or until there is an error */
+ /*
+ * Allocate file space until done or until there is an error
+ */
while (allocatesize_fsb && !error) {
- datablocks = allocatesize_fsb;
+ xfs_fileoff_t s, e;
+ unsigned int dblocks, rblocks, resblks;
+ int nimaps = 1;

- resblks = (uint)XFS_DIOSTRAT_SPACE_RES(mp, datablocks);
- error = xfs_trans_alloc(mp, &M_RES(mp)->tr_write, resblks,
- 0, 0, &tp);
/*
- * Check for running out of space
+ * Determine space reservations for data/realtime.
*/
- if (error) {
- ASSERT(error == -ENOSPC);
+ if (unlikely(extsz)) {
+ s = startoffset_fsb;
+ do_div(s, extsz);
+ s *= extsz;
+ e = startoffset_fsb + allocatesize_fsb;
+ div_u64_rem(startoffset_fsb, extsz, &temp);
+ if (temp)
+ e += temp;
+ div_u64_rem(e, extsz, &temp);
+ if (temp)
+ e += extsz - temp;
+ } else {
+ s = 0;
+ e = allocatesize_fsb;
+ }
+
+ /*
+ * The transaction reservation is limited to a 32-bit block
+ * count, hence we need to limit the number of blocks we are
+ * trying to reserve to avoid an overflow. We can't allocate
+ * more than @nimaps extents, and an extent is limited on disk
+ * to XFS_BMBT_MAX_EXTLEN (21 bits), so use that to enforce the
+ * limit.
+ */
+ resblks = min_t(xfs_fileoff_t, (e - s),
+ (XFS_MAX_BMBT_EXTLEN * nimaps));
+ if (unlikely(rt)) {
+ dblocks = XFS_DIOSTRAT_SPACE_RES(mp, 0);
+ rblocks = resblks;
+ } else {
+ dblocks = XFS_DIOSTRAT_SPACE_RES(mp, resblks);
+ rblocks = 0;
+ }
+
+ error = xfs_trans_alloc_inode(ip, &M_RES(mp)->tr_write,
+ dblocks, rblocks, false, &tp);
+ if (error)
break;
- }
- xfs_trans_ijoin(tp, ip, 0);

- error = xfs_bmapi_write(tp, ip, startoffset_fsb, allocatesize_fsb,
- xfs_bmapi_flags, 0, imapp, &reccount);
+ error = xfs_iext_count_extend(tp, ip, XFS_DATA_FORK,
+ XFS_IEXT_ADD_NOSPLIT_CNT);
+ if (error)
+ goto error;

+ error = xfs_bmapi_write(tp, ip, startoffset_fsb,
+ allocatesize_fsb, bmapi_flags, 0, imapp,
+ &nimaps);
if (error)
- goto error0;
+ goto error;
+
+ ip->i_diflags |= XFS_DIFLAG_PREALLOC;
+ xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);

- /*
- * Complete the transaction
- */
error = xfs_trans_commit(tp);
+ xfs_iunlock(ip, XFS_ILOCK_EXCL);
if (error)
break;

- allocated_fsb = imapp->br_blockcount;
- if (reccount == 0)
- return -ENOSPC;
-
- startoffset_fsb += allocated_fsb;
- allocatesize_fsb -= allocated_fsb;
+ /*
+ * If xfs_bmapi_write finds a delalloc extent at the requested
+ * range, it tries to convert the entire delalloc extent to a
+ * real allocation.
+ * If the allocator cannot find a single free extent large
+ * enough to cover the start block of the requested range,
+ * xfs_bmapi_write will return 0 but leave *nimaps set to 0.
+ * In that case we simply need to keep looping with the same
+ * startoffset_fsb.
+ */
+ if (nimaps) {
+ startoffset_fsb += imapp->br_blockcount;
+ allocatesize_fsb -= imapp->br_blockcount;
+ }
}
+
return error;

-error0: /* Cancel bmap, cancel trans */
+error:
xfs_trans_cancel(tp);
+ xfs_iunlock(ip, XFS_ILOCK_EXCL);
return error;
}

diff --git a/mkfs/proto.c b/mkfs/proto.c
index 05c2621f8a0b13..0764064e043e97 100644
--- a/mkfs/proto.c
+++ b/mkfs/proto.c
@@ -203,7 +203,7 @@ rsvfile(
int error;
xfs_trans_t *tp;

- error = -libxfs_alloc_file_space(ip, 0, llen, 1, 0);
+ error = -libxfs_alloc_file_space(ip, 0, llen, XFS_BMAPI_PREALLOC);

if (error) {
fail(_("error reserving space for a file"), error);