+/*
+ * Relocate a file to a new location on disk
+ * cnode must be locked on entry
+ *
+ * Relocation occurs by cloning the file's data from its
+ * current set of blocks to a new set of blocks. During
+ * the relocation all of the blocks (old and new) are
+ * owned by the file.
+ *
+ * -----------------
+ * |///////////////|
+ * -----------------
+ * 0 N (file offset)
+ *
+ * ----------------- -----------------
+ * |///////////////| | | STEP 1 (aquire new blocks)
+ * ----------------- -----------------
+ * 0 N N+1 2N
+ *
+ * ----------------- -----------------
+ * |///////////////| |///////////////| STEP 2 (clone data)
+ * ----------------- -----------------
+ * 0 N N+1 2N
+ *
+ * -----------------
+ * |///////////////| STEP 3 (head truncate blocks)
+ * -----------------
+ * 0 N
+ *
+ * During steps 2 and 3 page-outs to file offsets less
+ * than or equal to N are suspended.
+ *
+ * During step 3 page-ins to the file get supended.
+ */
+__private_extern__
+int
+hfs_relocate(vp, blockHint, cred, p)
+ struct vnode *vp;
+ u_int32_t blockHint;
+ struct ucred *cred;
+ struct proc *p;
+{
+ struct filefork *fp;
+ struct hfsmount *hfsmp;
+ ExtendedVCB *vcb;
+
+ u_int32_t headblks;
+ u_int32_t datablks;
+ u_int32_t blksize;
+ u_int32_t realsize;
+ u_int32_t growsize;
+ u_int32_t nextallocsave;
+ u_int32_t sector_a;
+ u_int32_t sector_b;
+ int eflags;
+ u_int32_t oldstart; /* debug only */
+ off_t newbytes;
+ int retval;
+
+ if (vp->v_type != VREG && vp->v_type != VLNK) {
+ return (EPERM);
+ }
+
+ hfsmp = VTOHFS(vp);
+ if (hfsmp->hfs_flags & HFS_FRAGMENTED_FREESPACE) {
+ return (ENOSPC);
+ }
+
+ fp = VTOF(vp);
+ if (fp->ff_unallocblocks)
+ return (EINVAL);
+ vcb = VTOVCB(vp);
+ blksize = vcb->blockSize;
+ if (blockHint == 0)
+ blockHint = vcb->nextAllocation;
+
+ if ((fp->ff_size > (u_int64_t)0x7fffffff) ||
+ (vp->v_type == VLNK && fp->ff_size > blksize)) {
+ return (EFBIG);
+ }
+
+ headblks = fp->ff_blocks;
+ datablks = howmany(fp->ff_size, blksize);
+ growsize = datablks * blksize;
+ realsize = fp->ff_size;
+ eflags = kEFContigMask | kEFAllMask | kEFNoClumpMask;
+ if (blockHint >= hfsmp->hfs_metazone_start &&
+ blockHint <= hfsmp->hfs_metazone_end)
+ eflags |= kEFMetadataMask;
+
+ hfs_global_shared_lock_acquire(hfsmp);
+ if (hfsmp->jnl) {
+ if (journal_start_transaction(hfsmp->jnl) != 0) {
+ return (EINVAL);
+ }
+ }
+
+ /* Lock extents b-tree (also protects volume bitmap) */
+ retval = hfs_metafilelocking(hfsmp, kHFSExtentsFileID, LK_EXCLUSIVE, p);
+ if (retval)
+ goto out2;
+
+ retval = MapFileBlockC(vcb, (FCB *)fp, 1, growsize - 1, §or_a, NULL);
+ if (retval) {
+ retval = MacToVFSError(retval);
+ goto out;
+ }
+
+ /*
+ * STEP 1 - aquire new allocation blocks.
+ */
+ nextallocsave = vcb->nextAllocation;
+ retval = ExtendFileC(vcb, (FCB*)fp, growsize, blockHint, eflags, &newbytes);
+ if (eflags & kEFMetadataMask)
+ vcb->nextAllocation = nextallocsave;
+
+ retval = MacToVFSError(retval);
+ if (retval == 0) {
+ VTOC(vp)->c_flag |= C_MODIFIED;
+ if (newbytes < growsize) {
+ retval = ENOSPC;
+ goto restore;
+ } else if (fp->ff_blocks < (headblks + datablks)) {
+ printf("hfs_relocate: allocation failed");
+ retval = ENOSPC;
+ goto restore;
+ }
+
+ retval = MapFileBlockC(vcb, (FCB *)fp, 1, growsize, §or_b, NULL);
+ if (retval) {
+ retval = MacToVFSError(retval);
+ } else if ((sector_a + 1) == sector_b) {
+ retval = ENOSPC;
+ goto restore;
+ } else if ((eflags & kEFMetadataMask) &&
+ ((((u_int64_t)sector_b * hfsmp->hfs_phys_block_size) / blksize) >
+ hfsmp->hfs_metazone_end)) {
+ printf("hfs_relocate: didn't move into metadata zone\n");
+ retval = ENOSPC;
+ goto restore;
+ }
+ }
+ if (retval) {
+ /*
+ * Check to see if failure is due to excessive fragmentation.
+ */
+ if (retval == ENOSPC &&
+ hfs_freeblks(hfsmp, 0) > (datablks * 2)) {
+ hfsmp->hfs_flags |= HFS_FRAGMENTED_FREESPACE;
+ }
+ goto out;
+ }
+
+ fp->ff_size = fp->ff_blocks * blksize;
+ if (UBCISVALID(vp))
+ (void) ubc_setsize(vp, fp->ff_size);
+
+ /*
+ * STEP 2 - clone data into the new allocation blocks.
+ */
+
+ if (vp->v_type == VLNK)
+ retval = hfs_clonelink(vp, blksize, cred, p);
+ else if (vp->v_flag & VSYSTEM)
+ retval = hfs_clonesysfile(vp, headblks, datablks, blksize, cred, p);
+ else
+ retval = hfs_clonefile(vp, headblks, datablks, blksize, cred, p);
+
+ if (retval)
+ goto restore;
+
+ oldstart = fp->ff_extents[0].startBlock;
+
+ /*
+ * STEP 3 - switch to clone and remove old blocks.
+ */
+ SET(VTOC(vp)->c_flag, C_NOBLKMAP); /* suspend page-ins */
+
+ retval = HeadTruncateFile(vcb, (FCB*)fp, headblks);
+
+ CLR(VTOC(vp)->c_flag, C_NOBLKMAP); /* resume page-ins */
+ if (ISSET(VTOC(vp)->c_flag, C_WBLKMAP))
+ wakeup(VTOC(vp));
+ if (retval)
+ goto restore;
+
+ fp->ff_size = realsize;
+ if (UBCISVALID(vp)) {
+ (void) ubc_setsize(vp, realsize);
+ (void) vinvalbuf(vp, V_SAVE, cred, p, 0, 0);
+ }
+
+ CLR(VTOC(vp)->c_flag, C_RELOCATING); /* Resume page-outs for this file. */
+out:
+ (void) hfs_metafilelocking(VTOHFS(vp), kHFSExtentsFileID, LK_RELEASE, p);
+
+ retval = VOP_FSYNC(vp, cred, MNT_WAIT, p);
+out2:
+ if (hfsmp->jnl) {
+ if (VTOC(vp)->c_cnid < kHFSFirstUserCatalogNodeID)
+ (void) hfs_flushvolumeheader(hfsmp, MNT_WAIT, HFS_ALTFLUSH);
+ else
+ (void) hfs_flushvolumeheader(hfsmp, MNT_NOWAIT, 0);
+ journal_end_transaction(hfsmp->jnl);
+ }
+ hfs_global_shared_lock_release(hfsmp);
+
+ return (retval);
+
+restore:
+ /*
+ * Give back any newly allocated space.
+ */
+ if (fp->ff_size != realsize)
+ fp->ff_size = realsize;
+ (void) TruncateFileC(vcb, (FCB*)fp, fp->ff_size, false);
+ if (UBCISVALID(vp))
+ (void) ubc_setsize(vp, fp->ff_size);
+ CLR(VTOC(vp)->c_flag, C_RELOCATING);
+ goto out;
+}
+
+
+/*
+ * Clone a symlink.
+ *
+ */
+static int
+hfs_clonelink(struct vnode *vp, int blksize, struct ucred *cred, struct proc *p)
+{
+ struct buf *head_bp = NULL;
+ struct buf *tail_bp = NULL;
+ int error;
+
+
+ error = meta_bread(vp, 0, blksize, cred, &head_bp);
+ if (error)
+ goto out;
+
+ tail_bp = getblk(vp, 1, blksize, 0, 0, BLK_META);
+ if (tail_bp == NULL) {
+ error = EIO;
+ goto out;
+ }
+ bcopy(head_bp->b_data, tail_bp->b_data, blksize);
+ error = bwrite(tail_bp);
+out:
+ if (head_bp) {
+ head_bp->b_flags |= B_INVAL;
+ brelse(head_bp);
+ }
+ (void) vinvalbuf(vp, V_SAVE, cred, p, 0, 0);
+
+ return (error);