+/*
+ * Check ordering of two cnodes. Return true if they are are in-order.
+ */
+static int
+hfs_isordered(struct cnode *cp1, struct cnode *cp2)
+{
+ if (cp1 == cp2)
+ return (0);
+ if (cp1 == NULL || cp2 == (struct cnode *)0xffffffff)
+ return (1);
+ if (cp2 == NULL || cp1 == (struct cnode *)0xffffffff)
+ return (0);
+ /*
+ * Locking order is cnode address order.
+ */
+ return (cp1 < cp2);
+}
+
+/*
+ * Acquire 4 cnode locks.
+ * - locked in cnode address order (lesser address first).
+ * - all or none of the locks are taken
+ * - only one lock taken per cnode (dup cnodes are skipped)
+ * - some of the cnode pointers may be null
+ */
+int
+hfs_lockfour(struct cnode *cp1, struct cnode *cp2, struct cnode *cp3,
+ struct cnode *cp4, enum hfs_locktype locktype, struct cnode **error_cnode)
+{
+ struct cnode * a[3];
+ struct cnode * b[3];
+ struct cnode * list[4];
+ struct cnode * tmp;
+ int i, j, k;
+ int error;
+ if (error_cnode) {
+ *error_cnode = NULL;
+ }
+
+ if (hfs_isordered(cp1, cp2)) {
+ a[0] = cp1; a[1] = cp2;
+ } else {
+ a[0] = cp2; a[1] = cp1;
+ }
+ if (hfs_isordered(cp3, cp4)) {
+ b[0] = cp3; b[1] = cp4;
+ } else {
+ b[0] = cp4; b[1] = cp3;
+ }
+ a[2] = (struct cnode *)0xffffffff; /* sentinel value */
+ b[2] = (struct cnode *)0xffffffff; /* sentinel value */
+
+ /*
+ * Build the lock list, skipping over duplicates
+ */
+ for (i = 0, j = 0, k = 0; (i < 2 || j < 2); ) {
+ tmp = hfs_isordered(a[i], b[j]) ? a[i++] : b[j++];
+ if (k == 0 || tmp != list[k-1])
+ list[k++] = tmp;
+ }
+
+ /*
+ * Now we can lock using list[0 - k].
+ * Skip over NULL entries.
+ */
+ for (i = 0; i < k; ++i) {
+ if (list[i])
+ if ((error = hfs_lock(list[i], locktype, HFS_LOCK_DEFAULT))) {
+ /* Only stuff error_cnode if requested */
+ if (error_cnode) {
+ *error_cnode = list[i];
+ }
+ /* Drop any locks we acquired. */
+ while (--i >= 0) {
+ if (list[i])
+ hfs_unlock(list[i]);
+ }
+ return (error);
+ }
+ }
+ return (0);
+}
+
+
+/*
+ * Unlock a cnode.
+ */
+void
+hfs_unlock(struct cnode *cp)
+{
+ vnode_t rvp = NULLVP;
+ vnode_t vp = NULLVP;
+ u_int32_t c_flag;
+
+ /*
+ * Only the extents and bitmap file's support lock recursion.
+ */
+ if ((cp->c_fileid == kHFSExtentsFileID) ||
+ (cp->c_fileid == kHFSAllocationFileID)) {
+ if (--cp->c_syslockcount > 0) {
+ return;
+ }
+ }
+
+ const thread_t thread = current_thread();
+
+ if (cp->c_lockowner == thread) {
+ c_flag = cp->c_flag;
+
+ // If we have the truncate lock, we must defer the puts
+ if (cp->c_truncatelockowner == thread) {
+ if (ISSET(c_flag, C_NEED_DVNODE_PUT)
+ && !cp->c_need_dvnode_put_after_truncate_unlock) {
+ CLR(c_flag, C_NEED_DVNODE_PUT);
+ cp->c_need_dvnode_put_after_truncate_unlock = true;
+ }
+ if (ISSET(c_flag, C_NEED_RVNODE_PUT)
+ && !cp->c_need_rvnode_put_after_truncate_unlock) {
+ CLR(c_flag, C_NEED_RVNODE_PUT);
+ cp->c_need_rvnode_put_after_truncate_unlock = true;
+ }
+ }
+
+ CLR(cp->c_flag, (C_NEED_DATA_SETSIZE | C_NEED_RSRC_SETSIZE
+ | C_NEED_DVNODE_PUT | C_NEED_RVNODE_PUT));
+
+ if (c_flag & (C_NEED_DVNODE_PUT | C_NEED_DATA_SETSIZE)) {
+ vp = cp->c_vp;
+ }
+ if (c_flag & (C_NEED_RVNODE_PUT | C_NEED_RSRC_SETSIZE)) {
+ rvp = cp->c_rsrc_vp;
+ }
+
+ cp->c_lockowner = NULL;
+ lck_rw_unlock_exclusive(&cp->c_rwlock);
+ } else {
+ lck_rw_unlock_shared(&cp->c_rwlock);
+ }
+
+ /* Perform any vnode post processing after cnode lock is dropped. */
+ if (vp) {
+ if (c_flag & C_NEED_DATA_SETSIZE) {
+ ubc_setsize(vp, VTOF(vp)->ff_size);
+#if HFS_COMPRESSION
+ /*
+ * If this is a compressed file, we need to reset the
+ * compression state. We will have set the size to zero
+ * above and it will get fixed up later (in exactly the
+ * same way that new vnodes are fixed up). Note that we
+ * should only be able to get here if the truncate lock is
+ * held exclusively and so we do the reset when that's
+ * unlocked.
+ */
+ decmpfs_cnode *dp = VTOCMP(vp);
+ if (dp && decmpfs_cnode_get_vnode_state(dp) != FILE_TYPE_UNKNOWN)
+ cp->c_need_decmpfs_reset = true;
+#endif
+ }
+ if (c_flag & C_NEED_DVNODE_PUT)
+ vnode_put(vp);
+ }
+ if (rvp) {
+ if (c_flag & C_NEED_RSRC_SETSIZE)
+ ubc_setsize(rvp, VTOF(rvp)->ff_size);
+ if (c_flag & C_NEED_RVNODE_PUT)
+ vnode_put(rvp);
+ }
+}
+
+/*
+ * Unlock a pair of cnodes.
+ */
+void
+hfs_unlockpair(struct cnode *cp1, struct cnode *cp2)
+{
+ hfs_unlock(cp1);
+ if (cp2 != cp1)
+ hfs_unlock(cp2);
+}
+
+/*
+ * Unlock a group of cnodes.
+ */
+void
+hfs_unlockfour(struct cnode *cp1, struct cnode *cp2, struct cnode *cp3, struct cnode *cp4)
+{
+ struct cnode * list[4];
+ int i, k = 0;
+
+ if (cp1) {
+ hfs_unlock(cp1);
+ list[k++] = cp1;
+ }
+ if (cp2) {
+ for (i = 0; i < k; ++i) {
+ if (list[i] == cp2)
+ goto skip1;
+ }
+ hfs_unlock(cp2);
+ list[k++] = cp2;
+ }
+skip1:
+ if (cp3) {
+ for (i = 0; i < k; ++i) {
+ if (list[i] == cp3)
+ goto skip2;
+ }
+ hfs_unlock(cp3);
+ list[k++] = cp3;
+ }
+skip2:
+ if (cp4) {
+ for (i = 0; i < k; ++i) {
+ if (list[i] == cp4)
+ return;
+ }
+ hfs_unlock(cp4);
+ }
+}
+
+
+/*
+ * Protect a cnode against a truncation.
+ *
+ * Used mainly by read/write since they don't hold the
+ * cnode lock across calls to the cluster layer.
+ *
+ * The process doing a truncation must take the lock
+ * exclusive. The read/write processes can take it
+ * shared. The locktype argument is the same as supplied to
+ * hfs_lock.
+ */
+void
+hfs_lock_truncate(struct cnode *cp, enum hfs_locktype locktype, enum hfs_lockflags flags)
+{
+ thread_t thread = current_thread();
+
+ if (cp->c_truncatelockowner == thread) {
+ /*
+ * Ignore grabbing the lock if it the current thread already
+ * holds exclusive lock.
+ *
+ * This is needed on the hfs_vnop_pagein path where we need to ensure
+ * the file does not change sizes while we are paging in. However,
+ * we may already hold the lock exclusive due to another
+ * VNOP from earlier in the call stack. So if we already hold
+ * the truncate lock exclusive, allow it to proceed, but ONLY if
+ * it's in the recursive case.
+ */
+ if ((flags & HFS_LOCK_SKIP_IF_EXCLUSIVE) == 0) {
+ panic("hfs_lock_truncate: cnode %p locked!", cp);
+ }
+ } else if (locktype == HFS_SHARED_LOCK) {
+ lck_rw_lock_shared(&cp->c_truncatelock);
+ cp->c_truncatelockowner = HFS_SHARED_OWNER;
+ } else { /* HFS_EXCLUSIVE_LOCK */
+ lck_rw_lock_exclusive(&cp->c_truncatelock);
+ cp->c_truncatelockowner = thread;
+ }
+}
+
+bool hfs_truncate_lock_upgrade(struct cnode *cp)
+{
+ assert(cp->c_truncatelockowner == HFS_SHARED_OWNER);
+ if (!lck_rw_lock_shared_to_exclusive(&cp->c_truncatelock))
+ return false;
+ cp->c_truncatelockowner = current_thread();
+ return true;
+}
+
+void hfs_truncate_lock_downgrade(struct cnode *cp)
+{
+ assert(cp->c_truncatelockowner == current_thread());
+ lck_rw_lock_exclusive_to_shared(&cp->c_truncatelock);
+ cp->c_truncatelockowner = HFS_SHARED_OWNER;
+}
+
+/*
+ * Attempt to get the truncate lock. If it cannot be acquired, error out.
+ * This function is needed in the degenerate hfs_vnop_pagein during force unmount
+ * case. To prevent deadlocks while a VM copy object is moving pages, HFS vnop pagein will
+ * temporarily need to disable V2 semantics.
+ */
+int hfs_try_trunclock (struct cnode *cp, enum hfs_locktype locktype, enum hfs_lockflags flags)
+{
+ thread_t thread = current_thread();
+ boolean_t didlock = false;
+
+ if (cp->c_truncatelockowner == thread) {
+ /*
+ * Ignore grabbing the lock if the current thread already
+ * holds exclusive lock.
+ *
+ * This is needed on the hfs_vnop_pagein path where we need to ensure
+ * the file does not change sizes while we are paging in. However,
+ * we may already hold the lock exclusive due to another
+ * VNOP from earlier in the call stack. So if we already hold
+ * the truncate lock exclusive, allow it to proceed, but ONLY if
+ * it's in the recursive case.
+ */
+ if ((flags & HFS_LOCK_SKIP_IF_EXCLUSIVE) == 0) {
+ panic("hfs_lock_truncate: cnode %p locked!", cp);
+ }
+ } else if (locktype == HFS_SHARED_LOCK) {
+ didlock = lck_rw_try_lock(&cp->c_truncatelock, LCK_RW_TYPE_SHARED);
+ if (didlock) {
+ cp->c_truncatelockowner = HFS_SHARED_OWNER;
+ }
+ } else { /* HFS_EXCLUSIVE_LOCK */
+ didlock = lck_rw_try_lock (&cp->c_truncatelock, LCK_RW_TYPE_EXCLUSIVE);
+ if (didlock) {
+ cp->c_truncatelockowner = thread;
+ }
+ }
+
+ return didlock;
+}
+
+
+/*
+ * Unlock the truncate lock, which protects against size changes.
+ *
+ * If HFS_LOCK_SKIP_IF_EXCLUSIVE flag was set, it means that a previous
+ * hfs_lock_truncate() might have skipped grabbing a lock because
+ * the current thread was already holding the lock exclusive and
+ * we may need to return from this function without actually unlocking
+ * the truncate lock.
+ */
+void
+hfs_unlock_truncate(struct cnode *cp, enum hfs_lockflags flags)
+{
+ thread_t thread = current_thread();
+
+ /*
+ * If HFS_LOCK_SKIP_IF_EXCLUSIVE is set in the flags AND the current
+ * lock owner of the truncate lock is our current thread, then
+ * we must have skipped taking the lock earlier by in
+ * hfs_lock_truncate() by setting HFS_LOCK_SKIP_IF_EXCLUSIVE in the
+ * flags (as the current thread was current lock owner).
+ *
+ * If HFS_LOCK_SKIP_IF_EXCLUSIVE is not set (most of the time) then
+ * we check the lockowner field to infer whether the lock was taken
+ * exclusively or shared in order to know what underlying lock
+ * routine to call.
+ */
+ if (flags & HFS_LOCK_SKIP_IF_EXCLUSIVE) {
+ if (cp->c_truncatelockowner == thread) {
+ return;
+ }
+ }
+
+ /* HFS_LOCK_EXCLUSIVE */
+ if (thread == cp->c_truncatelockowner) {
+ vnode_t vp = NULL, rvp = NULL;
+
+ /*
+ * If there are pending set sizes, the cnode lock should be dropped
+ * first.
+ */
+#if DEBUG
+ assert(!(cp->c_lockowner == thread
+ && ISSET(cp->c_flag, C_NEED_DATA_SETSIZE | C_NEED_RSRC_SETSIZE)));
+#elif DEVELOPMENT
+ if (cp->c_lockowner == thread
+ && ISSET(cp->c_flag, C_NEED_DATA_SETSIZE | C_NEED_RSRC_SETSIZE)) {
+ printf("hfs: hfs_unlock_truncate called with C_NEED_DATA/RSRC_SETSIZE set (caller: 0x%llx)\n",
+ (uint64_t)VM_KERNEL_UNSLIDE(__builtin_return_address(0)));
+ }
+#endif
+
+ if (cp->c_need_dvnode_put_after_truncate_unlock) {
+ vp = cp->c_vp;
+ cp->c_need_dvnode_put_after_truncate_unlock = false;
+ }
+ if (cp->c_need_rvnode_put_after_truncate_unlock) {
+ rvp = cp->c_rsrc_vp;
+ cp->c_need_rvnode_put_after_truncate_unlock = false;
+ }
+
+#if HFS_COMPRESSION
+ bool reset_decmpfs = cp->c_need_decmpfs_reset;
+ cp->c_need_decmpfs_reset = false;
+#endif
+
+ cp->c_truncatelockowner = NULL;
+ lck_rw_unlock_exclusive(&cp->c_truncatelock);
+
+#if HFS_COMPRESSION
+ if (reset_decmpfs) {
+ decmpfs_cnode *dp = cp->c_decmp;
+ if (dp && decmpfs_cnode_get_vnode_state(dp) != FILE_TYPE_UNKNOWN)
+ decmpfs_cnode_set_vnode_state(dp, FILE_TYPE_UNKNOWN, 0);
+ }
+#endif
+
+ // Do the puts now
+ if (vp)
+ vnode_put(vp);
+ if (rvp)
+ vnode_put(rvp);
+ } else { /* HFS_LOCK_SHARED */
+ lck_rw_unlock_shared(&cp->c_truncatelock);
+ }
+}