/* * Return true if the supplied vnode has a sub-directory exported. */ int has_visible(struct exportinfo *exi, vnode_t *vp) { struct exp_visible *visp; fid_t fid; bool_t vp_is_exported; vp_is_exported = VN_CMP(vp, exi->exi_vp); /* * An exported root vnode has a sub-dir shared if it has a visible list. * i.e. if it does not have a visible list, then there is no node in * this filesystem leads to any other shared node. */ if (vp_is_exported && (vp->v_flag & VROOT)) return (exi->exi_visible ? 1 : 0); /* * Only the exportinfo of a fs root node may have a visible list. * Either it is a pseudo root node, or a real exported root node. */ exi = get_root_export(exi); if (!exi->exi_visible) return (0); /* Get the fid of the vnode */ bzero(&fid, sizeof (fid)); fid.fid_len = MAXFIDSZ; if (vop_fid_pseudo(vp, &fid) != 0) { return (0); } /* * See if vp is in the visible list of the root node exportinfo. */ for (visp = exi->exi_visible; visp; visp = visp->vis_next) { if (EQFID(&fid, &visp->vis_fid)) { /* * If vp is an exported non-root node with only 1 path * count (for itself), it indicates no sub-dir shared * using this vp as a path. */ if (vp_is_exported && visp->vis_count < 2) break; return (1); } } return (0); }
/* * Returns true if the supplied vnode is the * directory of an export point. */ int nfs_exported(struct exportinfo *exi, vnode_t *vp) { struct exp_visible *visp; fid_t fid; /* * First check to see if vp is the export root * This check required for the case of lookup .. * where .. is a V_ROOT vnode and a pseudo exportroot. * Pseudo export root objects do not have an entry * in the visible list even though every V_ROOT * pseudonode is visible. It is safe to compare * vp here because pseudo_exportfs put a hold on * it when exi_vp was initialized. * * Note: VN_CMP() won't match for LOFS shares, but they're * handled below w/EQFID/EQFSID. */ if (VN_CMP(vp, exi->exi_vp)) return (1); /* Get the fid of the vnode */ bzero(&fid, sizeof (fid)); fid.fid_len = MAXFIDSZ; if (vop_fid_pseudo(vp, &fid) != 0) return (0); if (EQFID(&fid, &exi->exi_fid) && EQFSID(&vp->v_vfsp->vfs_fsid, &exi->exi_fsid)) { return (1); } /* See if it matches any fid in the visible list */ for (visp = exi->exi_visible; visp; visp = visp->vis_next) { if (EQFID(&fid, &visp->vis_fid)) return (visp->vis_exported); } return (0); }
/* * See if vp is in use by the accounting system on any zone. This does a deep * comparison of vnodes such that a file and a lofs "shadow" node of it will * appear to be the same. * * If 'compare_vfs' is true, the function will do a comparison of vfs_t's * instead (ie, is the vfs_t on which the vnode resides in use by the * accounting system in any zone). * * Returns 1 if found (in use), 0 otherwise. */ static int acct_find(vnode_t *vp, boolean_t compare_vfs) { struct acct_globals *ag; vnode_t *realvp; ASSERT(MUTEX_HELD(&acct_list_lock)); ASSERT(vp != NULL); if (VOP_REALVP(vp, &realvp, NULL)) realvp = vp; for (ag = list_head(&acct_list); ag != NULL; ag = list_next(&acct_list, ag)) { vnode_t *racctvp; boolean_t found = B_FALSE; mutex_enter(&ag->aclock); if (ag->acctvp == NULL) { mutex_exit(&ag->aclock); continue; } if (VOP_REALVP(ag->acctvp, &racctvp, NULL)) racctvp = ag->acctvp; if (compare_vfs) { if (racctvp->v_vfsp == realvp->v_vfsp) found = B_TRUE; } else { if (VN_CMP(realvp, racctvp)) found = B_TRUE; } mutex_exit(&ag->aclock); if (found) return (1); } return (0); }
/* * Returns true if the supplied vnode is visible * in this export. If vnode is visible, return * vis_exported in expseudo. */ int nfs_visible(struct exportinfo *exi, vnode_t *vp, int *expseudo) { struct exp_visible *visp; fid_t fid; /* * First check to see if vp is export root. * * A pseudo export root can never be exported * (it would be a real export then); however, * it is always visible. If a pseudo root object * was exported by server admin, then the entire * pseudo exportinfo (and all visible entries) would * be destroyed. A pseudo exportinfo only exists * to provide access to real (descendant) export(s). * * Previously, rootdir was special cased here; however, * the export root special case handles the rootdir * case also. */ if (VN_CMP(vp, exi->exi_vp)) { *expseudo = 0; return (1); } /* * Only a PSEUDO node has a visible list or an exported VROOT * node may have a visible list. */ if (! PSEUDO(exi)) exi = get_root_export(exi); /* Get the fid of the vnode */ bzero(&fid, sizeof (fid)); fid.fid_len = MAXFIDSZ; if (vop_fid_pseudo(vp, &fid) != 0) { *expseudo = 0; return (0); } /* * We can't trust VN_CMP() above because of LOFS. * Even though VOP_CMP will do the right thing for LOFS * objects, VN_CMP will short circuit out early when the * vnode ops ptrs are different. Just in case we're dealing * with LOFS, compare exi_fid/fsid here. * * expseudo is not set because this is not an export */ if (EQFID(&exi->exi_fid, &fid) && EQFSID(&exi->exi_fsid, &vp->v_vfsp->vfs_fsid)) { *expseudo = 0; return (1); } /* See if it matches any fid in the visible list */ for (visp = exi->exi_visible; visp; visp = visp->vis_next) { if (EQFID(&fid, &visp->vis_fid)) { *expseudo = visp->vis_exported; return (1); } } *expseudo = 0; return (0); }
/* * This function checks the path to a new export to * check whether all the pathname components are * exported. It works by climbing the file tree one * component at a time via "..", crossing mountpoints * if necessary until an export entry is found, or the * system root is reached. * * If an unexported mountpoint is found, then * a new pseudo export is added and the pathname from * the mountpoint down to the export is added to the * visible list for the new pseudo export. If an existing * pseudo export is found, then the pathname is added * to its visible list. * * Note that there's some tests for exportdir. * The exportinfo entry that's passed as a parameter * is that of the real export and exportdir is set * for this case. * * Here is an example of a possible setup: * * () - a new fs; fs mount point * EXPORT - a real exported node * PSEUDO - a pseudo node * vis - visible list * f# - security flavor# * (f#) - security flavor# propagated from its descendents * "" - covered vnode * * * / * | * (a) PSEUDO (f1,f2) * | vis: b,b,"c","n" * | * b * ---------|------------------ * | | * (c) EXPORT,f1(f2) (n) PSEUDO (f1,f2) * | vis: "e","d" | vis: m,m,,p,q,"o" * | | * ------------------ ------------------- * | | | | | * (d) (e) f m EXPORT,f1(f2) p * EXPORT EXPORT | | * f1 f2 | | * | | | * j (o) EXPORT,f2 q EXPORT f2 * */ int treeclimb_export(struct exportinfo *exip) { vnode_t *dvp, *vp; fid_t fid; int error; int exportdir; struct exportinfo *exi = NULL; struct exportinfo *new_exi = exip; struct exp_visible *visp; struct exp_visible *vis_head = NULL; struct vattr va; treenode_t *tree_head = NULL; ASSERT(RW_WRITE_HELD(&exported_lock)); vp = exip->exi_vp; VN_HOLD(vp); exportdir = 1; for (;;) { bzero(&fid, sizeof (fid)); fid.fid_len = MAXFIDSZ; error = vop_fid_pseudo(vp, &fid); if (error) break; if (! exportdir) { /* * Check if this exportroot is a VROOT dir. If so, * then attach the pseudonodes. If not, then * continue .. traversal until we hit a VROOT * export (pseudo or real). */ exi = checkexport4(&vp->v_vfsp->vfs_fsid, &fid, vp); if (exi != NULL && vp->v_flag & VROOT) { /* * Found an export info * * Extend the list of visible * directories whether it's a pseudo * or a real export. */ more_visible(exi, tree_head); break; /* and climb no further */ } } /* * If at the root of the filesystem, need * to traverse across the mountpoint * and continue the climb on the mounted-on * filesystem. */ if (vp->v_flag & VROOT) { if (! exportdir) { /* * Found the root directory of a filesystem * that isn't exported. Need to export * this as a pseudo export so that an NFS v4 * client can do lookups in it. */ new_exi = pseudo_exportfs(vp, &fid, vis_head, NULL); vis_head = NULL; } if (VN_CMP(vp, rootdir)) { /* at system root */ /* * If sharing "/", new_exi is shared exportinfo * (exip). Otherwise, new_exi is exportinfo * created in pseudo_exportfs() above. */ ns_root = tree_prepend_node(tree_head, 0, new_exi); break; } vp = untraverse(vp); exportdir = 0; continue; } /* * Do a getattr to obtain the nodeid (inode num) * for this vnode. */ va.va_mask = AT_NODEID; error = VOP_GETATTR(vp, &va, 0, CRED(), NULL); if (error) break; /* * Add this directory fid to visible list */ visp = kmem_alloc(sizeof (*visp), KM_SLEEP); VN_HOLD(vp); visp->vis_vp = vp; visp->vis_fid = fid; /* structure copy */ visp->vis_ino = va.va_nodeid; visp->vis_count = 1; visp->vis_exported = exportdir; visp->vis_secinfo = NULL; visp->vis_seccnt = 0; visp->vis_next = vis_head; vis_head = visp; /* * Will set treenode's pointer to exportinfo to * 1. shared exportinfo (exip) - if first visit here * 2. freshly allocated pseudo export (if any) * 3. null otherwise */ tree_head = tree_prepend_node(tree_head, visp, new_exi); new_exi = NULL; /* * Now, do a ".." to find parent dir of vp. */ error = VOP_LOOKUP(vp, "..", &dvp, NULL, 0, NULL, CRED(), NULL, NULL, NULL); if (error == ENOTDIR && exportdir) { dvp = exip->exi_dvp; ASSERT(dvp != NULL); VN_HOLD(dvp); error = 0; } if (error) break; exportdir = 0; VN_RELE(vp); vp = dvp; } VN_RELE(vp); /* * We can have set error due to error in: * 1. vop_fid_pseudo() * 2. VOP_GETATTR() * 3. VOP_LOOKUP() * We must free pseudo exportinfos, visibles and treenodes. * Visibles are referenced from treenode_t::tree_vis and * exportinfo_t::exi_visible. To avoid double freeing, only * exi_visible pointer is used, via exi_rele(), for the clean-up. */ if (error) { /* Free unconnected visibles, if there are any. */ if (vis_head) free_visible(vis_head); /* Connect unconnected exportinfo, if there is any. */ if (new_exi && new_exi != exip) tree_head = tree_prepend_node(tree_head, 0, new_exi); while (tree_head) { treenode_t *t2 = tree_head; exportinfo_t *e = tree_head->tree_exi; /* exip will be freed in exportfs() */ if (e && e != exip) { exp_kstats_delete(e->exi_kstats); avl_remove(&exi_id_tree, e); export_unlink(e); exi_rele(e); } tree_head = tree_head->tree_child_first; kmem_free(t2, sizeof (*t2)); } } return (error); }
static int chdirec(vnode_t *vp, int ischroot, int do_traverse) { int error; vnode_t *oldvp; proc_t *pp = curproc; vnode_t **vpp; refstr_t *cwd; int newcwd = 1; if (vp->v_type != VDIR) { error = ENOTDIR; goto bad; } if (error = VOP_ACCESS(vp, VEXEC, 0, CRED(), NULL)) goto bad; /* * The VOP_ACCESS() may have covered 'vp' with a new filesystem, * if 'vp' is an autoFS vnode. Traverse the mountpoint so * that we don't end up with a covered current directory. */ if (vn_mountedvfs(vp) != NULL && do_traverse) { if (error = traverse(&vp)) goto bad; } /* * Special chroot semantics: chroot is allowed if privileged * or if the target is really a loopback mount of the root (or * root of the zone) as determined by comparing dev and inode * numbers */ if (ischroot) { struct vattr tattr; struct vattr rattr; vnode_t *zonevp = curproc->p_zone->zone_rootvp; tattr.va_mask = AT_FSID|AT_NODEID; if (error = VOP_GETATTR(vp, &tattr, 0, CRED(), NULL)) goto bad; rattr.va_mask = AT_FSID|AT_NODEID; if (error = VOP_GETATTR(zonevp, &rattr, 0, CRED(), NULL)) goto bad; if ((tattr.va_fsid != rattr.va_fsid || tattr.va_nodeid != rattr.va_nodeid) && (error = secpolicy_chroot(CRED())) != 0) goto bad; vpp = &PTOU(pp)->u_rdir; } else { vpp = &PTOU(pp)->u_cdir; } if (audit_active) /* update abs cwd/root path see c2audit.c */ audit_chdirec(vp, vpp); mutex_enter(&pp->p_lock); /* * This bit of logic prevents us from overwriting u_cwd if we are * changing to the same directory. We set the cwd to NULL so that we * don't try to do the lookup on the next call to getcwd(). */ if (!ischroot && *vpp != NULL && vp != NULL && VN_CMP(*vpp, vp)) newcwd = 0; oldvp = *vpp; *vpp = vp; if ((cwd = PTOU(pp)->u_cwd) != NULL && newcwd) PTOU(pp)->u_cwd = NULL; mutex_exit(&pp->p_lock); if (cwd && newcwd) refstr_rele(cwd); if (oldvp) VN_RELE(oldvp); return (0); bad: VN_RELE(vp); return (error); }
/* * Given a directory, return the full, resolved path. This looks up "..", * searches for the given vnode in the parent, appends the component, etc. It * is used to implement vnodetopath() and getcwd() when the cached path fails. */ static int dirtopath(vnode_t *vrootp, vnode_t *vp, char *buf, size_t buflen, int flags, cred_t *cr) { pathname_t pn, rpn, emptypn; vnode_t *cmpvp, *pvp = NULL; vnode_t *startvp = vp; int err = 0, vprivs; size_t complen; char *dbuf; dirent64_t *dp; char *bufloc; size_t dlen = DIRENT64_RECLEN(MAXPATHLEN); refstr_t *mntpt; /* Operation only allowed on directories */ ASSERT(vp->v_type == VDIR); /* We must have at least enough space for "/" */ if (buflen < 2) return (ENAMETOOLONG); /* Start at end of string with terminating null */ bufloc = &buf[buflen - 1]; *bufloc = '\0'; pn_alloc(&pn); pn_alloc(&rpn); dbuf = kmem_alloc(dlen, KM_SLEEP); bzero(&emptypn, sizeof (emptypn)); /* * Begin with an additional reference on vp. This will be decremented * during the loop. */ VN_HOLD(vp); for (;;) { /* * Return if we've reached the root. If the buffer is empty, * return '/'. We explicitly don't use vn_compare(), since it * compares the real vnodes. A lofs mount of '/' would produce * incorrect results otherwise. */ if (VN_CMP(vrootp, vp)) { if (*bufloc == '\0') *--bufloc = '/'; break; } /* * If we've reached the VFS root, something has gone wrong. We * should have reached the root in the above check. The only * explantation is that 'vp' is not contained withing the given * root, in which case we return EPERM. */ if (VN_CMP(rootdir, vp)) { err = EPERM; goto out; } /* * Shortcut: see if this vnode is a mountpoint. If so, * grab the path information from the vfs_t. */ if (vp->v_flag & VROOT) { mntpt = vfs_getmntpoint(vp->v_vfsp); if ((err = pn_set(&pn, (char *)refstr_value(mntpt))) == 0) { refstr_rele(mntpt); rpn.pn_path = rpn.pn_buf; /* * Ensure the mountpoint still exists. */ VN_HOLD(vrootp); if (vrootp != rootdir) VN_HOLD(vrootp); if (lookuppnvp(&pn, &rpn, flags, NULL, &cmpvp, vrootp, vrootp, cr) == 0) { if (VN_CMP(vp, cmpvp)) { VN_RELE(cmpvp); complen = strlen(rpn.pn_path); bufloc -= complen; if (bufloc < buf) { err = ERANGE; goto out; } bcopy(rpn.pn_path, bufloc, complen); break; } else { VN_RELE(cmpvp); } } } else { refstr_rele(mntpt); } } /* * Shortcut: see if this vnode has correct v_path. If so, * we have the work done. */ mutex_enter(&vp->v_lock); if (vp->v_path != NULL) { if ((err = pn_set(&pn, vp->v_path)) == 0) { mutex_exit(&vp->v_lock); rpn.pn_path = rpn.pn_buf; /* * Ensure the v_path pointing to correct vnode */ VN_HOLD(vrootp); if (vrootp != rootdir) VN_HOLD(vrootp); if (lookuppnvp(&pn, &rpn, flags, NULL, &cmpvp, vrootp, vrootp, cr) == 0) { if (VN_CMP(vp, cmpvp)) { VN_RELE(cmpvp); complen = strlen(rpn.pn_path); bufloc -= complen; if (bufloc < buf) { err = ERANGE; goto out; } bcopy(rpn.pn_path, bufloc, complen); break; } else { VN_RELE(cmpvp); } } } else { mutex_exit(&vp->v_lock); } } else { mutex_exit(&vp->v_lock); } /* * Shortcuts failed, search for this vnode in its parent. If * this is a mountpoint, then get the vnode underneath. */ if (vp->v_flag & VROOT) vp = vn_under(vp); if ((err = VOP_LOOKUP(vp, "..", &pvp, &emptypn, 0, vrootp, cr, NULL, NULL, NULL)) != 0) goto out; /* * With extended attributes, it's possible for a directory to * have a parent that is a regular file. Check for that here. */ if (pvp->v_type != VDIR) { err = ENOTDIR; goto out; } /* * If this is true, something strange has happened. This is * only true if we are the root of a filesystem, which should * have been caught by the check above. */ if (VN_CMP(pvp, vp)) { err = ENOENT; goto out; } /* * Check if we have read and search privilege so, that * we can lookup the path in the directory */ vprivs = (flags & LOOKUP_CHECKREAD) ? VREAD | VEXEC : VEXEC; if ((err = VOP_ACCESS(pvp, vprivs, 0, cr, NULL)) != 0) { goto out; } /* * Try to obtain the path component from dnlc cache * before searching through the directory. */ if ((cmpvp = dnlc_reverse_lookup(vp, dbuf, dlen)) != NULL) { /* * If we got parent vnode as a result, * then the answered path is correct. */ if (VN_CMP(cmpvp, pvp)) { VN_RELE(cmpvp); complen = strlen(dbuf); bufloc -= complen; if (bufloc <= buf) { err = ENAMETOOLONG; goto out; } bcopy(dbuf, bufloc, complen); /* Prepend a slash to the current path */ *--bufloc = '/'; /* And continue with the next component */ VN_RELE(vp); vp = pvp; pvp = NULL; continue; } else { VN_RELE(cmpvp); } } /* * Search the parent directory for the entry corresponding to * this vnode. */ if ((err = dirfindvp(vrootp, pvp, vp, cr, dbuf, dlen, &dp)) != 0) goto out; complen = strlen(dp->d_name); bufloc -= complen; if (bufloc <= buf) { err = ENAMETOOLONG; goto out; } bcopy(dp->d_name, bufloc, complen); /* Prepend a slash to the current path. */ *--bufloc = '/'; /* And continue with the next component */ VN_RELE(vp); vp = pvp; pvp = NULL; } /* * Place the path at the beginning of the buffer. */ if (bufloc != buf) ovbcopy(bufloc, buf, buflen - (bufloc - buf)); out: /* * If the error was ESTALE and the current directory to look in * was the root for this lookup, the root for a mounted file * system, or the starting directory for lookups, then * return ENOENT instead of ESTALE. In this case, no recovery * is possible by the higher level. If ESTALE was returned for * some intermediate directory along the path, then recovery * is potentially possible and retrying from the higher level * will either correct the situation by purging stale cache * entries or eventually get back to the point where no recovery * is possible. */ if (err == ESTALE && (VN_CMP(vp, vrootp) || (vp->v_flag & VROOT) || vp == startvp)) err = ENOENT; kmem_free(dbuf, dlen); VN_RELE(vp); if (pvp) VN_RELE(pvp); pn_free(&pn); pn_free(&rpn); return (err); }
/* * Starting at current directory, translate pathname pnp to end. * Leave pathname of final component in pnp, return the vnode * for the final component in *compvpp, and return the vnode * for the parent of the final component in dirvpp. * * This is the central routine in pathname translation and handles * multiple components in pathnames, separating them at /'s. It also * implements mounted file systems and processes symbolic links. * * vp is the vnode where the directory search should start. * * Reference counts: vp must be held prior to calling this function. rootvp * should only be held if rootvp != rootdir. */ int lookuppnvp( struct pathname *pnp, /* pathname to lookup */ struct pathname *rpnp, /* if non-NULL, return resolved path */ int flags, /* follow symlinks */ vnode_t **dirvpp, /* ptr for parent vnode */ vnode_t **compvpp, /* ptr for entry vnode */ vnode_t *rootvp, /* rootvp */ vnode_t *vp, /* directory to start search at */ cred_t *cr) /* user's credential */ { vnode_t *cvp; /* current component vp */ char component[MAXNAMELEN]; /* buffer for component (incl null) */ int error; int nlink; int lookup_flags; struct pathname presrvd; /* case preserved name */ struct pathname *pp = NULL; vnode_t *startvp; vnode_t *zonevp = curproc->p_zone->zone_rootvp; /* zone root */ int must_be_directory = 0; boolean_t retry_with_kcred; uint32_t auditing = AU_AUDITING(); CPU_STATS_ADDQ(CPU, sys, namei, 1); nlink = 0; cvp = NULL; if (rpnp) rpnp->pn_pathlen = 0; lookup_flags = dirvpp ? LOOKUP_DIR : 0; if (flags & FIGNORECASE) { lookup_flags |= FIGNORECASE; pn_alloc(&presrvd); pp = &presrvd; } if (auditing) audit_anchorpath(pnp, vp == rootvp); /* * Eliminate any trailing slashes in the pathname. * If there are any, we must follow all symlinks. * Also, we must guarantee that the last component is a directory. */ if (pn_fixslash(pnp)) { flags |= FOLLOW; must_be_directory = 1; } startvp = vp; next: retry_with_kcred = B_FALSE; /* * Make sure we have a directory. */ if (vp->v_type != VDIR) { error = ENOTDIR; goto bad; } if (rpnp && VN_CMP(vp, rootvp)) (void) pn_set(rpnp, "/"); /* * Process the next component of the pathname. */ if (error = pn_getcomponent(pnp, component)) { goto bad; } /* * Handle "..": two special cases. * 1. If we're at the root directory (e.g. after chroot or * zone_enter) then change ".." to "." so we can't get * out of this subtree. * 2. If this vnode is the root of a mounted file system, * then replace it with the vnode that was mounted on * so that we take the ".." in the other file system. */ if (component[0] == '.' && component[1] == '.' && component[2] == 0) { checkforroot: if (VN_CMP(vp, rootvp) || VN_CMP(vp, zonevp)) { component[1] = '\0'; } else if (vp->v_flag & VROOT) { vfs_t *vfsp; cvp = vp; /* * While we deal with the vfs pointer from the vnode * the filesystem could have been forcefully unmounted * and the vnode's v_vfsp could have been invalidated * by VFS_UNMOUNT. Hence, we cache v_vfsp and use it * with vfs_rlock_wait/vfs_unlock. * It is safe to use the v_vfsp even it is freed by * VFS_UNMOUNT because vfs_rlock_wait/vfs_unlock * do not dereference v_vfsp. It is just used as a * magic cookie. * One more corner case here is the memory getting * reused for another vfs structure. In this case * lookuppnvp's vfs_rlock_wait will succeed, domount's * vfs_lock will fail and domount will bail out with an * error (EBUSY). */ vfsp = cvp->v_vfsp; /* * This lock is used to synchronize * mounts/unmounts and lookups. * Threads doing mounts/unmounts hold the * writers version vfs_lock_wait(). */ vfs_rlock_wait(vfsp); /* * If this vnode is on a file system that * has been forcibly unmounted, * we can't proceed. Cancel this operation * and return EIO. * * vfs_vnodecovered is NULL if unmounted. * Currently, nfs uses VFS_UNMOUNTED to * check if it's a forced-umount. Keep the * same checking here as well even though it * may not be needed. */ if (((vp = cvp->v_vfsp->vfs_vnodecovered) == NULL) || (cvp->v_vfsp->vfs_flag & VFS_UNMOUNTED)) { vfs_unlock(vfsp); VN_RELE(cvp); if (pp) pn_free(pp); return (EIO); } VN_HOLD(vp); vfs_unlock(vfsp); VN_RELE(cvp); cvp = NULL; /* * Crossing mount points. For eg: We are doing * a lookup of ".." for file systems root vnode * mounted here, and VOP_LOOKUP() (with covered vnode) * will be on underlying file systems mount point * vnode. Set retry_with_kcred flag as we might end * up doing VOP_LOOKUP() with kcred if required. */ retry_with_kcred = B_TRUE; goto checkforroot; } } /* * LOOKUP_CHECKREAD is a private flag used by vnodetopath() to indicate * that we need to have read permission on every directory in the entire * path. This is used to ensure that a forward-lookup of a cached value * has the same effect as a reverse-lookup when the cached value cannot * be found. */ if ((flags & LOOKUP_CHECKREAD) && (error = VOP_ACCESS(vp, VREAD, 0, cr, NULL)) != 0) goto bad; /* * Perform a lookup in the current directory. */ error = VOP_LOOKUP(vp, component, &cvp, pnp, lookup_flags, rootvp, cr, NULL, NULL, pp); /* * Retry with kcred - If crossing mount points & error is EACCES. * * If we are crossing mount points here and doing ".." lookup, * VOP_LOOKUP() might fail if the underlying file systems * mount point has no execute permission. In cases like these, * we retry VOP_LOOKUP() by giving as much privilage as possible * by passing kcred credentials. * * In case of hierarchical file systems, passing kcred still may * or may not work. * For eg: UFS FS --> Mount NFS FS --> Again mount UFS on some * directory inside NFS FS. */ if ((error == EACCES) && retry_with_kcred) error = VOP_LOOKUP(vp, component, &cvp, pnp, lookup_flags, rootvp, zone_kcred(), NULL, NULL, pp); if (error) { cvp = NULL; /* * On error, return hard error if * (a) we're not at the end of the pathname yet, or * (b) the caller didn't want the parent directory, or * (c) we failed for some reason other than a missing entry. */ if (pn_pathleft(pnp) || dirvpp == NULL || error != ENOENT) goto bad; if (auditing) { /* directory access */ if (error = audit_savepath(pnp, vp, vp, error, cr)) goto bad_noaudit; } pn_setlast(pnp); /* * We inform the caller that the desired entry must be * a directory by adding a '/' to the component name. */ if (must_be_directory && (error = pn_addslash(pnp)) != 0) goto bad; *dirvpp = vp; if (compvpp != NULL) *compvpp = NULL; if (rootvp != rootdir) VN_RELE(rootvp); if (pp) pn_free(pp); return (0); } /* * Traverse mount points. * XXX why don't we need to hold a read lock here (call vn_vfsrlock)? * What prevents a concurrent update to v_vfsmountedhere? * Possible answer: if mounting, we might not see the mount * if it is concurrently coming into existence, but that's * really not much different from the thread running a bit slower. * If unmounting, we may get into traverse() when we shouldn't, * but traverse() will catch this case for us. * (For this to work, fetching v_vfsmountedhere had better * be atomic!) */ if (vn_mountedvfs(cvp) != NULL) { if ((error = traverse(&cvp)) != 0) goto bad; } /* * If we hit a symbolic link and there is more path to be * translated or this operation does not wish to apply * to a link, then place the contents of the link at the * front of the remaining pathname. */ if (cvp->v_type == VLNK && ((flags & FOLLOW) || pn_pathleft(pnp))) { struct pathname linkpath; if (++nlink > MAXSYMLINKS) { error = ELOOP; goto bad; } pn_alloc(&linkpath); if (error = pn_getsymlink(cvp, &linkpath, cr)) { pn_free(&linkpath); goto bad; } if (auditing) audit_symlink(pnp, &linkpath); if (pn_pathleft(&linkpath) == 0) (void) pn_set(&linkpath, "."); error = pn_insert(pnp, &linkpath, strlen(component)); pn_free(&linkpath); if (error) goto bad; VN_RELE(cvp); cvp = NULL; if (pnp->pn_pathlen == 0) { error = ENOENT; goto bad; } if (pnp->pn_path[0] == '/') { do { pnp->pn_path++; pnp->pn_pathlen--; } while (pnp->pn_path[0] == '/'); VN_RELE(vp); vp = rootvp; VN_HOLD(vp); } if (auditing) audit_anchorpath(pnp, vp == rootvp); if (pn_fixslash(pnp)) { flags |= FOLLOW; must_be_directory = 1; } goto next; } /* * If rpnp is non-NULL, remember the resolved path name therein. * Do not include "." components. Collapse occurrences of * "previous/..", so long as "previous" is not itself "..". * Exhausting rpnp results in error ENAMETOOLONG. */ if (rpnp && strcmp(component, ".") != 0) { size_t len; if (strcmp(component, "..") == 0 && rpnp->pn_pathlen != 0 && !((rpnp->pn_pathlen > 2 && strncmp(rpnp->pn_path+rpnp->pn_pathlen-3, "/..", 3) == 0) || (rpnp->pn_pathlen == 2 && strncmp(rpnp->pn_path, "..", 2) == 0))) { while (rpnp->pn_pathlen && rpnp->pn_path[rpnp->pn_pathlen-1] != '/') rpnp->pn_pathlen--; if (rpnp->pn_pathlen > 1) rpnp->pn_pathlen--; rpnp->pn_path[rpnp->pn_pathlen] = '\0'; } else { if (rpnp->pn_pathlen != 0 && rpnp->pn_path[rpnp->pn_pathlen-1] != '/') rpnp->pn_path[rpnp->pn_pathlen++] = '/'; if (flags & FIGNORECASE) { /* * Return the case-preserved name * within the resolved path. */ error = copystr(pp->pn_buf, rpnp->pn_path + rpnp->pn_pathlen, rpnp->pn_bufsize - rpnp->pn_pathlen, &len); } else { error = copystr(component, rpnp->pn_path + rpnp->pn_pathlen, rpnp->pn_bufsize - rpnp->pn_pathlen, &len); } if (error) /* copystr() returns ENAMETOOLONG */ goto bad; rpnp->pn_pathlen += (len - 1); ASSERT(rpnp->pn_bufsize > rpnp->pn_pathlen); } } /* * If no more components, return last directory (if wanted) and * last component (if wanted). */ if (pn_pathleft(pnp) == 0) { /* * If there was a trailing slash in the pathname, * make sure the last component is a directory. */ if (must_be_directory && cvp->v_type != VDIR) { error = ENOTDIR; goto bad; } if (dirvpp != NULL) { /* * Check that we have the real parent and not * an alias of the last component. */ if (vn_compare(vp, cvp)) { if (auditing) (void) audit_savepath(pnp, cvp, vp, EINVAL, cr); pn_setlast(pnp); VN_RELE(vp); VN_RELE(cvp); if (rootvp != rootdir) VN_RELE(rootvp); if (pp) pn_free(pp); return (EINVAL); } *dirvpp = vp; } else VN_RELE(vp); if (auditing) (void) audit_savepath(pnp, cvp, vp, 0, cr); if (pnp->pn_path == pnp->pn_buf) (void) pn_set(pnp, "."); else pn_setlast(pnp); if (rpnp) { if (VN_CMP(cvp, rootvp)) (void) pn_set(rpnp, "/"); else if (rpnp->pn_pathlen == 0) (void) pn_set(rpnp, "."); } if (compvpp != NULL) *compvpp = cvp; else VN_RELE(cvp); if (rootvp != rootdir) VN_RELE(rootvp); if (pp) pn_free(pp); return (0); } /* * Skip over slashes from end of last component. */ while (pnp->pn_path[0] == '/') { pnp->pn_path++; pnp->pn_pathlen--; } /* * Searched through another level of directory: * release previous directory handle and save new (result * of lookup) as current directory. */ VN_RELE(vp); vp = cvp; cvp = NULL; goto next; bad: if (auditing) /* reached end of path */ (void) audit_savepath(pnp, cvp, vp, error, cr); bad_noaudit: /* * Error. Release vnodes and return. */ if (cvp) VN_RELE(cvp); /* * If the error was ESTALE and the current directory to look in * was the root for this lookup, the root for a mounted file * system, or the starting directory for lookups, then * return ENOENT instead of ESTALE. In this case, no recovery * is possible by the higher level. If ESTALE was returned for * some intermediate directory along the path, then recovery * is potentially possible and retrying from the higher level * will either correct the situation by purging stale cache * entries or eventually get back to the point where no recovery * is possible. */ if (error == ESTALE && (VN_CMP(vp, rootvp) || (vp->v_flag & VROOT) || vp == startvp)) error = ENOENT; VN_RELE(vp); if (rootvp != rootdir) VN_RELE(rootvp); if (pp) pn_free(pp); return (error); }
int dogetcwd(char *buf, size_t buflen) { int ret; vnode_t *vp; vnode_t *compvp; refstr_t *cwd, *oldcwd; const char *value; pathname_t rpnp, pnp; proc_t *p = curproc; /* * Check to see if there is a cached version of the cwd. If so, lookup * the cached value and make sure it is the same vnode. */ mutex_enter(&p->p_lock); if ((cwd = PTOU(p)->u_cwd) != NULL) refstr_hold(cwd); vp = PTOU(p)->u_cdir; VN_HOLD(vp); mutex_exit(&p->p_lock); /* * Make sure we have permission to access the current directory. */ if ((ret = VOP_ACCESS(vp, VEXEC, 0, CRED(), NULL)) != 0) { if (cwd != NULL) refstr_rele(cwd); VN_RELE(vp); return (ret); } if (cwd) { value = refstr_value(cwd); if ((ret = pn_get((char *)value, UIO_SYSSPACE, &pnp)) != 0) { refstr_rele(cwd); VN_RELE(vp); return (ret); } pn_alloc(&rpnp); if (lookuppn(&pnp, &rpnp, NO_FOLLOW, NULL, &compvp) == 0) { if (VN_CMP(vp, compvp) && strcmp(value, rpnp.pn_path) == 0) { VN_RELE(compvp); VN_RELE(vp); pn_free(&pnp); pn_free(&rpnp); if (strlen(value) + 1 > buflen) { refstr_rele(cwd); return (ENAMETOOLONG); } bcopy(value, buf, strlen(value) + 1); refstr_rele(cwd); return (0); } VN_RELE(compvp); } pn_free(&rpnp); pn_free(&pnp); refstr_rele(cwd); } ret = vnodetopath_common(NULL, vp, buf, buflen, CRED(), LOOKUP_CHECKREAD); VN_RELE(vp); /* * Store the new cwd and replace the existing cached copy. */ if (ret == 0) cwd = refstr_alloc(buf); else cwd = NULL; mutex_enter(&p->p_lock); oldcwd = PTOU(p)->u_cwd; PTOU(p)->u_cwd = cwd; mutex_exit(&p->p_lock); if (oldcwd) refstr_rele(oldcwd); return (ret); }
/************************************************************************ * iumfs_free_node() * * 指定された vnode および iumnode を解放する * * 1. iumnode に関連づいたリソースを解放 * 2. iumnode 構造体を解放 * 3. vnode 構造体を解放 * * これが呼ばれるのは、iumfs_inactive() もしくは iumfs_unmount() 経由の * iumfs_free_all_node() だけ。つまり、v_count が 1(未参照状態)である * 事が確かな場合だけ。 * * 引数: * * vp: 解放する vnode 構造体のポインタ * cr : システムコールを呼び出したユーザのクレデンシャル * * 戻り値: * 無し * ************************************************************************/ void iumfs_free_node(vnode_t *vp, struct cred *cr) { iumnode_t *inp; // ファイルシステム型依存のノード情報(iumnode構造体) vnode_t *rootvp; // ファイルシステムのルートディレクトリの vnode。 iumfs_t *iumfsp; // ファイルシステム型依存のプライベートデータ構造体 vfs_t *vfsp; // ファイルシステム構造体 int err; DEBUG_PRINT((CE_CONT, "iumfs_free_node is called\n")); iumfsp = VNODE2IUMFS(vp); vfsp = VNODE2VFS(vp); inp = VNODE2IUMNODE(vp); DEBUG_PRINT((CE_CONT, "iumfs_free_node: vnode=%p, vp->v_count=%d\n", vp, vp->v_count)); /* * 最初にノードリンクリストから iumnode をはずす。誰かが利用中(EBUSY)だったらリターン。 * 仮にノードリストに入っていなかったとしても、(ありえないはずだが) vnode のフリーは行う。 */ if((err = iumfs_remove_node_from_list(vfsp, vp)) != 0){ if (err == ENOENT) cmn_err(CE_CONT, "iumfs_free_node: can't find vnode in the list. Free it anyway.\n"); else return; } // debug 用 rootvp = VNODE2ROOT(vp); if (rootvp != NULL && VN_CMP(rootvp, vp) != 0) { DEBUG_PRINT((CE_CONT, "iumfs_free_node: rootvnode is being freed\n")); mutex_enter(&(iumfsp->iumfs_lock)); iumfsp->rootvnode = NULL; mutex_exit(&(iumfsp->iumfs_lock)); } /* * もし iumnode にデータ(ディレクトリエントリ等) * を含んでいたらそれらも解放する。 */ if (inp->data != NULL) { kmem_free(inp->data, inp->dlen); } /* * この vnode に関連した page を無効にする */ err = pvn_vplist_dirty(vp, 0, iumfs_putapage, B_INVAL, cr); DEBUG_PRINT((CE_CONT, "iumfs_free_node: pvn_vplist_dirty returned with (%d)\n", err)); if (vn_has_cached_data(vp)) { cmn_err(CE_WARN, "iumfs_free_node: vnode still have cached\n"); } // iumnode を解放 mutex_destroy(&(inp)->i_dlock); rw_destroy(&(inp)->i_listlock); kmem_free(inp, sizeof (iumnode_t)); // vnode を解放 #ifdef SOL10 vn_free(vp); #else mutex_destroy(&(vp)->v_lock); kmem_free(vp, sizeof (vnode_t)); #endif DEBUG_PRINT((CE_CONT, "iumfs_free_node: return\n")); return; }