+#if CONFIG_FIRMLINKS
+errno_t
+vnode_setasfirmlink(vnode_t vp, vnode_t target_vp)
+{
+ int error = 0;
+ vnode_t old_target_vp = NULLVP;
+ vnode_t old_target_vp_v_fmlink = NULLVP;
+ kauth_cred_t target_vp_cred = NULL;
+ kauth_cred_t old_target_vp_cred = NULL;
+
+ if (!vp) {
+ return EINVAL;
+ }
+
+ if (target_vp) {
+ if (vp->v_fmlink == target_vp) { /* Will be checked again under the name cache lock */
+ return 0;
+ }
+
+ /*
+ * Firmlink source and target will take both a usecount
+ * and kusecount on each other.
+ */
+ if ((error = vnode_ref_ext(target_vp, O_EVTONLY, VNODE_REF_FORCE))) {
+ return error;
+ }
+
+ if ((error = vnode_ref_ext(vp, O_EVTONLY, VNODE_REF_FORCE))) {
+ vnode_rele_ext(target_vp, O_EVTONLY, 1);
+ return error;
+ }
+ }
+
+ NAME_CACHE_LOCK();
+
+ old_target_vp = vp->v_fmlink;
+ if (target_vp && (target_vp == old_target_vp)) {
+ NAME_CACHE_UNLOCK();
+ return 0;
+ }
+ vp->v_fmlink = target_vp;
+
+ vnode_lock_spin(vp);
+ vp->v_flag &= ~VFMLINKTARGET;
+ vnode_unlock(vp);
+
+ if (target_vp) {
+ target_vp->v_fmlink = vp;
+ vnode_lock_spin(target_vp);
+ target_vp->v_flag |= VFMLINKTARGET;
+ vnode_unlock(target_vp);
+ cache_purge_locked(vp, &target_vp_cred);
+ }
+
+ if (old_target_vp) {
+ old_target_vp_v_fmlink = old_target_vp->v_fmlink;
+ old_target_vp->v_fmlink = NULLVP;
+ vnode_lock_spin(old_target_vp);
+ old_target_vp->v_flag &= ~VFMLINKTARGET;
+ vnode_unlock(old_target_vp);
+ cache_purge_locked(vp, &old_target_vp_cred);
+ }
+
+ NAME_CACHE_UNLOCK();
+
+ if (target_vp_cred && IS_VALID_CRED(target_vp_cred)) {
+ kauth_cred_unref(&target_vp_cred);
+ }
+
+ if (old_target_vp) {
+ if (old_target_vp_cred && IS_VALID_CRED(old_target_vp_cred)) {
+ kauth_cred_unref(&old_target_vp_cred);
+ }
+
+ vnode_rele_ext(old_target_vp, O_EVTONLY, 1);
+ if (old_target_vp_v_fmlink) {
+ vnode_rele_ext(old_target_vp_v_fmlink, O_EVTONLY, 1);
+ }
+ }
+
+ return 0;
+}
+
+errno_t
+vnode_getfirmlink(vnode_t vp, vnode_t *target_vp)
+{
+ int error;
+
+ if (!vp->v_fmlink) {
+ return ENODEV;
+ }
+
+ NAME_CACHE_LOCK_SHARED();
+ if (vp->v_fmlink && !(vp->v_flag & VFMLINKTARGET) &&
+ (vnode_get(vp->v_fmlink) == 0)) {
+ vnode_t tvp = vp->v_fmlink;
+
+ vnode_lock_spin(tvp);
+ if (tvp->v_lflag & (VL_TERMINATE | VL_DEAD)) {
+ vnode_unlock(tvp);
+ NAME_CACHE_UNLOCK();
+ vnode_put(tvp);
+ return ENOENT;
+ }
+ if (!(tvp->v_flag & VFMLINKTARGET)) {
+ panic("firmlink target for vnode %p does not have flag set", vp);
+ }
+ vnode_unlock(tvp);
+ *target_vp = tvp;
+ error = 0;
+ } else {
+ *target_vp = NULLVP;
+ error = ENODEV;
+ }
+ NAME_CACHE_UNLOCK();
+ return error;
+}
+
+#else /* CONFIG_FIRMLINKS */
+
+errno_t
+vnode_setasfirmlink(__unused vnode_t vp, __unused vnode_t src_vp)
+{
+ return ENOTSUP;
+}
+
+errno_t
+vnode_getfirmlink(__unused vnode_t vp, __unused vnode_t *target_vp)
+{
+ return ENOTSUP;
+}
+
+#endif