+ }
+
+ /* If the source has the rsrc open, make sure the destination is also the rsrc */
+ if (rsrc) {
+ if (to_vp != to_cp->c_rsrc_vp) {
+ error = EINVAL;
+ goto movedata_exit;
+ }
+ }
+ else {
+ /* Verify that both forks are data forks */
+ if (to_vp != to_cp->c_vp) {
+ error = EINVAL;
+ goto movedata_exit;
+ }
+ }
+
+ /*
+ * See if the source file has overflow extents. If it doesn't, we don't
+ * need to call into MoveData, and the catalog will be enough.
+ */
+ if (rsrc) {
+ overflow_blocks = overflow_extents(from_cp->c_rsrcfork);
+ }
+ else {
+ overflow_blocks = overflow_extents(from_cp->c_datafork);
+ }
+
+ if ((error = hfs_start_transaction (hfsmp)) != 0) {
+ goto movedata_exit;
+ }
+ started_tr = 1;
+
+ /* Lock the system files: catalog, extents, attributes */
+ lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG | SFL_EXTENTS | SFL_ATTRIBUTE, HFS_EXCLUSIVE_LOCK);
+
+ /* Copy over any catalog allocation data into the new spot. */
+ if (rsrc) {
+ if ((error = hfs_move_fork (from_cp->c_rsrcfork, from_cp, to_cp->c_rsrcfork, to_cp))){
+ hfs_systemfile_unlock(hfsmp, lockflags);
+ goto movedata_exit;
+ }
+ }
+ else {
+ if ((error = hfs_move_fork (from_cp->c_datafork, from_cp, to_cp->c_datafork, to_cp))) {
+ hfs_systemfile_unlock(hfsmp, lockflags);
+ goto movedata_exit;
+ }
+ }
+
+ /*
+ * Note that because all we're doing is moving the extents around, we can
+ * probably do this in a single transaction: Each extent record (group of 8)
+ * is 64 bytes. A extent overflow B-Tree node is typically 4k. This means
+ * each node can hold roughly ~60 extent records == (480 extents).
+ *
+ * If a file was massively fragmented and had 20k extents, this means we'd
+ * roughly touch 20k/480 == 41 to 42 nodes, plus the index nodes, for half
+ * of the operation. (inserting or deleting). So if we're manipulating 80-100
+ * nodes, this is basically 320k of data to write to the journal in
+ * a bad case.
+ */
+ if (overflow_blocks != 0) {
+ if (rsrc) {
+ error = MoveData(hfsmp, from_cp->c_cnid, to_cp->c_cnid, 1);
+ }
+ else {
+ error = MoveData (hfsmp, from_cp->c_cnid, to_cp->c_cnid, 0);
+ }
+ }
+
+ if (error) {
+ /* Reverse the operation. Copy the fork data back into the source */
+ if (rsrc) {
+ hfs_move_fork (to_cp->c_rsrcfork, to_cp, from_cp->c_rsrcfork, from_cp);
+ }
+ else {
+ hfs_move_fork (to_cp->c_datafork, to_cp, from_cp->c_datafork, from_cp);
+ }
+ }
+ else {
+ struct cat_fork *src_data = NULL;
+ struct cat_fork *src_rsrc = NULL;
+ struct cat_fork *dst_data = NULL;
+ struct cat_fork *dst_rsrc = NULL;
+
+ /* Touch the times*/
+ to_cp->c_touch_acctime = TRUE;
+ to_cp->c_touch_chgtime = TRUE;
+ to_cp->c_touch_modtime = TRUE;
+
+ from_cp->c_touch_acctime = TRUE;
+ from_cp->c_touch_chgtime = TRUE;
+ from_cp->c_touch_modtime = TRUE;
+
+ hfs_touchtimes(hfsmp, to_cp);
+ hfs_touchtimes(hfsmp, from_cp);
+
+ if (from_cp->c_datafork) {
+ src_data = &from_cp->c_datafork->ff_data;
+ }
+ if (from_cp->c_rsrcfork) {
+ src_rsrc = &from_cp->c_rsrcfork->ff_data;
+ }
+
+ if (to_cp->c_datafork) {
+ dst_data = &to_cp->c_datafork->ff_data;
+ }
+ if (to_cp->c_rsrcfork) {
+ dst_rsrc = &to_cp->c_rsrcfork->ff_data;
+ }
+
+ /* Update the catalog nodes */
+ (void) cat_update(hfsmp, &from_cp->c_desc, &from_cp->c_attr,
+ src_data, src_rsrc);
+
+ (void) cat_update(hfsmp, &to_cp->c_desc, &to_cp->c_attr,
+ dst_data, dst_rsrc);
+
+ }
+ /* unlock the system files */
+ hfs_systemfile_unlock(hfsmp, lockflags);
+
+
+movedata_exit:
+ if (started_tr) {
+ hfs_end_transaction(hfsmp);
+ }
+
+ return error;
+
+}
+
+/*
+ * Copy all of the catalog and runtime data in srcfork to dstfork.
+ *
+ * This allows us to maintain the invalid ranges across the movedata operation so
+ * we don't need to force all of the pending IO right now. In addition, we move all
+ * non overflow-extent extents into the destination here.
+ */
+static int hfs_move_fork (struct filefork *srcfork, struct cnode *src_cp,
+ struct filefork *dstfork, struct cnode *dst_cp) {
+ struct rl_entry *invalid_range;
+ int size = sizeof(struct HFSPlusExtentDescriptor);
+ size = size * kHFSPlusExtentDensity;
+
+ /* If the dstfork has any invalid ranges, bail out */
+ invalid_range = TAILQ_FIRST(&dstfork->ff_invalidranges);
+ if (invalid_range != NULL) {
+ return EFBIG;
+ }
+
+ if (dstfork->ff_data.cf_size != 0 || dstfork->ff_data.cf_new_size != 0) {
+ return EFBIG;
+ }
+
+ /* First copy the invalid ranges */
+ while ((invalid_range = TAILQ_FIRST(&srcfork->ff_invalidranges))) {
+ off_t start = invalid_range->rl_start;
+ off_t end = invalid_range->rl_end;
+
+ /* Remove it from the srcfork and add it to dstfork */
+ rl_remove(start, end, &srcfork->ff_invalidranges);
+ rl_add(start, end, &dstfork->ff_invalidranges);
+ }
+
+ /*
+ * Ignore the ff_union. We don't move symlinks or system files.
+ * Now copy the in-catalog extent information
+ */
+ dstfork->ff_data.cf_size = srcfork->ff_data.cf_size;
+ dstfork->ff_data.cf_new_size = srcfork->ff_data.cf_new_size;
+ dstfork->ff_data.cf_vblocks = srcfork->ff_data.cf_vblocks;
+ dstfork->ff_data.cf_blocks = srcfork->ff_data.cf_blocks;
+
+ /* just memcpy the whole array of extents to the new location. */
+ memcpy (dstfork->ff_data.cf_extents, srcfork->ff_data.cf_extents, size);
+
+ /*
+ * Copy the cnode attribute data.
+ *
+ */
+ src_cp->c_blocks -= srcfork->ff_data.cf_vblocks;
+ src_cp->c_blocks -= srcfork->ff_data.cf_blocks;
+
+ dst_cp->c_blocks += srcfork->ff_data.cf_vblocks;
+ dst_cp->c_blocks += srcfork->ff_data.cf_blocks;
+
+ /* Now delete the entries in the source fork */
+ srcfork->ff_data.cf_size = 0;
+ srcfork->ff_data.cf_new_size = 0;
+ srcfork->ff_data.cf_union.cfu_bytesread = 0;
+ srcfork->ff_data.cf_vblocks = 0;
+ srcfork->ff_data.cf_blocks = 0;
+
+ /* Zero out the old extents */
+ bzero (srcfork->ff_data.cf_extents, size);
+ return 0;
+}
+
+
+/*
+ * cnode must be locked
+ */
+int
+hfs_fsync(struct vnode *vp, int waitfor, int fullsync, struct proc *p)
+{
+ struct cnode *cp = VTOC(vp);
+ struct filefork *fp = NULL;
+ int retval = 0;
+ struct hfsmount *hfsmp = VTOHFS(vp);
+ struct rl_entry *invalid_range;
+ struct timeval tv;
+ int waitdata; /* attributes necessary for data retrieval */
+ int wait; /* all other attributes (e.g. atime, etc.) */
+ int lockflag;
+ int took_trunc_lock = 0;
+ int locked_buffers = 0;
+
+ /*
+ * Applications which only care about data integrity rather than full
+ * file integrity may opt out of (delay) expensive metadata update
+ * operations as a performance optimization.
+ */
+ wait = (waitfor == MNT_WAIT);
+ waitdata = (waitfor == MNT_DWAIT) | wait;
+ if (always_do_fullfsync)
+ fullsync = 1;
+
+ /* HFS directories don't have any data blocks. */
+ if (vnode_isdir(vp))
+ goto metasync;
+ fp = VTOF(vp);
+
+ /*
+ * For system files flush the B-tree header and
+ * for regular files write out any clusters
+ */
+ if (vnode_issystem(vp)) {
+ if (VTOF(vp)->fcbBTCBPtr != NULL) {
+ // XXXdbg
+ if (hfsmp->jnl == NULL) {
+ BTFlushPath(VTOF(vp));
+ }
+ }
+ } else if (UBCINFOEXISTS(vp)) {
+ hfs_unlock(cp);
+ hfs_lock_truncate(cp, HFS_SHARED_LOCK);
+ took_trunc_lock = 1;
+
+ if (fp->ff_unallocblocks != 0) {
+ hfs_unlock_truncate(cp, 0);
+
+ hfs_lock_truncate(cp, HFS_EXCLUSIVE_LOCK);
+ }
+ /* Don't hold cnode lock when calling into cluster layer. */
+ (void) cluster_push(vp, waitdata ? IO_SYNC : 0);
+
+ hfs_lock(cp, HFS_FORCE_LOCK);
+ }
+ /*
+ * When MNT_WAIT is requested and the zero fill timeout
+ * has expired then we must explicitly zero out any areas
+ * that are currently marked invalid (holes).
+ *
+ * Files with NODUMP can bypass zero filling here.
+ */
+ if (fp && (((cp->c_flag & C_ALWAYS_ZEROFILL) && !TAILQ_EMPTY(&fp->ff_invalidranges)) ||
+ ((wait || (cp->c_flag & C_ZFWANTSYNC)) &&
+ ((cp->c_bsdflags & UF_NODUMP) == 0) &&
+ UBCINFOEXISTS(vp) && (vnode_issystem(vp) ==0) &&
+ cp->c_zftimeout != 0))) {
+
+ microuptime(&tv);
+ if ((cp->c_flag & C_ALWAYS_ZEROFILL) == 0 && !fullsync && tv.tv_sec < (long)cp->c_zftimeout) {
+ /* Remember that a force sync was requested. */
+ cp->c_flag |= C_ZFWANTSYNC;
+ goto datasync;
+ }
+ if (!TAILQ_EMPTY(&fp->ff_invalidranges)) {
+ if (!took_trunc_lock || (cp->c_truncatelockowner == HFS_SHARED_OWNER)) {
+ hfs_unlock(cp);
+ if (took_trunc_lock) {
+ hfs_unlock_truncate(cp, 0);
+ }
+ hfs_lock_truncate(cp, HFS_EXCLUSIVE_LOCK);
+ hfs_lock(cp, HFS_FORCE_LOCK);
+ took_trunc_lock = 1;
+ }
+ while ((invalid_range = TAILQ_FIRST(&fp->ff_invalidranges))) {
+ off_t start = invalid_range->rl_start;
+ off_t end = invalid_range->rl_end;