|
@@ -5403,6 +5403,120 @@ error0:
|
|
|
return error;
|
|
|
}
|
|
|
|
|
|
+/*
|
|
|
+ * Determine whether an extent shift can be accomplished by a merge with the
|
|
|
+ * extent that precedes the target hole of the shift.
|
|
|
+ */
|
|
|
+STATIC bool
|
|
|
+xfs_bmse_can_merge(
|
|
|
+ struct xfs_bmbt_irec *left, /* preceding extent */
|
|
|
+ struct xfs_bmbt_irec *got, /* current extent to shift */
|
|
|
+ xfs_fileoff_t shift) /* shift fsb */
|
|
|
+{
|
|
|
+ xfs_fileoff_t startoff;
|
|
|
+
|
|
|
+ startoff = got->br_startoff - shift;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * The extent, once shifted, must be adjacent in-file and on-disk with
|
|
|
+ * the preceding extent.
|
|
|
+ */
|
|
|
+ if ((left->br_startoff + left->br_blockcount != startoff) ||
|
|
|
+ (left->br_startblock + left->br_blockcount != got->br_startblock) ||
|
|
|
+ (left->br_state != got->br_state) ||
|
|
|
+ (left->br_blockcount + got->br_blockcount > MAXEXTLEN))
|
|
|
+ return false;
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * A bmap extent shift adjusts the file offset of an extent to fill a preceding
|
|
|
+ * hole in the file. If an extent shift would result in the extent being fully
|
|
|
+ * adjacent to the extent that currently precedes the hole, we can merge with
|
|
|
+ * the preceding extent rather than do the shift.
|
|
|
+ *
|
|
|
+ * This function assumes the caller has verified a shift-by-merge is possible
|
|
|
+ * with the provided extents via xfs_bmse_can_merge().
|
|
|
+ */
|
|
|
+STATIC int
|
|
|
+xfs_bmse_merge(
|
|
|
+ struct xfs_inode *ip,
|
|
|
+ int whichfork,
|
|
|
+ xfs_fileoff_t shift, /* shift fsb */
|
|
|
+ int current_ext, /* idx of gotp */
|
|
|
+ struct xfs_bmbt_rec_host *gotp, /* extent to shift */
|
|
|
+ struct xfs_bmbt_rec_host *leftp, /* preceding extent */
|
|
|
+ struct xfs_btree_cur *cur,
|
|
|
+ int *logflags) /* output */
|
|
|
+{
|
|
|
+ struct xfs_ifork *ifp;
|
|
|
+ struct xfs_bmbt_irec got;
|
|
|
+ struct xfs_bmbt_irec left;
|
|
|
+ xfs_filblks_t blockcount;
|
|
|
+ int error, i;
|
|
|
+
|
|
|
+ ifp = XFS_IFORK_PTR(ip, whichfork);
|
|
|
+ xfs_bmbt_get_all(gotp, &got);
|
|
|
+ xfs_bmbt_get_all(leftp, &left);
|
|
|
+ blockcount = left.br_blockcount + got.br_blockcount;
|
|
|
+
|
|
|
+ ASSERT(xfs_isilocked(ip, XFS_IOLOCK_EXCL));
|
|
|
+ ASSERT(xfs_isilocked(ip, XFS_ILOCK_EXCL));
|
|
|
+ ASSERT(xfs_bmse_can_merge(&left, &got, shift));
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Merge the in-core extents. Note that the host record pointers and
|
|
|
+ * current_ext index are invalid once the extent has been removed via
|
|
|
+ * xfs_iext_remove().
|
|
|
+ */
|
|
|
+ xfs_bmbt_set_blockcount(leftp, blockcount);
|
|
|
+ xfs_iext_remove(ip, current_ext, 1, 0);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Update the on-disk extent count, the btree if necessary and log the
|
|
|
+ * inode.
|
|
|
+ */
|
|
|
+ XFS_IFORK_NEXT_SET(ip, whichfork,
|
|
|
+ XFS_IFORK_NEXTENTS(ip, whichfork) - 1);
|
|
|
+ *logflags |= XFS_ILOG_CORE;
|
|
|
+ if (!cur) {
|
|
|
+ *logflags |= XFS_ILOG_DEXT;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* lookup and remove the extent to merge */
|
|
|
+ error = xfs_bmbt_lookup_eq(cur, got.br_startoff, got.br_startblock,
|
|
|
+ got.br_blockcount, &i);
|
|
|
+ if (error)
|
|
|
+ goto out_error;
|
|
|
+ XFS_WANT_CORRUPTED_GOTO(i == 1, out_error);
|
|
|
+
|
|
|
+ error = xfs_btree_delete(cur, &i);
|
|
|
+ if (error)
|
|
|
+ goto out_error;
|
|
|
+ XFS_WANT_CORRUPTED_GOTO(i == 1, out_error);
|
|
|
+
|
|
|
+ /* lookup and update size of the previous extent */
|
|
|
+ error = xfs_bmbt_lookup_eq(cur, left.br_startoff, left.br_startblock,
|
|
|
+ left.br_blockcount, &i);
|
|
|
+ if (error)
|
|
|
+ goto out_error;
|
|
|
+ XFS_WANT_CORRUPTED_GOTO(i == 1, out_error);
|
|
|
+
|
|
|
+ left.br_blockcount = blockcount;
|
|
|
+
|
|
|
+ error = xfs_bmbt_update(cur, left.br_startoff, left.br_startblock,
|
|
|
+ left.br_blockcount, left.br_state);
|
|
|
+ if (error)
|
|
|
+ goto out_error;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+
|
|
|
+out_error:
|
|
|
+ return error;
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
* Shift extent records to the left to cover a hole.
|
|
|
*
|
|
@@ -5427,6 +5541,7 @@ xfs_bmap_shift_extents(
|
|
|
{
|
|
|
struct xfs_btree_cur *cur = NULL;
|
|
|
struct xfs_bmbt_rec_host *gotp;
|
|
|
+ struct xfs_bmbt_rec_host *leftp;
|
|
|
struct xfs_bmbt_irec got;
|
|
|
struct xfs_bmbt_irec left;
|
|
|
struct xfs_mount *mp = ip->i_mount;
|
|
@@ -5438,7 +5553,6 @@ xfs_bmap_shift_extents(
|
|
|
int i;
|
|
|
int whichfork = XFS_DATA_FORK;
|
|
|
int logflags = 0;
|
|
|
- xfs_filblks_t blockcount = 0;
|
|
|
int total_extents;
|
|
|
|
|
|
if (unlikely(XFS_TEST_ERROR(
|
|
@@ -5464,6 +5578,13 @@ xfs_bmap_shift_extents(
|
|
|
return error;
|
|
|
}
|
|
|
|
|
|
+ if (ifp->if_flags & XFS_IFBROOT) {
|
|
|
+ cur = xfs_bmbt_init_cursor(mp, tp, ip, whichfork);
|
|
|
+ cur->bc_private.b.firstblock = *firstblock;
|
|
|
+ cur->bc_private.b.flist = flist;
|
|
|
+ cur->bc_private.b.flags = 0;
|
|
|
+ }
|
|
|
+
|
|
|
/*
|
|
|
* Look up the extent index for the fsb where we start shifting. We can
|
|
|
* henceforth iterate with current_ext as extent list changes are locked
|
|
@@ -5476,14 +5597,17 @@ xfs_bmap_shift_extents(
|
|
|
gotp = xfs_iext_bno_to_ext(ifp, start_fsb, ¤t_ext);
|
|
|
if (!gotp) {
|
|
|
*done = 1;
|
|
|
- return 0;
|
|
|
+ goto del_cursor;
|
|
|
}
|
|
|
+ xfs_bmbt_get_all(gotp, &got);
|
|
|
|
|
|
- if (ifp->if_flags & XFS_IFBROOT) {
|
|
|
- cur = xfs_bmbt_init_cursor(mp, tp, ip, whichfork);
|
|
|
- cur->bc_private.b.firstblock = *firstblock;
|
|
|
- cur->bc_private.b.flist = flist;
|
|
|
- cur->bc_private.b.flags = 0;
|
|
|
+ /*
|
|
|
+ * If the first extent is shifted, offset_shift_fsb cannot be larger
|
|
|
+ * than the starting offset of the first extent.
|
|
|
+ */
|
|
|
+ if (current_ext == 0 && got.br_startoff < offset_shift_fsb) {
|
|
|
+ error = -EINVAL;
|
|
|
+ goto del_cursor;
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -5493,30 +5617,41 @@ xfs_bmap_shift_extents(
|
|
|
*/
|
|
|
total_extents = ifp->if_bytes / sizeof(xfs_bmbt_rec_t);
|
|
|
while (nexts++ < num_exts && current_ext < total_extents) {
|
|
|
-
|
|
|
- gotp = xfs_iext_get_ext(ifp, current_ext);
|
|
|
- xfs_bmbt_get_all(gotp, &got);
|
|
|
startoff = got.br_startoff - offset_shift_fsb;
|
|
|
|
|
|
- /*
|
|
|
- * Before shifting extent into hole, make sure that the hole is
|
|
|
- * large enough to accommodate the shift.
|
|
|
- */
|
|
|
+ /* grab the left extent and check for a potential merge */
|
|
|
if (current_ext > 0) {
|
|
|
- xfs_bmbt_get_all(xfs_iext_get_ext(ifp, current_ext - 1),
|
|
|
- &left);
|
|
|
- if (startoff < left.br_startoff + left.br_blockcount)
|
|
|
+ leftp = xfs_iext_get_ext(ifp, current_ext - 1);
|
|
|
+ xfs_bmbt_get_all(leftp, &left);
|
|
|
+
|
|
|
+ /* make sure hole is large enough for shift */
|
|
|
+ if (startoff < left.br_startoff + left.br_blockcount) {
|
|
|
error = -EINVAL;
|
|
|
- } else if (offset_shift_fsb > got.br_startoff) {
|
|
|
- /*
|
|
|
- * When first extent is shifted, offset_shift_fsb should
|
|
|
- * be less than the stating offset of the first extent.
|
|
|
- */
|
|
|
- error = -EINVAL;
|
|
|
+ goto del_cursor;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (xfs_bmse_can_merge(&left, &got, offset_shift_fsb)) {
|
|
|
+ error = xfs_bmse_merge(ip, whichfork,
|
|
|
+ offset_shift_fsb, current_ext, gotp,
|
|
|
+ leftp, cur, &logflags);
|
|
|
+ if (error)
|
|
|
+ goto del_cursor;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * The extent was merged so adjust the extent
|
|
|
+ * index and move onto the next.
|
|
|
+ */
|
|
|
+ current_ext--;
|
|
|
+ goto next;
|
|
|
+ }
|
|
|
}
|
|
|
- if (error)
|
|
|
- goto del_cursor;
|
|
|
|
|
|
+ /*
|
|
|
+ * We didn't merge the extent so do the shift. Update the start
|
|
|
+ * offset in the in-core extent and btree, if necessary.
|
|
|
+ */
|
|
|
+ xfs_bmbt_set_startoff(gotp, startoff);
|
|
|
+ logflags |= XFS_ILOG_CORE;
|
|
|
if (cur) {
|
|
|
error = xfs_bmbt_lookup_eq(cur, got.br_startoff,
|
|
|
got.br_startblock,
|
|
@@ -5525,53 +5660,8 @@ xfs_bmap_shift_extents(
|
|
|
if (error)
|
|
|
goto del_cursor;
|
|
|
XFS_WANT_CORRUPTED_GOTO(i == 1, del_cursor);
|
|
|
- }
|
|
|
-
|
|
|
- /* Check if we can merge 2 adjacent extents */
|
|
|
- if (current_ext &&
|
|
|
- left.br_startoff + left.br_blockcount == startoff &&
|
|
|
- left.br_startblock + left.br_blockcount ==
|
|
|
- got.br_startblock &&
|
|
|
- left.br_state == got.br_state &&
|
|
|
- left.br_blockcount + got.br_blockcount <= MAXEXTLEN) {
|
|
|
- blockcount = left.br_blockcount +
|
|
|
- got.br_blockcount;
|
|
|
- xfs_iext_remove(ip, current_ext, 1, 0);
|
|
|
- logflags |= XFS_ILOG_CORE;
|
|
|
- if (cur) {
|
|
|
- error = xfs_btree_delete(cur, &i);
|
|
|
- if (error)
|
|
|
- goto del_cursor;
|
|
|
- XFS_WANT_CORRUPTED_GOTO(i == 1, del_cursor);
|
|
|
- } else {
|
|
|
- logflags |= XFS_ILOG_DEXT;
|
|
|
- }
|
|
|
- XFS_IFORK_NEXT_SET(ip, whichfork,
|
|
|
- XFS_IFORK_NEXTENTS(ip, whichfork) - 1);
|
|
|
- gotp = xfs_iext_get_ext(ifp, --current_ext);
|
|
|
- xfs_bmbt_get_all(gotp, &got);
|
|
|
-
|
|
|
- /* Make cursor point to the extent we will update */
|
|
|
- if (cur) {
|
|
|
- error = xfs_bmbt_lookup_eq(cur, got.br_startoff,
|
|
|
- got.br_startblock,
|
|
|
- got.br_blockcount,
|
|
|
- &i);
|
|
|
- if (error)
|
|
|
- goto del_cursor;
|
|
|
- XFS_WANT_CORRUPTED_GOTO(i == 1, del_cursor);
|
|
|
- }
|
|
|
|
|
|
- xfs_bmbt_set_blockcount(gotp, blockcount);
|
|
|
- got.br_blockcount = blockcount;
|
|
|
- } else {
|
|
|
- /* We have to update the startoff */
|
|
|
- xfs_bmbt_set_startoff(gotp, startoff);
|
|
|
got.br_startoff = startoff;
|
|
|
- }
|
|
|
-
|
|
|
- logflags |= XFS_ILOG_CORE;
|
|
|
- if (cur) {
|
|
|
error = xfs_bmbt_update(cur, got.br_startoff,
|
|
|
got.br_startblock,
|
|
|
got.br_blockcount,
|
|
@@ -5582,18 +5672,20 @@ xfs_bmap_shift_extents(
|
|
|
logflags |= XFS_ILOG_DEXT;
|
|
|
}
|
|
|
|
|
|
- current_ext++;
|
|
|
+next:
|
|
|
+ /* update total extent count and grab the next record */
|
|
|
total_extents = ifp->if_bytes / sizeof(xfs_bmbt_rec_t);
|
|
|
+ if (++current_ext >= total_extents)
|
|
|
+ break;
|
|
|
+ gotp = xfs_iext_get_ext(ifp, current_ext);
|
|
|
+ xfs_bmbt_get_all(gotp, &got);
|
|
|
}
|
|
|
|
|
|
/* Check if we are done */
|
|
|
if (current_ext == total_extents)
|
|
|
*done = 1;
|
|
|
- else if (next_fsb) {
|
|
|
- gotp = xfs_iext_get_ext(ifp, current_ext);
|
|
|
- xfs_bmbt_get_all(gotp, &got);
|
|
|
+ else if (next_fsb)
|
|
|
*next_fsb = got.br_startoff;
|
|
|
- }
|
|
|
|
|
|
del_cursor:
|
|
|
if (cur)
|