+/*
+ * Given a vnode in a union mount, traverse to the equivalent
+ * vnode in the underlying mount.
+ */
+int
+lookup_traverse_union(vnode_t dvp, vnode_t *new_dvp, vfs_context_t ctx)
+{
+ char *path = NULL, *pp;
+ const char *name, *np;
+ int len;
+ int error = 0;
+ struct nameidata nd;
+ vnode_t vp = dvp;
+
+ *new_dvp = NULL;
+
+ if (vp && vp->v_flag & VROOT) {
+ *new_dvp = vp->v_mount->mnt_vnodecovered;
+ if (vnode_getwithref(*new_dvp))
+ return ENOENT;
+ return 0;
+ }
+
+ path = (char *) kalloc(MAXPATHLEN);
+ if (path == NULL) {
+ error = ENOMEM;
+ goto done;
+ }
+
+ /*
+ * Walk back up to the mountpoint following the
+ * v_parent chain and build a slash-separated path.
+ * Then lookup that path starting with the covered vnode.
+ */
+ pp = path + (MAXPATHLEN - 1);
+ *pp = '\0';
+
+ while (1) {
+ name = vnode_getname(vp);
+ if (name == NULL) {
+ printf("lookup_traverse_union: null parent name: .%s\n", pp);
+ error = ENOENT;
+ goto done;
+ }
+ len = strlen(name);
+ if ((len + 1) > (pp - path)) { // Enough space for this name ?
+ error = ENAMETOOLONG;
+ vnode_putname(name);
+ goto done;
+ }
+ for (np = name + len; len > 0; len--) // Copy name backwards
+ *--pp = *--np;
+ vnode_putname(name);
+ vp = vp->v_parent;
+ if (vp == NULLVP || vp->v_flag & VROOT)
+ break;
+ *--pp = '/';
+ }
+
+ /* Evaluate the path in the underlying mount */
+ NDINIT(&nd, LOOKUP, OP_LOOKUP, USEDVP, UIO_SYSSPACE, CAST_USER_ADDR_T(pp), ctx);
+ nd.ni_dvp = dvp->v_mount->mnt_vnodecovered;
+ error = namei(&nd);
+ if (error == 0)
+ *new_dvp = nd.ni_vp;
+ nameidone(&nd);
+done:
+ if (path)
+ kfree(path, MAXPATHLEN);
+ return error;
+}
+