@@ -301,7 +301,7 @@ xfs_getbmap_report_one(
bool shared = false;
int error;
- error = xfs_reflink_trim_around_shared(ip, got, &shared);
+ error = xfs_reflink_trim_around_shared(ip, got, &shared, NULL);
if (error)
return error;
@@ -553,13 +553,102 @@ static int xfs_defrag_copy_piece_sync(struct xfs_defrag_info *di,
return error;
}
+/* caller makes sure both irec1 and irec2 are real ones. */
+static int compare_bmbt_by_fsb(const void *a, const void *b)
+{
+ const struct xfs_bmbt_irec *irec1 = a, *irec2 = b;
+
+ return irec1->br_startblock > irec2->br_startblock ? 1 : -1;
+}
+
+/* sort the extents in dp_extents to be in fsb order, low to high */
+static void xfs_sort_piece_exts_by_fsb(struct xfs_defrag_piece *dp)
+{
+ sort(dp->dp_extents, dp->dp_nr_ext, sizeof(struct xfs_bmbt_irec),
+ compare_bmbt_by_fsb, NULL);
+}
+
+/*
+ * unmap the given extent from inode
+ * free non-shared blocks and decrease shared counter for shared ones.
+ */
+static int xfs_defrag_unmap_ext(struct xfs_inode *ip,
+ struct xfs_bmbt_irec *irec,
+ struct xfs_trans *tp)
+{
+ struct xfs_bmbt_irec unmap = *irec; /* don't update original extent */
+ xfs_fsblock_t irec_end = irec->br_startblock + irec->br_blockcount;
+ int error = 0;
+
+ while (unmap.br_startblock < irec_end) {
+ bool shared;
+
+ error = xfs_reflink_trim_around_shared(ip, &unmap, &shared, tp);
+ if (error)
+ goto out;
+
+ /* unmap blocks from data fork */
+ xfs_bmap_unmap_extent(tp, ip, &unmap);
+ /*
+ * decrease refcount counter for shared blocks, or free the
+ * non-shared blocks
+ */
+ if (shared) {
+ xfs_refcount_decrease_extent(tp, &unmap);
+ } else {
+ ASSERT(unmap.br_state != XFS_EXT_UNWRITTEN);
+ __xfs_free_extent_later(tp, unmap.br_startblock,
+ unmap.br_blockcount, NULL, 0, false);
+ }
+ xfs_trans_mod_dquot_byino(tp, ip, XFS_TRANS_DQ_BCOUNT,
+ -unmap.br_blockcount);
+
+ /* for next */
+ unmap.br_startoff += unmap.br_blockcount;
+ unmap.br_startblock += unmap.br_blockcount;
+ unmap.br_blockcount = irec_end - unmap.br_startblock;
+ }
+out:
+ return error;
+}
+
+/*
+ * unmap original extents in this piece
+ * for those non-shared ones, also free them; for shared, decrease refcount
+ * counter.
+ * XFS_ILOCK_EXCL is locked by caller.
+ */
+static int xfs_defrag_unmap_piece(struct xfs_defrag_info *di, struct xfs_trans *tp)
+{
+ struct xfs_defrag_piece *dp = &di->di_dp;
+ xfs_fsblock_t last_fsb = 0;
+ int i, error;
+
+ for (i = 0; i < dp->dp_nr_ext; i++) {
+ struct xfs_bmbt_irec *irec = &dp->dp_extents[i];
+
+ /* debug only, remove the following two lines for production use */
+ ASSERT(last_fsb == 0 || irec->br_startblock > last_fsb);
+ last_fsb = irec->br_startblock;
+
+ error = xfs_defrag_unmap_ext(di->di_ip, irec, tp);
+ if (error)
+ break;
+ }
+ return error;
+}
+
/* defrag on the given piece
* XFS_ILOCK_EXCL is held by caller
*/
static int xfs_defrag_file_piece(struct xfs_defrag_info *di)
{
struct xfs_inode *ip = di->di_ip;
- struct xfs_bmbt_irec imap;
+ struct xfs_mount *mp = ip->i_mount;
+ struct xfs_trans *tp = NULL;
+ struct xfs_bmbt_irec imap, del;
+ unsigned int resblks;
+
int error;
struct xfs_iext_cursor icur;
@@ -579,6 +668,55 @@ static int xfs_defrag_file_piece(struct xfs_defrag_info *di)
error = xfs_defrag_copy_piece_sync(di, &imap);
if (error)
goto out;
+
+ /* sort the extents by FSB, low -> high, for later unmapping*/
+ xfs_sort_piece_exts_by_fsb(&di->di_dp);
+
+ resblks = XFS_EXTENTADD_SPACE_RES(mp, XFS_DATA_FORK);
+ error = xfs_trans_alloc(mp, &M_RES(mp)->tr_write, resblks, 0,
+ XFS_TRANS_RESERVE, &tp);
+ if (error)
+ goto out;
+
+ xfs_ilock(ip, XFS_ILOCK_EXCL);
+ xfs_trans_ijoin(tp, ip, 0);
+
+ /* unmap original extents in data fork */
+ error = xfs_defrag_unmap_piece(di, tp);
+ if (error) {
+ xfs_trans_cancel(tp);
+ goto out;
+ }
+
+ /* adjust new blocks to proper range */
+ del = imap;
+ if (del.br_blockcount > di->di_dp.dp_len) {
+ xfs_filblks_t diff = di->di_dp.dp_start_off - del.br_startoff;
+
+ del.br_startoff += diff;
+ del.br_startblock += diff;
+ del.br_blockcount = di->di_dp.dp_len;
+ }
+
+ /* Free the CoW orphan record. */
+ xfs_refcount_free_cow_extent(tp, del.br_startblock, del.br_blockcount);
+
+ /* map the adjusted new blocks to data fork */
+ xfs_bmap_map_extent(tp, ip, &del);
+
+ /* Charge this new data fork mapping to the on-disk quota. */
+ xfs_trans_mod_dquot_byino(tp, ip, XFS_TRANS_DQ_DELBCOUNT,
+ (long)del.br_blockcount);
+
+ /* remove the extent from Cow fork */
+ xfs_bmap_del_extent_cow(ip, &icur, &imap, &del);
+
+ /* modify inode change time */
+ xfs_trans_ichgtime(tp, ip, XFS_ICHGTIME_CHG);
+
+ error = xfs_trans_commit(tp);
+ xfs_iunlock(ip, XFS_ILOCK_EXCL);
+
out:
return error;
}
@@ -1256,7 +1256,7 @@ xfs_read_iomap_begin(
error = xfs_bmapi_read(ip, offset_fsb, end_fsb - offset_fsb, &imap,
&nimaps, 0);
if (!error && ((flags & IOMAP_REPORT) || IS_DAX(inode)))
- error = xfs_reflink_trim_around_shared(ip, &imap, &shared);
+ error = xfs_reflink_trim_around_shared(ip, &imap, &shared, NULL);
seq = xfs_iomap_inode_sequence(ip, shared ? IOMAP_F_SHARED : 0);
xfs_iunlock(ip, lockmode);
@@ -168,7 +168,8 @@ int
xfs_reflink_trim_around_shared(
struct xfs_inode *ip,
struct xfs_bmbt_irec *irec,
- bool *shared)
+ bool *shared,
+ struct xfs_trans *tp)
{
struct xfs_mount *mp = ip->i_mount;
struct xfs_perag *pag;
@@ -190,7 +191,7 @@ xfs_reflink_trim_around_shared(
agbno = XFS_FSB_TO_AGBNO(mp, irec->br_startblock);
aglen = irec->br_blockcount;
- error = xfs_reflink_find_shared(pag, NULL, agbno, aglen, &fbno, &flen,
+ error = xfs_reflink_find_shared(pag, tp, agbno, aglen, &fbno, &flen,
true);
xfs_perag_put(pag);
if (error)
@@ -238,7 +239,7 @@ xfs_bmap_trim_cow(
}
/* Trim the mapping to the nearest shared extent boundary. */
- return xfs_reflink_trim_around_shared(ip, imap, shared);
+ return xfs_reflink_trim_around_shared(ip, imap, shared, NULL);
}
static int
@@ -17,7 +17,8 @@ static inline bool xfs_is_cow_inode(struct xfs_inode *ip)
}
extern int xfs_reflink_trim_around_shared(struct xfs_inode *ip,
- struct xfs_bmbt_irec *irec, bool *shared);
+ struct xfs_bmbt_irec *irec, bool *shared,
+ struct xfs_trans *tp);
int xfs_bmap_trim_cow(struct xfs_inode *ip, struct xfs_bmbt_irec *imap,
bool *shared);
Unmap original extents. Drop refcounter for shared blocks; free not shared ones. Fre Cow orphon record. map new blocks to data fork and remove them from cow fork. copy data from old blocks to new blocks synchronously Signed-off-by: Wengang Wang <wen.gang.wang@oracle.com> --- fs/xfs/xfs_bmap_util.c | 2 +- fs/xfs/xfs_defrag.c | 140 ++++++++++++++++++++++++++++++++++++++++- fs/xfs/xfs_iomap.c | 2 +- fs/xfs/xfs_reflink.c | 7 ++- fs/xfs/xfs_reflink.h | 3 +- 5 files changed, 147 insertions(+), 7 deletions(-)