/* * Ensure the reflink bit is set in both inodes. */ STATIC int xfs_reflink_set_inode_flag( struct xfs_inode *src, struct xfs_inode *dest) { struct xfs_mount *mp = src->i_mount; int error; struct xfs_trans *tp; if (xfs_is_reflink_inode(src) && xfs_is_reflink_inode(dest)) return 0; error = xfs_trans_alloc(mp, &M_RES(mp)->tr_ichange, 0, 0, 0, &tp); if (error) goto out_error; /* Lock both files against IO */ if (src->i_ino == dest->i_ino) xfs_ilock(src, XFS_ILOCK_EXCL); else xfs_lock_two_inodes(src, dest, XFS_ILOCK_EXCL); if (!xfs_is_reflink_inode(src)) { trace_xfs_reflink_set_inode_flag(src); xfs_trans_ijoin(tp, src, XFS_ILOCK_EXCL); src->i_d.di_flags2 |= XFS_DIFLAG2_REFLINK; xfs_trans_log_inode(tp, src, XFS_ILOG_CORE); xfs_ifork_init_cow(src); } else xfs_iunlock(src, XFS_ILOCK_EXCL); if (src->i_ino == dest->i_ino) goto commit_flags; if (!xfs_is_reflink_inode(dest)) { trace_xfs_reflink_set_inode_flag(dest); xfs_trans_ijoin(tp, dest, XFS_ILOCK_EXCL); dest->i_d.di_flags2 |= XFS_DIFLAG2_REFLINK; xfs_trans_log_inode(tp, dest, XFS_ILOG_CORE); xfs_ifork_init_cow(dest); } else xfs_iunlock(dest, XFS_ILOCK_EXCL); commit_flags: error = xfs_trans_commit(tp); if (error) goto out_error; return error; out_error: trace_xfs_reflink_set_inode_flag_error(dest, error, _RET_IP_); return error; }
/* Allocate all CoW reservations covering a range of blocks in a file. */ int xfs_reflink_allocate_cow( struct xfs_inode *ip, struct xfs_bmbt_irec *imap, bool *shared, uint *lockmode, bool convert_now) { struct xfs_mount *mp = ip->i_mount; xfs_fileoff_t offset_fsb = imap->br_startoff; xfs_filblks_t count_fsb = imap->br_blockcount; struct xfs_trans *tp; int nimaps, error = 0; bool found; xfs_filblks_t resaligned; xfs_extlen_t resblks = 0; ASSERT(xfs_isilocked(ip, XFS_ILOCK_EXCL)); if (!ip->i_cowfp) { ASSERT(!xfs_is_reflink_inode(ip)); xfs_ifork_init_cow(ip); } error = xfs_find_trim_cow_extent(ip, imap, shared, &found); if (error || !*shared) return error; if (found) goto convert; resaligned = xfs_aligned_fsb_count(imap->br_startoff, imap->br_blockcount, xfs_get_cowextsz_hint(ip)); resblks = XFS_DIOSTRAT_SPACE_RES(mp, resaligned); xfs_iunlock(ip, *lockmode); error = xfs_trans_alloc(mp, &M_RES(mp)->tr_write, resblks, 0, 0, &tp); *lockmode = XFS_ILOCK_EXCL; xfs_ilock(ip, *lockmode); if (error) return error; error = xfs_qm_dqattach_locked(ip, false); if (error) goto out_trans_cancel; /* * Check for an overlapping extent again now that we dropped the ilock. */ error = xfs_find_trim_cow_extent(ip, imap, shared, &found); if (error || !*shared) goto out_trans_cancel; if (found) { xfs_trans_cancel(tp); goto convert; } error = xfs_trans_reserve_quota_nblks(tp, ip, resblks, 0, XFS_QMOPT_RES_REGBLKS); if (error) goto out_trans_cancel; xfs_trans_ijoin(tp, ip, 0); /* Allocate the entire reservation as unwritten blocks. */ nimaps = 1; error = xfs_bmapi_write(tp, ip, imap->br_startoff, imap->br_blockcount, XFS_BMAPI_COWFORK | XFS_BMAPI_PREALLOC, resblks, imap, &nimaps); if (error) goto out_unreserve; xfs_inode_set_cowblocks_tag(ip); error = xfs_trans_commit(tp); if (error) return error; /* * Allocation succeeded but the requested range was not even partially * satisfied? Bail out! */ if (nimaps == 0) return -ENOSPC; convert: xfs_trim_extent(imap, offset_fsb, count_fsb); /* * COW fork extents are supposed to remain unwritten until we're ready * to initiate a disk write. For direct I/O we are going to write the * data and need the conversion, but for buffered writes we're done. */ if (!convert_now || imap->br_state == XFS_EXT_NORM) return 0; trace_xfs_reflink_convert_cow(ip, imap); return xfs_reflink_convert_cow_locked(ip, offset_fsb, count_fsb); out_unreserve: xfs_trans_unreserve_quota_nblks(tp, ip, (long)resblks, 0, XFS_QMOPT_RES_REGBLKS); out_trans_cancel: xfs_trans_cancel(tp); return error; }