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@xxxxxxxxxx> --- 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(-) diff --git a/fs/xfs/xfs_bmap_util.c b/fs/xfs/xfs_bmap_util.c index 731260a5af6d..09053c0abe28 100644 --- a/fs/xfs/xfs_bmap_util.c +++ b/fs/xfs/xfs_bmap_util.c @@ -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; diff --git a/fs/xfs/xfs_defrag.c b/fs/xfs/xfs_defrag.c index 0375b542024e..6867f81783a0 100644 --- a/fs/xfs/xfs_defrag.c +++ b/fs/xfs/xfs_defrag.c @@ -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; } diff --git a/fs/xfs/xfs_iomap.c b/fs/xfs/xfs_iomap.c index 18c8f168b153..f6fdff3bdca4 100644 --- a/fs/xfs/xfs_iomap.c +++ b/fs/xfs/xfs_iomap.c @@ -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); diff --git a/fs/xfs/xfs_reflink.c b/fs/xfs/xfs_reflink.c index e5b62dc28466..7d7d67087fcc 100644 --- a/fs/xfs/xfs_reflink.c +++ b/fs/xfs/xfs_reflink.c @@ -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 diff --git a/fs/xfs/xfs_reflink.h b/fs/xfs/xfs_reflink.h index 65c5dfe17ecf..d751420650f2 100644 --- a/fs/xfs/xfs_reflink.h +++ b/fs/xfs/xfs_reflink.h @@ -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); -- 2.39.3 (Apple Git-145)