/* * Trim an extent to end at the next CoW reservation past offset_fsb. */ void xfs_reflink_trim_irec_to_next_cow( struct xfs_inode *ip, xfs_fileoff_t offset_fsb, struct xfs_bmbt_irec *imap) { struct xfs_ifork *ifp = XFS_IFORK_PTR(ip, XFS_COW_FORK); struct xfs_bmbt_irec got; xfs_extnum_t idx; if (!xfs_is_reflink_inode(ip)) return; /* Find the extent in the CoW fork. */ if (!xfs_iext_lookup_extent(ip, ifp, offset_fsb, &idx, &got)) return; /* This is the extent before; try sliding up one. */ if (got.br_startoff < offset_fsb) { if (!xfs_iext_get_extent(ifp, idx + 1, &got)) return; } if (got.br_startoff >= imap->br_startoff + imap->br_blockcount) return; imap->br_blockcount = got.br_startoff - imap->br_startoff; trace_xfs_reflink_trim_irec(ip, imap); }
/* Convert all of the unwritten CoW extents in a file's range to real ones. */ int xfs_reflink_convert_cow( struct xfs_inode *ip, xfs_off_t offset, xfs_off_t count) { struct xfs_bmbt_irec got; struct xfs_defer_ops dfops; struct xfs_mount *mp = ip->i_mount; struct xfs_ifork *ifp = XFS_IFORK_PTR(ip, XFS_COW_FORK); xfs_fileoff_t offset_fsb = XFS_B_TO_FSBT(mp, offset); xfs_fileoff_t end_fsb = XFS_B_TO_FSB(mp, offset + count); xfs_extnum_t idx; bool found; int error = 0; xfs_ilock(ip, XFS_ILOCK_EXCL); /* Convert all the extents to real from unwritten. */ for (found = xfs_iext_lookup_extent(ip, ifp, offset_fsb, &idx, &got); found && got.br_startoff < end_fsb; found = xfs_iext_get_extent(ifp, ++idx, &got)) { error = xfs_reflink_convert_cow_extent(ip, &got, offset_fsb, end_fsb - offset_fsb, &dfops); if (error) break; } /* Finish up. */ xfs_iunlock(ip, XFS_ILOCK_EXCL); return error; }
/* Does this inode need the reflink flag? */ int xfs_reflink_inode_has_shared_extents( struct xfs_trans *tp, struct xfs_inode *ip, bool *has_shared) { struct xfs_bmbt_irec got; struct xfs_mount *mp = ip->i_mount; struct xfs_ifork *ifp; xfs_agnumber_t agno; xfs_agblock_t agbno; xfs_extlen_t aglen; xfs_agblock_t rbno; xfs_extlen_t rlen; xfs_extnum_t idx; bool found; int error; ifp = XFS_IFORK_PTR(ip, XFS_DATA_FORK); if (!(ifp->if_flags & XFS_IFEXTENTS)) { error = xfs_iread_extents(tp, ip, XFS_DATA_FORK); if (error) return error; } *has_shared = false; found = xfs_iext_lookup_extent(ip, ifp, 0, &idx, &got); while (found) { if (isnullstartblock(got.br_startblock) || got.br_state != XFS_EXT_NORM) goto next; agno = XFS_FSB_TO_AGNO(mp, got.br_startblock); agbno = XFS_FSB_TO_AGBNO(mp, got.br_startblock); aglen = got.br_blockcount; error = xfs_reflink_find_shared(mp, tp, agno, agbno, aglen, &rbno, &rlen, false); if (error) return error; /* Is there still a shared block here? */ if (rbno != NULLAGBLOCK) { *has_shared = true; return 0; } next: found = xfs_iext_get_extent(ifp, ++idx, &got); } return 0; }
/* * Remap parts of a file's data fork after a successful CoW. */ int xfs_reflink_end_cow( struct xfs_inode *ip, xfs_off_t offset, xfs_off_t count) { struct xfs_ifork *ifp = XFS_IFORK_PTR(ip, XFS_COW_FORK); struct xfs_bmbt_irec got, del; struct xfs_trans *tp; xfs_fileoff_t offset_fsb; xfs_fileoff_t end_fsb; xfs_fsblock_t firstfsb; struct xfs_defer_ops dfops; int error; unsigned int resblks; xfs_filblks_t rlen; xfs_extnum_t idx; trace_xfs_reflink_end_cow(ip, offset, count); /* No COW extents? That's easy! */ if (ifp->if_bytes == 0) return 0; offset_fsb = XFS_B_TO_FSBT(ip->i_mount, offset); end_fsb = XFS_B_TO_FSB(ip->i_mount, offset + count); /* * Start a rolling transaction to switch the mappings. We're * unlikely ever to have to remap 16T worth of single-block * extents, so just cap the worst case extent count to 2^32-1. * Stick a warning in just in case, and avoid 64-bit division. */ BUILD_BUG_ON(MAX_RW_COUNT > UINT_MAX); if (end_fsb - offset_fsb > UINT_MAX) { error = -EFSCORRUPTED; xfs_force_shutdown(ip->i_mount, SHUTDOWN_CORRUPT_INCORE); ASSERT(0); goto out; } resblks = XFS_NEXTENTADD_SPACE_RES(ip->i_mount, (unsigned int)(end_fsb - offset_fsb), XFS_DATA_FORK); error = xfs_trans_alloc(ip->i_mount, &M_RES(ip->i_mount)->tr_write, resblks, 0, 0, &tp); if (error) goto out; xfs_ilock(ip, XFS_ILOCK_EXCL); xfs_trans_ijoin(tp, ip, 0); /* If there is a hole at end_fsb - 1 go to the previous extent */ if (!xfs_iext_lookup_extent(ip, ifp, end_fsb - 1, &idx, &got) || got.br_startoff > end_fsb) { /* * In case of racing, overlapping AIO writes no COW extents * might be left by the time I/O completes for the loser of * the race. In that case we are done. */ if (idx <= 0) goto out_cancel; xfs_iext_get_extent(ifp, --idx, &got); } /* Walk backwards until we're out of the I/O range... */ while (got.br_startoff + got.br_blockcount > offset_fsb) { del = got; xfs_trim_extent(&del, offset_fsb, end_fsb - offset_fsb); /* Extent delete may have bumped idx forward */ if (!del.br_blockcount) { idx--; goto next_extent; } ASSERT(!isnullstartblock(got.br_startblock)); /* * Don't remap unwritten extents; these are * speculatively preallocated CoW extents that have been * allocated but have not yet been involved in a write. */ if (got.br_state == XFS_EXT_UNWRITTEN) { idx--; goto next_extent; } /* Unmap the old blocks in the data fork. */ xfs_defer_init(&dfops, &firstfsb); rlen = del.br_blockcount; error = __xfs_bunmapi(tp, ip, del.br_startoff, &rlen, 0, 1, &firstfsb, &dfops); if (error) goto out_defer; /* Trim the extent to whatever got unmapped. */ if (rlen) { xfs_trim_extent(&del, del.br_startoff + rlen, del.br_blockcount - rlen); } trace_xfs_reflink_cow_remap(ip, &del); /* Free the CoW orphan record. */ error = xfs_refcount_free_cow_extent(tp->t_mountp, &dfops, del.br_startblock, del.br_blockcount); if (error) goto out_defer; /* Map the new blocks into the data fork. */ error = xfs_bmap_map_extent(tp->t_mountp, &dfops, ip, &del); if (error) goto out_defer; /* Remove the mapping from the CoW fork. */ xfs_bmap_del_extent_cow(ip, &idx, &got, &del); xfs_defer_ijoin(&dfops, ip); error = xfs_defer_finish(&tp, &dfops); if (error) goto out_defer; next_extent: if (!xfs_iext_get_extent(ifp, idx, &got)) break; } error = xfs_trans_commit(tp); xfs_iunlock(ip, XFS_ILOCK_EXCL); if (error) goto out; return 0; out_defer: xfs_defer_cancel(&dfops); out_cancel: xfs_trans_cancel(tp); xfs_iunlock(ip, XFS_ILOCK_EXCL); out: trace_xfs_reflink_end_cow_error(ip, error, _RET_IP_); return error; }
/* * Cancel CoW reservations for some block range of an inode. * * If cancel_real is true this function cancels all COW fork extents for the * inode; if cancel_real is false, real extents are not cleared. */ int xfs_reflink_cancel_cow_blocks( struct xfs_inode *ip, struct xfs_trans **tpp, xfs_fileoff_t offset_fsb, xfs_fileoff_t end_fsb, bool cancel_real) { struct xfs_ifork *ifp = XFS_IFORK_PTR(ip, XFS_COW_FORK); struct xfs_bmbt_irec got, del; xfs_extnum_t idx; xfs_fsblock_t firstfsb; struct xfs_defer_ops dfops; int error = 0; if (!xfs_is_reflink_inode(ip)) return 0; if (!xfs_iext_lookup_extent(ip, ifp, offset_fsb, &idx, &got)) return 0; while (got.br_startoff < end_fsb) { del = got; xfs_trim_extent(&del, offset_fsb, end_fsb - offset_fsb); trace_xfs_reflink_cancel_cow(ip, &del); if (isnullstartblock(del.br_startblock)) { error = xfs_bmap_del_extent_delay(ip, XFS_COW_FORK, &idx, &got, &del); if (error) break; } else if (del.br_state == XFS_EXT_UNWRITTEN || cancel_real) { xfs_trans_ijoin(*tpp, ip, 0); xfs_defer_init(&dfops, &firstfsb); /* Free the CoW orphan record. */ error = xfs_refcount_free_cow_extent(ip->i_mount, &dfops, del.br_startblock, del.br_blockcount); if (error) break; xfs_bmap_add_free(ip->i_mount, &dfops, del.br_startblock, del.br_blockcount, NULL); /* Update quota accounting */ xfs_trans_mod_dquot_byino(*tpp, ip, XFS_TRANS_DQ_BCOUNT, -(long)del.br_blockcount); /* Roll the transaction */ xfs_defer_ijoin(&dfops, ip); error = xfs_defer_finish(tpp, &dfops); if (error) { xfs_defer_cancel(&dfops); break; } /* Remove the mapping from the CoW fork. */ xfs_bmap_del_extent_cow(ip, &idx, &got, &del); } if (!xfs_iext_get_extent(ifp, ++idx, &got)) break; } /* clear tag if cow fork is emptied */ if (!ifp->if_bytes) xfs_inode_clear_cowblocks_tag(ip); return error; }
/* * Cancel CoW reservations for some block range of an inode. * * If cancel_real is true this function cancels all COW fork extents for the * inode; if cancel_real is false, real extents are not cleared. */ int xfs_reflink_cancel_cow_blocks( struct xfs_inode *ip, struct xfs_trans **tpp, xfs_fileoff_t offset_fsb, xfs_fileoff_t end_fsb, bool cancel_real) { struct xfs_ifork *ifp = XFS_IFORK_PTR(ip, XFS_COW_FORK); struct xfs_bmbt_irec got, del; struct xfs_iext_cursor icur; xfs_fsblock_t firstfsb; struct xfs_defer_ops dfops; int error = 0; if (!xfs_is_reflink_inode(ip)) return 0; if (!xfs_iext_lookup_extent_before(ip, ifp, &end_fsb, &icur, &got)) return 0; /* Walk backwards until we're out of the I/O range... */ while (got.br_startoff + got.br_blockcount > offset_fsb) { del = got; xfs_trim_extent(&del, offset_fsb, end_fsb - offset_fsb); /* Extent delete may have bumped ext forward */ if (!del.br_blockcount) { xfs_iext_prev(ifp, &icur); goto next_extent; } trace_xfs_reflink_cancel_cow(ip, &del); if (isnullstartblock(del.br_startblock)) { error = xfs_bmap_del_extent_delay(ip, XFS_COW_FORK, &icur, &got, &del); if (error) break; } else if (del.br_state == XFS_EXT_UNWRITTEN || cancel_real) { xfs_trans_ijoin(*tpp, ip, 0); xfs_defer_init(&dfops, &firstfsb); /* Free the CoW orphan record. */ error = xfs_refcount_free_cow_extent(ip->i_mount, &dfops, del.br_startblock, del.br_blockcount); if (error) break; xfs_bmap_add_free(ip->i_mount, &dfops, del.br_startblock, del.br_blockcount, NULL); /* Update quota accounting */ xfs_trans_mod_dquot_byino(*tpp, ip, XFS_TRANS_DQ_BCOUNT, -(long)del.br_blockcount); /* Roll the transaction */ xfs_defer_ijoin(&dfops, ip); error = xfs_defer_finish(tpp, &dfops); if (error) { xfs_defer_cancel(&dfops); break; } /* Remove the mapping from the CoW fork. */ xfs_bmap_del_extent_cow(ip, &icur, &got, &del); } else { /* Didn't do anything, push cursor back. */ xfs_iext_prev(ifp, &icur); } next_extent: if (!xfs_iext_get_extent(ifp, &icur, &got)) break; } /* clear tag if cow fork is emptied */ if (!ifp->if_bytes) xfs_inode_clear_cowblocks_tag(ip); return error; }
/* * Cancel CoW reservations for some block range of an inode. * * If cancel_real is true this function cancels all COW fork extents for the * inode; if cancel_real is false, real extents are not cleared. * * Caller must have already joined the inode to the current transaction. The * inode will be joined to the transaction returned to the caller. */ int xfs_reflink_cancel_cow_blocks( struct xfs_inode *ip, struct xfs_trans **tpp, xfs_fileoff_t offset_fsb, xfs_fileoff_t end_fsb, bool cancel_real) { struct xfs_ifork *ifp = XFS_IFORK_PTR(ip, XFS_COW_FORK); struct xfs_bmbt_irec got, del; struct xfs_iext_cursor icur; int error = 0; if (!xfs_inode_has_cow_data(ip)) return 0; if (!xfs_iext_lookup_extent_before(ip, ifp, &end_fsb, &icur, &got)) return 0; /* Walk backwards until we're out of the I/O range... */ while (got.br_startoff + got.br_blockcount > offset_fsb) { del = got; xfs_trim_extent(&del, offset_fsb, end_fsb - offset_fsb); /* Extent delete may have bumped ext forward */ if (!del.br_blockcount) { xfs_iext_prev(ifp, &icur); goto next_extent; } trace_xfs_reflink_cancel_cow(ip, &del); if (isnullstartblock(del.br_startblock)) { error = xfs_bmap_del_extent_delay(ip, XFS_COW_FORK, &icur, &got, &del); if (error) break; } else if (del.br_state == XFS_EXT_UNWRITTEN || cancel_real) { ASSERT((*tpp)->t_firstblock == NULLFSBLOCK); /* Free the CoW orphan record. */ error = xfs_refcount_free_cow_extent(*tpp, del.br_startblock, del.br_blockcount); if (error) break; xfs_bmap_add_free(*tpp, del.br_startblock, del.br_blockcount, NULL); /* Roll the transaction */ error = xfs_defer_finish(tpp); if (error) break; /* Remove the mapping from the CoW fork. */ xfs_bmap_del_extent_cow(ip, &icur, &got, &del); /* Remove the quota reservation */ error = xfs_trans_reserve_quota_nblks(NULL, ip, -(long)del.br_blockcount, 0, XFS_QMOPT_RES_REGBLKS); if (error) break; } else { /* Didn't do anything, push cursor back. */ xfs_iext_prev(ifp, &icur); } next_extent: if (!xfs_iext_get_extent(ifp, &icur, &got)) break; } /* clear tag if cow fork is emptied */ if (!ifp->if_bytes) xfs_inode_clear_cowblocks_tag(ip); return error; }