+ /* Rehash the cnodes using their new file IDs */
+ hfs_chash_rehash(hfsmp, from_cp, to_cp);
+
+ /*
+ * When a file moves out of "Cleanup At Startup"
+ * we can drop its NODUMP status.
+ */
+ if ((from_cp->c_bsdflags & UF_NODUMP) &&
+ (from_cp->c_parentcnid != to_cp->c_parentcnid)) {
+ from_cp->c_bsdflags &= ~UF_NODUMP;
+ from_cp->c_touch_chgtime = TRUE;
+ }
+ if ((to_cp->c_bsdflags & UF_NODUMP) &&
+ (to_cp->c_parentcnid != from_cp->c_parentcnid)) {
+ to_cp->c_bsdflags &= ~UF_NODUMP;
+ to_cp->c_touch_chgtime = TRUE;
+ }
+
+exit:
+ if (got_cookie) {
+ cat_postflight(hfsmp, &cookie, vfs_context_proc(ap->a_context));
+ }
+ if (started_tr) {
+ hfs_end_transaction(hfsmp);
+ }
+
+ if (have_cnode_locks)
+ hfs_unlockpair(from_cp, to_cp);
+
+ if (have_from_trunc_lock)
+ hfs_unlock_truncate(from_cp, 0);
+
+ if (have_to_trunc_lock)
+ hfs_unlock_truncate(to_cp, 0);
+
+ return (error);
+}
+
+#if HFS_COMPRESSION
+/*
+ * This function is used specifically for the case when a namespace
+ * handler is trying to steal data before it's deleted. Note that we
+ * don't bother deleting the xattr from the source because it will get
+ * deleted a short time later anyway.
+ *
+ * cnodes must be locked
+ */
+static int hfs_move_compressed(cnode_t *from_cp, cnode_t *to_cp)
+{
+ int ret;
+ void *data = NULL;
+
+ CLR(from_cp->c_bsdflags, UF_COMPRESSED);
+ SET(from_cp->c_flag, C_MODIFIED);
+
+ ret = hfs_move_data(from_cp, to_cp, HFS_MOVE_DATA_INCLUDE_RSRC);
+ if (ret)
+ goto exit;
+
+ /*
+ * Transfer the xattr that decmpfs uses. Ideally, this code
+ * should be with the other decmpfs code but it's file system
+ * agnostic and this path is currently, and likely to remain, HFS+
+ * specific. It's easier and more performant if we implement it
+ * here.
+ */
+
+ size_t size = MAX_DECMPFS_XATTR_SIZE;
+ MALLOC(data, void *, size, M_TEMP, M_WAITOK);
+
+ ret = hfs_xattr_read(from_cp->c_vp, DECMPFS_XATTR_NAME, data, &size);
+ if (ret)
+ goto exit;
+
+ ret = hfs_xattr_write(to_cp->c_vp, DECMPFS_XATTR_NAME, data, size);
+ if (ret)
+ goto exit;
+
+ SET(to_cp->c_bsdflags, UF_COMPRESSED);
+ SET(to_cp->c_flag, C_MODIFIED);
+
+exit:
+ if (data)
+ FREE(data, M_TEMP);
+
+ return ret;
+}
+#endif // HFS_COMPRESSION
+
+int
+hfs_vnop_mmap(struct vnop_mmap_args *ap)
+{
+ struct vnode *vp = ap->a_vp;
+ cnode_t *cp = VTOC(vp);
+ int error;
+
+ if (VNODE_IS_RSRC(vp)) {
+ /* allow pageins of the resource fork */
+ } else {
+ int compressed = hfs_file_is_compressed(cp, 1); /* 1 == don't take the cnode lock */
+ time_t orig_ctime = cp->c_ctime;
+
+ if (!compressed && (cp->c_bsdflags & UF_COMPRESSED)) {
+ error = check_for_dataless_file(vp, NAMESPACE_HANDLER_READ_OP);
+ if (error != 0) {
+ return error;
+ }
+ }
+
+ if (ap->a_fflags & PROT_WRITE) {
+ check_for_tracked_file(vp, orig_ctime, NAMESPACE_HANDLER_WRITE_OP, NULL);
+ }
+ }
+
+ //
+ // NOTE: we return ENOTSUP because we want the cluster layer
+ // to actually do all the real work.
+ //
+ return (ENOTSUP);
+}
+
+static errno_t hfs_vnop_mnomap(struct vnop_mnomap_args *ap)
+{
+ vnode_t vp = ap->a_vp;
+
+ /*
+ * Whilst the file was mapped, there may not have been any
+ * page-outs so we need to increment the generation counter now.
+ * Unfortunately this may lead to a change in the generation
+ * counter when no actual change has been made, but there is
+ * little we can do about that with our current architecture.
+ */
+ if (ubc_is_mapped_writable(vp)) {
+ cnode_t *cp = VTOC(vp);
+ hfs_lock(cp, HFS_EXCLUSIVE_LOCK, HFS_LOCK_ALLOW_NOEXISTS);
+ hfs_incr_gencount(cp);
+
+ /*
+ * We don't want to set the modification time here since a
+ * change to that is not acceptable if no changes were made.
+ * Instead we set a flag so that if we get any page-outs we
+ * know to update the modification time. It's possible that
+ * they weren't actually because of changes made whilst the
+ * file was mapped but that's not easy to fix now.
+ */
+ SET(cp->c_flag, C_MIGHT_BE_DIRTY_FROM_MAPPING);
+
+ hfs_unlock(cp);
+ }
+
+ return 0;
+}
+
+/*
+ * Mark the resource fork as needing a ubc_setsize when we drop the
+ * cnode lock later.
+ */
+static void hfs_rsrc_setsize(cnode_t *cp)
+{
+ /*
+ * We need to take an iocount if we don't have one. vnode_get
+ * will return ENOENT if the vnode is terminating which is what we
+ * want as it's not safe to call ubc_setsize in that case.
+ */
+ if (cp->c_rsrc_vp && !vnode_get(cp->c_rsrc_vp)) {
+ // Shouldn't happen, but better safe...
+ if (ISSET(cp->c_flag, C_NEED_RVNODE_PUT))
+ vnode_put(cp->c_rsrc_vp);
+ SET(cp->c_flag, C_NEED_RVNODE_PUT | C_NEED_RSRC_SETSIZE);
+ }
+}
+
+/*
+ * hfs_move_data
+ *
+ * This is a non-symmetric variant of exchangedata. In this function,
+ * the contents of the data fork (and optionally the resource fork)
+ * are moved from from_cp to to_cp.
+ *
+ * The cnodes must be locked.
+ *
+ * The cnode pointed to by 'to_cp' *must* be empty prior to invoking
+ * this function. We impose this restriction because we may not be
+ * able to fully delete the entire file's contents in a single
+ * transaction, particularly if it has a lot of extents. In the
+ * normal file deletion codepath, the file is screened for two
+ * conditions: 1) bigger than 400MB, and 2) more than 8 extents. If
+ * so, the file is relocated to the hidden directory and the deletion
+ * is broken up into multiple truncates. We can't do that here
+ * because both files need to exist in the namespace. The main reason
+ * this is imposed is that we may have to touch a whole lot of bitmap
+ * blocks if there are many extents.
+ *
+ * Any data written to 'from_cp' after this call completes is not
+ * guaranteed to be moved.
+ *
+ * Arguments:
+ * cnode_t *from_cp : source file
+ * cnode_t *to_cp : destination file; must be empty
+ *
+ * Returns:
+ *
+ * EBUSY - File has been deleted or is in use
+ * EFBIG - Destination file was not empty
+ * EIO - An I/O error
+ * 0 - success
+ * other - Other errors that can be returned from called functions
+ */
+int hfs_move_data(cnode_t *from_cp, cnode_t *to_cp,
+ hfs_move_data_options_t options)
+{
+ hfsmount_t *hfsmp = VTOHFS(from_cp->c_vp);
+ int error = 0;
+ int lockflags = 0;
+ bool return_EIO_on_error = false;
+ const bool include_rsrc = ISSET(options, HFS_MOVE_DATA_INCLUDE_RSRC);
+
+ /* Verify that neither source/dest file is open-unlinked */
+ if (ISSET(from_cp->c_flag, C_DELETED | C_NOEXISTS)
+ || ISSET(to_cp->c_flag, C_DELETED | C_NOEXISTS)) {
+ return EBUSY;
+ }
+
+ /*
+ * Verify the source file is not in use by anyone besides us.
+ *
+ * This function is typically invoked by a namespace handler
+ * process responding to a temporarily stalled system call.
+ * The FD that it is working off of is opened O_EVTONLY, so
+ * it really has no active usecounts (the kusecount from O_EVTONLY
+ * is subtracted from the total usecounts).
+ *
+ * As a result, we shouldn't have any active usecounts against
+ * this vnode when we go to check it below.
+ */
+ if (vnode_isinuse(from_cp->c_vp, 0))
+ return EBUSY;
+
+ if (include_rsrc && from_cp->c_rsrc_vp) {
+ if (vnode_isinuse(from_cp->c_rsrc_vp, 0))
+ return EBUSY;
+
+ /*
+ * In the code below, if the destination file doesn't have a
+ * c_rsrcfork then we don't create it which means we we cannot
+ * transfer the ff_invalidranges and cf_vblocks fields. These
+ * shouldn't be set because we flush the resource fork before
+ * calling this function but there is a tiny window when we
+ * did not have any locks...
+ */
+ if (!to_cp->c_rsrcfork
+ && (!TAILQ_EMPTY(&from_cp->c_rsrcfork->ff_invalidranges)
+ || from_cp->c_rsrcfork->ff_unallocblocks)) {
+ /*
+ * The file isn't really busy now but something did slip
+ * in and tinker with the file while we didn't have any
+ * locks, so this is the most meaningful return code for
+ * the caller.
+ */
+ return EBUSY;
+ }
+ }
+
+ // Check the destination file is empty
+ if (to_cp->c_datafork->ff_blocks
+ || to_cp->c_datafork->ff_size
+ || (include_rsrc
+ && (to_cp->c_blocks
+ || (to_cp->c_rsrcfork && to_cp->c_rsrcfork->ff_size)))) {
+ return EFBIG;
+ }
+
+ if ((error = hfs_start_transaction (hfsmp)))
+ return error;
+
+ lockflags = hfs_systemfile_lock(hfsmp, SFL_CATALOG | SFL_EXTENTS | SFL_ATTRIBUTE,
+ HFS_EXCLUSIVE_LOCK);
+
+ // filefork_t is 128 bytes which should be OK
+ filefork_t rfork_buf, *from_rfork = NULL;
+
+ if (include_rsrc) {
+ from_rfork = from_cp->c_rsrcfork;
+
+ /*
+ * Creating resource fork vnodes is expensive, so just get get
+ * the fork data if we need it.
+ */
+ if (!from_rfork && hfs_has_rsrc(from_cp)) {
+ from_rfork = &rfork_buf;
+
+ from_rfork->ff_cp = from_cp;
+ TAILQ_INIT(&from_rfork->ff_invalidranges);
+
+ error = cat_idlookup(hfsmp, from_cp->c_fileid, 0, 1, NULL, NULL,
+ &from_rfork->ff_data);
+
+ if (error)
+ goto exit;
+ }
+ }
+
+ /*
+ * From here on, any failures mean that we might be leaving things
+ * in a weird or inconsistent state. Ideally, we should back out
+ * all the changes, but to do that properly we need to fix
+ * MoveData. We'll save fixing that for another time. For now,
+ * just return EIO in all cases to the caller so that they know.
+ */
+ return_EIO_on_error = true;
+
+ bool data_overflow_extents = overflow_extents(from_cp->c_datafork);
+
+ // Move the data fork
+ if ((error = hfs_move_fork (from_cp->c_datafork, from_cp,
+ to_cp->c_datafork, to_cp))) {
+ goto exit;
+ }
+
+ SET(from_cp->c_flag, C_NEED_DATA_SETSIZE);
+ SET(to_cp->c_flag, C_NEED_DATA_SETSIZE);
+
+ // We move the resource fork later
+
+ /*
+ * 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 (data_overflow_extents) {
+ if ((error = MoveData(hfsmp, from_cp->c_cnid, to_cp->c_cnid, 0)))
+ goto exit;
+ }
+
+ if (from_rfork && overflow_extents(from_rfork)) {
+ if ((error = MoveData(hfsmp, from_cp->c_cnid, to_cp->c_cnid, 1)))
+ goto exit;
+ }
+
+ // Touch times
+ from_cp->c_touch_acctime = TRUE;
+ from_cp->c_touch_chgtime = TRUE;
+ from_cp->c_touch_modtime = TRUE;
+ hfs_touchtimes(hfsmp, from_cp);
+
+ to_cp->c_touch_acctime = TRUE;
+ to_cp->c_touch_chgtime = TRUE;
+ to_cp->c_touch_modtime = TRUE;
+ hfs_touchtimes(hfsmp, to_cp);
+
+ struct cat_fork dfork_buf;
+ const struct cat_fork *dfork, *rfork;
+
+ dfork = hfs_prepare_fork_for_update(to_cp->c_datafork, NULL,
+ &dfork_buf, hfsmp->blockSize);
+ rfork = hfs_prepare_fork_for_update(from_rfork, NULL,
+ &rfork_buf.ff_data, hfsmp->blockSize);
+
+ // Update the catalog nodes, to_cp first
+ if ((error = cat_update(hfsmp, &to_cp->c_desc, &to_cp->c_attr,
+ dfork, rfork))) {
+ goto exit;