Merge branch 'fallocate-insert-range' into for-next
This commit is contained in:
@@ -5486,52 +5486,92 @@ xfs_bmse_shift_one(
|
||||
int *current_ext,
|
||||
struct xfs_bmbt_rec_host *gotp,
|
||||
struct xfs_btree_cur *cur,
|
||||
int *logflags)
|
||||
int *logflags,
|
||||
enum shift_direction direction)
|
||||
{
|
||||
struct xfs_ifork *ifp;
|
||||
struct xfs_mount *mp;
|
||||
xfs_fileoff_t startoff;
|
||||
struct xfs_bmbt_rec_host *leftp;
|
||||
struct xfs_bmbt_rec_host *adj_irecp;
|
||||
struct xfs_bmbt_irec got;
|
||||
struct xfs_bmbt_irec left;
|
||||
struct xfs_bmbt_irec adj_irec;
|
||||
int error;
|
||||
int i;
|
||||
int total_extents;
|
||||
|
||||
mp = ip->i_mount;
|
||||
ifp = XFS_IFORK_PTR(ip, whichfork);
|
||||
total_extents = ifp->if_bytes / sizeof(xfs_bmbt_rec_t);
|
||||
|
||||
xfs_bmbt_get_all(gotp, &got);
|
||||
startoff = got.br_startoff - offset_shift_fsb;
|
||||
|
||||
/* delalloc extents should be prevented by caller */
|
||||
XFS_WANT_CORRUPTED_RETURN(mp, !isnullstartblock(got.br_startblock));
|
||||
|
||||
/*
|
||||
* Check for merge if we've got an extent to the left, otherwise make
|
||||
* sure there's enough room at the start of the file for the shift.
|
||||
*/
|
||||
if (*current_ext) {
|
||||
/* grab the left extent and check for a large enough hole */
|
||||
leftp = xfs_iext_get_ext(ifp, *current_ext - 1);
|
||||
xfs_bmbt_get_all(leftp, &left);
|
||||
if (direction == SHIFT_LEFT) {
|
||||
startoff = got.br_startoff - offset_shift_fsb;
|
||||
|
||||
if (startoff < left.br_startoff + left.br_blockcount)
|
||||
/*
|
||||
* Check for merge if we've got an extent to the left,
|
||||
* otherwise make sure there's enough room at the start
|
||||
* of the file for the shift.
|
||||
*/
|
||||
if (!*current_ext) {
|
||||
if (got.br_startoff < offset_shift_fsb)
|
||||
return -EINVAL;
|
||||
goto update_current_ext;
|
||||
}
|
||||
/*
|
||||
* grab the left extent and check for a large
|
||||
* enough hole.
|
||||
*/
|
||||
adj_irecp = xfs_iext_get_ext(ifp, *current_ext - 1);
|
||||
xfs_bmbt_get_all(adj_irecp, &adj_irec);
|
||||
|
||||
if (startoff <
|
||||
adj_irec.br_startoff + adj_irec.br_blockcount)
|
||||
return -EINVAL;
|
||||
|
||||
/* check whether to merge the extent or shift it down */
|
||||
if (xfs_bmse_can_merge(&left, &got, offset_shift_fsb)) {
|
||||
if (xfs_bmse_can_merge(&adj_irec, &got,
|
||||
offset_shift_fsb)) {
|
||||
return xfs_bmse_merge(ip, whichfork, offset_shift_fsb,
|
||||
*current_ext, gotp, leftp, cur,
|
||||
logflags);
|
||||
*current_ext, gotp, adj_irecp,
|
||||
cur, logflags);
|
||||
}
|
||||
} else if (got.br_startoff < offset_shift_fsb)
|
||||
return -EINVAL;
|
||||
|
||||
} else {
|
||||
startoff = got.br_startoff + offset_shift_fsb;
|
||||
/* nothing to move if this is the last extent */
|
||||
if (*current_ext >= (total_extents - 1))
|
||||
goto update_current_ext;
|
||||
/*
|
||||
* If this is not the last extent in the file, make sure there
|
||||
* is enough room between current extent and next extent for
|
||||
* accommodating the shift.
|
||||
*/
|
||||
adj_irecp = xfs_iext_get_ext(ifp, *current_ext + 1);
|
||||
xfs_bmbt_get_all(adj_irecp, &adj_irec);
|
||||
if (startoff + got.br_blockcount > adj_irec.br_startoff)
|
||||
return -EINVAL;
|
||||
/*
|
||||
* Unlike a left shift (which involves a hole punch),
|
||||
* a right shift does not modify extent neighbors
|
||||
* in any way. We should never find mergeable extents
|
||||
* in this scenario. Check anyways and warn if we
|
||||
* encounter two extents that could be one.
|
||||
*/
|
||||
if (xfs_bmse_can_merge(&got, &adj_irec, offset_shift_fsb))
|
||||
WARN_ON_ONCE(1);
|
||||
}
|
||||
/*
|
||||
* Increment the extent index for the next iteration, update the start
|
||||
* offset of the in-core extent and update the btree if applicable.
|
||||
*/
|
||||
(*current_ext)++;
|
||||
update_current_ext:
|
||||
if (direction == SHIFT_LEFT)
|
||||
(*current_ext)++;
|
||||
else
|
||||
(*current_ext)--;
|
||||
xfs_bmbt_set_startoff(gotp, startoff);
|
||||
*logflags |= XFS_ILOG_CORE;
|
||||
if (!cur) {
|
||||
@@ -5547,14 +5587,14 @@ xfs_bmse_shift_one(
|
||||
|
||||
got.br_startoff = startoff;
|
||||
return xfs_bmbt_update(cur, got.br_startoff, got.br_startblock,
|
||||
got.br_blockcount, got.br_state);
|
||||
got.br_blockcount, got.br_state);
|
||||
}
|
||||
|
||||
/*
|
||||
* Shift extent records to the left to cover a hole.
|
||||
* Shift extent records to the left/right to cover/create a hole.
|
||||
*
|
||||
* The maximum number of extents to be shifted in a single operation is
|
||||
* @num_exts. @start_fsb specifies the file offset to start the shift and the
|
||||
* @num_exts. @stop_fsb specifies the file offset at which to stop shift and the
|
||||
* file offset where we've left off is returned in @next_fsb. @offset_shift_fsb
|
||||
* is the length by which each extent is shifted. If there is no hole to shift
|
||||
* the extents into, this will be considered invalid operation and we abort
|
||||
@@ -5564,12 +5604,13 @@ int
|
||||
xfs_bmap_shift_extents(
|
||||
struct xfs_trans *tp,
|
||||
struct xfs_inode *ip,
|
||||
xfs_fileoff_t start_fsb,
|
||||
xfs_fileoff_t *next_fsb,
|
||||
xfs_fileoff_t offset_shift_fsb,
|
||||
int *done,
|
||||
xfs_fileoff_t *next_fsb,
|
||||
xfs_fileoff_t stop_fsb,
|
||||
xfs_fsblock_t *firstblock,
|
||||
struct xfs_bmap_free *flist,
|
||||
enum shift_direction direction,
|
||||
int num_exts)
|
||||
{
|
||||
struct xfs_btree_cur *cur = NULL;
|
||||
@@ -5579,10 +5620,11 @@ xfs_bmap_shift_extents(
|
||||
struct xfs_ifork *ifp;
|
||||
xfs_extnum_t nexts = 0;
|
||||
xfs_extnum_t current_ext;
|
||||
xfs_extnum_t total_extents;
|
||||
xfs_extnum_t stop_extent;
|
||||
int error = 0;
|
||||
int whichfork = XFS_DATA_FORK;
|
||||
int logflags = 0;
|
||||
int total_extents;
|
||||
|
||||
if (unlikely(XFS_TEST_ERROR(
|
||||
(XFS_IFORK_FORMAT(ip, whichfork) != XFS_DINODE_FMT_EXTENTS &&
|
||||
@@ -5598,6 +5640,8 @@ xfs_bmap_shift_extents(
|
||||
|
||||
ASSERT(xfs_isilocked(ip, XFS_IOLOCK_EXCL));
|
||||
ASSERT(xfs_isilocked(ip, XFS_ILOCK_EXCL));
|
||||
ASSERT(direction == SHIFT_LEFT || direction == SHIFT_RIGHT);
|
||||
ASSERT(*next_fsb != NULLFSBLOCK || direction == SHIFT_RIGHT);
|
||||
|
||||
ifp = XFS_IFORK_PTR(ip, whichfork);
|
||||
if (!(ifp->if_flags & XFS_IFEXTENTS)) {
|
||||
@@ -5614,44 +5658,84 @@ xfs_bmap_shift_extents(
|
||||
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
|
||||
* out via ilock.
|
||||
*
|
||||
* gotp can be null in 2 cases: 1) if there are no extents or 2)
|
||||
* start_fsb lies in a hole beyond which there are no extents. Either
|
||||
* way, we are done.
|
||||
*/
|
||||
gotp = xfs_iext_bno_to_ext(ifp, start_fsb, ¤t_ext);
|
||||
if (!gotp) {
|
||||
*done = 1;
|
||||
goto del_cursor;
|
||||
}
|
||||
|
||||
/*
|
||||
* There may be delalloc extents in the data fork before the range we
|
||||
* are collapsing out, so we cannot use the count of real extents here.
|
||||
* Instead we have to calculate it from the incore fork.
|
||||
*/
|
||||
total_extents = ifp->if_bytes / sizeof(xfs_bmbt_rec_t);
|
||||
while (nexts++ < num_exts && current_ext < total_extents) {
|
||||
if (total_extents == 0) {
|
||||
*done = 1;
|
||||
goto del_cursor;
|
||||
}
|
||||
|
||||
/*
|
||||
* In case of first right shift, we need to initialize next_fsb
|
||||
*/
|
||||
if (*next_fsb == NULLFSBLOCK) {
|
||||
gotp = xfs_iext_get_ext(ifp, total_extents - 1);
|
||||
xfs_bmbt_get_all(gotp, &got);
|
||||
*next_fsb = got.br_startoff;
|
||||
if (stop_fsb > *next_fsb) {
|
||||
*done = 1;
|
||||
goto del_cursor;
|
||||
}
|
||||
}
|
||||
|
||||
/* Lookup the extent index at which we have to stop */
|
||||
if (direction == SHIFT_RIGHT) {
|
||||
gotp = xfs_iext_bno_to_ext(ifp, stop_fsb, &stop_extent);
|
||||
/* Make stop_extent exclusive of shift range */
|
||||
stop_extent--;
|
||||
} else
|
||||
stop_extent = total_extents;
|
||||
|
||||
/*
|
||||
* 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
|
||||
* out via ilock.
|
||||
*
|
||||
* gotp can be null in 2 cases: 1) if there are no extents or 2)
|
||||
* *next_fsb lies in a hole beyond which there are no extents. Either
|
||||
* way, we are done.
|
||||
*/
|
||||
gotp = xfs_iext_bno_to_ext(ifp, *next_fsb, ¤t_ext);
|
||||
if (!gotp) {
|
||||
*done = 1;
|
||||
goto del_cursor;
|
||||
}
|
||||
|
||||
/* some sanity checking before we finally start shifting extents */
|
||||
if ((direction == SHIFT_LEFT && current_ext >= stop_extent) ||
|
||||
(direction == SHIFT_RIGHT && current_ext <= stop_extent)) {
|
||||
error = -EIO;
|
||||
goto del_cursor;
|
||||
}
|
||||
|
||||
while (nexts++ < num_exts) {
|
||||
error = xfs_bmse_shift_one(ip, whichfork, offset_shift_fsb,
|
||||
¤t_ext, gotp, cur, &logflags);
|
||||
¤t_ext, gotp, cur, &logflags,
|
||||
direction);
|
||||
if (error)
|
||||
goto del_cursor;
|
||||
/*
|
||||
* If there was an extent merge during the shift, the extent
|
||||
* count can change. Update the total and grade the next record.
|
||||
*/
|
||||
if (direction == SHIFT_LEFT) {
|
||||
total_extents = ifp->if_bytes / sizeof(xfs_bmbt_rec_t);
|
||||
stop_extent = total_extents;
|
||||
}
|
||||
|
||||
/* update total extent count and grab the next record */
|
||||
total_extents = ifp->if_bytes / sizeof(xfs_bmbt_rec_t);
|
||||
if (current_ext >= total_extents)
|
||||
if (current_ext == stop_extent) {
|
||||
*done = 1;
|
||||
*next_fsb = NULLFSBLOCK;
|
||||
break;
|
||||
}
|
||||
gotp = xfs_iext_get_ext(ifp, current_ext);
|
||||
}
|
||||
|
||||
/* Check if we are done */
|
||||
if (current_ext == total_extents) {
|
||||
*done = 1;
|
||||
} else if (next_fsb) {
|
||||
if (!*done) {
|
||||
xfs_bmbt_get_all(gotp, &got);
|
||||
*next_fsb = got.br_startoff;
|
||||
}
|
||||
@@ -5666,3 +5750,189 @@ del_cursor:
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
/*
|
||||
* Splits an extent into two extents at split_fsb block such that it is
|
||||
* the first block of the current_ext. @current_ext is a target extent
|
||||
* to be split. @split_fsb is a block where the extents is split.
|
||||
* If split_fsb lies in a hole or the first block of extents, just return 0.
|
||||
*/
|
||||
STATIC int
|
||||
xfs_bmap_split_extent_at(
|
||||
struct xfs_trans *tp,
|
||||
struct xfs_inode *ip,
|
||||
xfs_fileoff_t split_fsb,
|
||||
xfs_fsblock_t *firstfsb,
|
||||
struct xfs_bmap_free *free_list)
|
||||
{
|
||||
int whichfork = XFS_DATA_FORK;
|
||||
struct xfs_btree_cur *cur = NULL;
|
||||
struct xfs_bmbt_rec_host *gotp;
|
||||
struct xfs_bmbt_irec got;
|
||||
struct xfs_bmbt_irec new; /* split extent */
|
||||
struct xfs_mount *mp = ip->i_mount;
|
||||
struct xfs_ifork *ifp;
|
||||
xfs_fsblock_t gotblkcnt; /* new block count for got */
|
||||
xfs_extnum_t current_ext;
|
||||
int error = 0;
|
||||
int logflags = 0;
|
||||
int i = 0;
|
||||
|
||||
if (unlikely(XFS_TEST_ERROR(
|
||||
(XFS_IFORK_FORMAT(ip, whichfork) != XFS_DINODE_FMT_EXTENTS &&
|
||||
XFS_IFORK_FORMAT(ip, whichfork) != XFS_DINODE_FMT_BTREE),
|
||||
mp, XFS_ERRTAG_BMAPIFORMAT, XFS_RANDOM_BMAPIFORMAT))) {
|
||||
XFS_ERROR_REPORT("xfs_bmap_split_extent_at",
|
||||
XFS_ERRLEVEL_LOW, mp);
|
||||
return -EFSCORRUPTED;
|
||||
}
|
||||
|
||||
if (XFS_FORCED_SHUTDOWN(mp))
|
||||
return -EIO;
|
||||
|
||||
ifp = XFS_IFORK_PTR(ip, whichfork);
|
||||
if (!(ifp->if_flags & XFS_IFEXTENTS)) {
|
||||
/* Read in all the extents */
|
||||
error = xfs_iread_extents(tp, ip, whichfork);
|
||||
if (error)
|
||||
return error;
|
||||
}
|
||||
|
||||
/*
|
||||
* gotp can be null in 2 cases: 1) if there are no extents
|
||||
* or 2) split_fsb lies in a hole beyond which there are
|
||||
* no extents. Either way, we are done.
|
||||
*/
|
||||
gotp = xfs_iext_bno_to_ext(ifp, split_fsb, ¤t_ext);
|
||||
if (!gotp)
|
||||
return 0;
|
||||
|
||||
xfs_bmbt_get_all(gotp, &got);
|
||||
|
||||
/*
|
||||
* Check split_fsb lies in a hole or the start boundary offset
|
||||
* of the extent.
|
||||
*/
|
||||
if (got.br_startoff >= split_fsb)
|
||||
return 0;
|
||||
|
||||
gotblkcnt = split_fsb - got.br_startoff;
|
||||
new.br_startoff = split_fsb;
|
||||
new.br_startblock = got.br_startblock + gotblkcnt;
|
||||
new.br_blockcount = got.br_blockcount - gotblkcnt;
|
||||
new.br_state = got.br_state;
|
||||
|
||||
if (ifp->if_flags & XFS_IFBROOT) {
|
||||
cur = xfs_bmbt_init_cursor(mp, tp, ip, whichfork);
|
||||
cur->bc_private.b.firstblock = *firstfsb;
|
||||
cur->bc_private.b.flist = free_list;
|
||||
cur->bc_private.b.flags = 0;
|
||||
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(mp, i == 1, del_cursor);
|
||||
}
|
||||
|
||||
xfs_bmbt_set_blockcount(gotp, gotblkcnt);
|
||||
got.br_blockcount = gotblkcnt;
|
||||
|
||||
logflags = XFS_ILOG_CORE;
|
||||
if (cur) {
|
||||
error = xfs_bmbt_update(cur, got.br_startoff,
|
||||
got.br_startblock,
|
||||
got.br_blockcount,
|
||||
got.br_state);
|
||||
if (error)
|
||||
goto del_cursor;
|
||||
} else
|
||||
logflags |= XFS_ILOG_DEXT;
|
||||
|
||||
/* Add new extent */
|
||||
current_ext++;
|
||||
xfs_iext_insert(ip, current_ext, 1, &new, 0);
|
||||
XFS_IFORK_NEXT_SET(ip, whichfork,
|
||||
XFS_IFORK_NEXTENTS(ip, whichfork) + 1);
|
||||
|
||||
if (cur) {
|
||||
error = xfs_bmbt_lookup_eq(cur, new.br_startoff,
|
||||
new.br_startblock, new.br_blockcount,
|
||||
&i);
|
||||
if (error)
|
||||
goto del_cursor;
|
||||
XFS_WANT_CORRUPTED_GOTO(mp, i == 0, del_cursor);
|
||||
cur->bc_rec.b.br_state = new.br_state;
|
||||
|
||||
error = xfs_btree_insert(cur, &i);
|
||||
if (error)
|
||||
goto del_cursor;
|
||||
XFS_WANT_CORRUPTED_GOTO(mp, i == 1, del_cursor);
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert to a btree if necessary.
|
||||
*/
|
||||
if (xfs_bmap_needs_btree(ip, whichfork)) {
|
||||
int tmp_logflags; /* partial log flag return val */
|
||||
|
||||
ASSERT(cur == NULL);
|
||||
error = xfs_bmap_extents_to_btree(tp, ip, firstfsb, free_list,
|
||||
&cur, 0, &tmp_logflags, whichfork);
|
||||
logflags |= tmp_logflags;
|
||||
}
|
||||
|
||||
del_cursor:
|
||||
if (cur) {
|
||||
cur->bc_private.b.allocated = 0;
|
||||
xfs_btree_del_cursor(cur,
|
||||
error ? XFS_BTREE_ERROR : XFS_BTREE_NOERROR);
|
||||
}
|
||||
|
||||
if (logflags)
|
||||
xfs_trans_log_inode(tp, ip, logflags);
|
||||
return error;
|
||||
}
|
||||
|
||||
int
|
||||
xfs_bmap_split_extent(
|
||||
struct xfs_inode *ip,
|
||||
xfs_fileoff_t split_fsb)
|
||||
{
|
||||
struct xfs_mount *mp = ip->i_mount;
|
||||
struct xfs_trans *tp;
|
||||
struct xfs_bmap_free free_list;
|
||||
xfs_fsblock_t firstfsb;
|
||||
int committed;
|
||||
int error;
|
||||
|
||||
tp = xfs_trans_alloc(mp, XFS_TRANS_DIOSTRAT);
|
||||
error = xfs_trans_reserve(tp, &M_RES(mp)->tr_write,
|
||||
XFS_DIOSTRAT_SPACE_RES(mp, 0), 0);
|
||||
if (error) {
|
||||
xfs_trans_cancel(tp, 0);
|
||||
return error;
|
||||
}
|
||||
|
||||
xfs_ilock(ip, XFS_ILOCK_EXCL);
|
||||
xfs_trans_ijoin(tp, ip, XFS_ILOCK_EXCL);
|
||||
|
||||
xfs_bmap_init(&free_list, &firstfsb);
|
||||
|
||||
error = xfs_bmap_split_extent_at(tp, ip, split_fsb,
|
||||
&firstfsb, &free_list);
|
||||
if (error)
|
||||
goto out;
|
||||
|
||||
error = xfs_bmap_finish(&tp, &free_list, &committed);
|
||||
if (error)
|
||||
goto out;
|
||||
|
||||
return xfs_trans_commit(tp, XFS_TRANS_RELEASE_LOG_RES);
|
||||
|
||||
|
||||
out:
|
||||
xfs_trans_cancel(tp, XFS_TRANS_RELEASE_LOG_RES | XFS_TRANS_ABORT);
|
||||
return error;
|
||||
}
|
||||
|
Reference in New Issue
Block a user