summaryrefslogtreecommitdiff
path: root/fs/xfs/xfs_iomap.c
diff options
context:
space:
mode:
Diffstat (limited to 'fs/xfs/xfs_iomap.c')
-rw-r--r--fs/xfs/xfs_iomap.c170
1 files changed, 57 insertions, 113 deletions
diff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c
index ab69caa685b4..6af1d3ec0a9c 100644
--- a/fs/xfs/xfs_iomap.c
+++ b/fs/xfs/xfs_iomap.c
@@ -677,25 +677,19 @@ out_unlock:
*/
int
xfs_iomap_write_allocate(
- xfs_inode_t *ip,
- int whichfork,
- xfs_off_t offset,
- xfs_bmbt_irec_t *imap,
- unsigned int *seq)
+ struct xfs_inode *ip,
+ int whichfork,
+ xfs_off_t offset,
+ struct xfs_bmbt_irec *imap,
+ unsigned int *seq)
{
- xfs_mount_t *mp = ip->i_mount;
- struct xfs_ifork *ifp = XFS_IFORK_PTR(ip, whichfork);
- xfs_fileoff_t offset_fsb, last_block;
- xfs_fileoff_t end_fsb, map_start_fsb;
- xfs_filblks_t count_fsb;
- xfs_trans_t *tp;
- int nimaps;
- int error = 0;
- int flags = XFS_BMAPI_DELALLOC;
- int nres;
-
- if (whichfork == XFS_COW_FORK)
- flags |= XFS_BMAPI_COWFORK | XFS_BMAPI_PREALLOC;
+ struct xfs_mount *mp = ip->i_mount;
+ struct xfs_ifork *ifp = XFS_IFORK_PTR(ip, whichfork);
+ xfs_fileoff_t offset_fsb;
+ xfs_fileoff_t map_start_fsb;
+ xfs_extlen_t map_count_fsb;
+ struct xfs_trans *tp;
+ int error = 0;
/*
* Make sure that the dquots are there.
@@ -704,106 +698,60 @@ xfs_iomap_write_allocate(
if (error)
return error;
+ /*
+ * Store the file range the caller is interested in because it encodes
+ * state such as potential overlap with COW fork blocks. We must trim
+ * the allocated extent down to this range to maintain consistency with
+ * what the caller expects. Revalidation of the range itself is the
+ * responsibility of the caller.
+ */
offset_fsb = XFS_B_TO_FSBT(mp, offset);
- count_fsb = imap->br_blockcount;
map_start_fsb = imap->br_startoff;
+ map_count_fsb = imap->br_blockcount;
- XFS_STATS_ADD(mp, xs_xstrat_bytes, XFS_FSB_TO_B(mp, count_fsb));
+ XFS_STATS_ADD(mp, xs_xstrat_bytes,
+ XFS_FSB_TO_B(mp, imap->br_blockcount));
- while (count_fsb != 0) {
+ while (true) {
/*
- * Set up a transaction with which to allocate the
- * backing store for the file. Do allocations in a
- * loop until we get some space in the range we are
- * interested in. The other space that might be allocated
- * is in the delayed allocation extent on which we sit
- * but before our buffer starts.
+ * Allocate in a loop because it may take several attempts to
+ * allocate real blocks for a contiguous delalloc extent if free
+ * space is sufficiently fragmented. Note that space for the
+ * extent and indirect blocks was reserved when the delalloc
+ * extent was created so there's no need to do so here.
*/
- nimaps = 0;
- while (nimaps == 0) {
- nres = XFS_EXTENTADD_SPACE_RES(mp, XFS_DATA_FORK);
- /*
- * We have already reserved space for the extent and any
- * indirect blocks when creating the delalloc extent,
- * there is no need to reserve space in this transaction
- * again.
- */
- error = xfs_trans_alloc(mp, &M_RES(mp)->tr_write, 0,
- 0, XFS_TRANS_RESERVE, &tp);
- if (error)
- return error;
-
- xfs_ilock(ip, XFS_ILOCK_EXCL);
- xfs_trans_ijoin(tp, ip, 0);
-
- /*
- * it is possible that the extents have changed since
- * we did the read call as we dropped the ilock for a
- * while. We have to be careful about truncates or hole
- * punchs here - we are not allowed to allocate
- * non-delalloc blocks here.
- *
- * The only protection against truncation is the pages
- * for the range we are being asked to convert are
- * locked and hence a truncate will block on them
- * first.
- *
- * As a result, if we go beyond the range we really
- * need and hit an delalloc extent boundary followed by
- * a hole while we have excess blocks in the map, we
- * will fill the hole incorrectly and overrun the
- * transaction reservation.
- *
- * Using a single map prevents this as we are forced to
- * check each map we look for overlap with the desired
- * range and abort as soon as we find it. Also, given
- * that we only return a single map, having one beyond
- * what we can return is probably a bit silly.
- *
- * We also need to check that we don't go beyond EOF;
- * this is a truncate optimisation as a truncate sets
- * the new file size before block on the pages we
- * currently have locked under writeback. Because they
- * are about to be tossed, we don't need to write them
- * back....
- */
- nimaps = 1;
- end_fsb = XFS_B_TO_FSB(mp, XFS_ISIZE(ip));
- error = xfs_bmap_last_offset(ip, &last_block,
- XFS_DATA_FORK);
- if (error)
- goto trans_cancel;
+ error = xfs_trans_alloc(mp, &M_RES(mp)->tr_write, 0, 0,
+ XFS_TRANS_RESERVE, &tp);
+ if (error)
+ return error;
- last_block = XFS_FILEOFF_MAX(last_block, end_fsb);
- if ((map_start_fsb + count_fsb) > last_block) {
- count_fsb = last_block - map_start_fsb;
- if (count_fsb == 0) {
- error = -EAGAIN;
- goto trans_cancel;
- }
- }
+ xfs_ilock(ip, XFS_ILOCK_EXCL);
+ xfs_trans_ijoin(tp, ip, 0);
- /*
- * From this point onwards we overwrite the imap
- * pointer that the caller gave to us.
- */
- error = xfs_bmapi_write(tp, ip, map_start_fsb,
- count_fsb, flags, nres, imap,
- &nimaps);
- if (error)
- goto trans_cancel;
+ /*
+ * ilock was dropped since imap was populated which means it
+ * might no longer be valid. The current page is held locked so
+ * nothing could have removed the block backing offset_fsb.
+ * Attempt to allocate whatever delalloc extent currently backs
+ * offset_fsb and put the result in the imap pointer from the
+ * caller. We'll trim it down to the caller's most recently
+ * validated range before we return.
+ */
+ error = xfs_bmapi_convert_delalloc(tp, ip, offset_fsb,
+ whichfork, imap);
+ if (error)
+ goto trans_cancel;
- error = xfs_trans_commit(tp);
- if (error)
- goto error0;
+ error = xfs_trans_commit(tp);
+ if (error)
+ goto error0;
- *seq = READ_ONCE(ifp->if_seq);
- xfs_iunlock(ip, XFS_ILOCK_EXCL);
- }
+ *seq = READ_ONCE(ifp->if_seq);
+ xfs_iunlock(ip, XFS_ILOCK_EXCL);
/*
- * See if we were able to allocate an extent that
- * covers at least part of the callers request
+ * See if we were able to allocate an extent that covers at
+ * least part of the callers request.
*/
if (!(imap->br_startblock || XFS_IS_REALTIME_INODE(ip)))
return xfs_alert_fsblock_zero(ip, imap);
@@ -812,15 +760,11 @@ xfs_iomap_write_allocate(
(offset_fsb < (imap->br_startoff +
imap->br_blockcount))) {
XFS_STATS_INC(mp, xs_xstrat_quick);
+ xfs_trim_extent(imap, map_start_fsb, map_count_fsb);
+ ASSERT(offset_fsb >= imap->br_startoff &&
+ offset_fsb < imap->br_startoff + imap->br_blockcount);
return 0;
}
-
- /*
- * So far we have not mapped the requested part of the
- * file, just surrounding data, try again.
- */
- count_fsb -= imap->br_blockcount;
- map_start_fsb = imap->br_startoff + imap->br_blockcount;
}
trans_cancel: