void lookup_consider_update_cache(vnode_t dvp, vnode_t vp, struct componentname *cnp, int nc_generation) { int isdot_or_dotdot; isdot_or_dotdot = (cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.') || (cnp->cn_flags & ISDOTDOT); if (vp->v_name == NULL || vp->v_parent == NULLVP) { int update_flags = 0; if (isdot_or_dotdot == 0) { if (vp->v_name == NULL) update_flags |= VNODE_UPDATE_NAME; if (dvp != NULLVP && vp->v_parent == NULLVP) update_flags |= VNODE_UPDATE_PARENT; if (update_flags) vnode_update_identity(vp, dvp, cnp->cn_nameptr, cnp->cn_namelen, cnp->cn_hash, update_flags); } } if ( (cnp->cn_flags & MAKEENTRY) && (vp->v_flag & VNCACHEABLE) && LIST_FIRST(&vp->v_nclinks) == NULL) { /* * missing from name cache, but should * be in it... this can happen if volfs * causes the vnode to be created or the * name cache entry got recycled but the * vnode didn't... * check to make sure that ni_dvp is valid * cache_lookup_path may return a NULL * do a quick check to see if the generation of the * directory matches our snapshot... this will get * rechecked behind the name cache lock, but if it * already fails to match, no need to go any further */ if (dvp != NULLVP && (nc_generation == dvp->v_nc_generation) && (!isdot_or_dotdot)) cache_enter_with_gen(dvp, vp, cnp, nc_generation); } }
/* * Look up a vnode/nfsnode by file handle. * Callers must check for mount points!! * In all cases, a pointer to a * nfsnode structure is returned. */ int nfs_nget( mount_t mp, nfsnode_t dnp, struct componentname *cnp, u_char *fhp, int fhsize, struct nfs_vattr *nvap, u_int64_t *xidp, uint32_t auth, int flags, nfsnode_t *npp) { nfsnode_t np; struct nfsnodehashhead *nhpp; vnode_t vp; int error, nfsvers; mount_t mp2; struct vnode_fsparam vfsp; uint32_t vid; FSDBG_TOP(263, mp, dnp, flags, npp); /* Check for unmount in progress */ if (!mp || vfs_isforce(mp)) { *npp = NULL; error = ENXIO; FSDBG_BOT(263, mp, dnp, 0xd1e, error); return (error); } nfsvers = VFSTONFS(mp)->nm_vers; nhpp = NFSNOHASH(nfs_hash(fhp, fhsize)); loop: lck_mtx_lock(nfs_node_hash_mutex); for (np = nhpp->lh_first; np != 0; np = np->n_hash.le_next) { mp2 = (np->n_hflag & NHINIT) ? np->n_mount : NFSTOMP(np); if (mp != mp2 || np->n_fhsize != fhsize || bcmp(fhp, np->n_fhp, fhsize)) continue; if (nvap && (nvap->nva_flags & NFS_FFLAG_TRIGGER_REFERRAL) && cnp && (cnp->cn_namelen > (fhsize - (int)sizeof(dnp)))) { /* The name was too long to fit in the file handle. Check it against the node's name. */ int namecmp = 0; const char *vname = vnode_getname(NFSTOV(np)); if (vname) { if (cnp->cn_namelen != (int)strlen(vname)) namecmp = 1; else namecmp = strncmp(vname, cnp->cn_nameptr, cnp->cn_namelen); vnode_putname(vname); } if (namecmp) /* full name didn't match */ continue; } FSDBG(263, dnp, np, np->n_flag, 0xcace0000); /* if the node is locked, sleep on it */ if ((np->n_hflag & NHLOCKED) && !(flags & NG_NOCREATE)) { np->n_hflag |= NHLOCKWANT; FSDBG(263, dnp, np, np->n_flag, 0xcace2222); msleep(np, nfs_node_hash_mutex, PDROP | PINOD, "nfs_nget", NULL); FSDBG(263, dnp, np, np->n_flag, 0xcace3333); goto loop; } vp = NFSTOV(np); vid = vnode_vid(vp); lck_mtx_unlock(nfs_node_hash_mutex); if ((error = vnode_getwithvid(vp, vid))) { /* * If vnode is being reclaimed or has already * changed identity, no need to wait. */ FSDBG_BOT(263, dnp, *npp, 0xcace0d1e, error); return (error); } if ((error = nfs_node_lock(np))) { /* this only fails if the node is now unhashed */ /* so let's see if we can find/create it again */ FSDBG(263, dnp, *npp, 0xcaced1e2, error); vnode_put(vp); if (flags & NG_NOCREATE) { *npp = 0; FSDBG_BOT(263, dnp, *npp, 0xcaced1e0, ENOENT); return (ENOENT); } goto loop; } /* update attributes */ if (nvap) error = nfs_loadattrcache(np, nvap, xidp, 0); if (error) { nfs_node_unlock(np); vnode_put(vp); } else { if (dnp && cnp && (flags & NG_MAKEENTRY)) cache_enter(NFSTOV(dnp), vp, cnp); /* * Update the vnode if the name/and or the parent has * changed. We need to do this so that if getattrlist is * called asking for ATTR_CMN_NAME, that the "most" * correct name is being returned. In addition for * monitored vnodes we need to kick the vnode out of the * name cache. We do this so that if there are hard * links in the same directory the link will not be * found and a lookup will get us here to return the * name of the current link. In addition by removing the * name from the name cache the old name will not be * found after a rename done on another client or the * server. The principle reason to do this is because * Finder is asking for notifications on a directory. * The directory changes, Finder gets notified, reads * the directory (which we have purged) and for each * entry returned calls getattrlist with the name * returned from readdir. gettattrlist has to call * namei/lookup to resolve the name, because its not in * the cache we end up here. We need to update the name * so Finder will get the name it called us with. * * We had an imperfect solution with respect to case * sensitivity. There is a test that is run in * FileBuster that does renames from some name to * another name differing only in case. It then reads * the directory looking for the new name, after it * finds that new name, it ask gettattrlist to verify * that the name is the new name. Usually that works, * but renames generate fsevents and fseventsd will do a * lookup on the name via lstat. Since that test renames * old name to new name back and forth there is a race * that an fsevent will be behind and will access the * file by the old name, on a case insensitive file * system that will work. Problem is if we do a case * sensitive compare, we're going to change the name, * which the test's getattrlist verification step is * going to fail. So we will check the case sensitivity * of the file system and do the appropriate compare. In * a rare instance for non homogeneous file systems * w.r.t. pathconf we will use case sensitive compares. * That could break if the file system is actually case * insensitive. * * Note that V2 does not know the case, so we just * assume case sensitivity. * * This is clearly not perfect due to races, but this is * as good as its going to get. You can defeat the * handling of hard links simply by doing: * * while :; do ls -l > /dev/null; done * * in a terminal window. Even a single ls -l can cause a * race. * * <rant>What we really need is for the caller, that * knows the name being used is valid since it got it * from a readdir to use that name and not ask for the * ATTR_CMN_NAME</rant> */ if (dnp && cnp && (vp != NFSTOV(dnp))) { int update_flags = (vnode_ismonitored((NFSTOV(dnp)))) ? VNODE_UPDATE_CACHE : 0; int (*cmp)(const char *s1, const char *s2, size_t n); cmp = nfs_case_insensitive(mp) ? strncasecmp : strncmp; if (vp->v_name && cnp->cn_namelen && (*cmp)(cnp->cn_nameptr, vp->v_name, cnp->cn_namelen)) update_flags |= VNODE_UPDATE_NAME; if ((vp->v_name == NULL && cnp->cn_namelen != 0) || (vp->v_name != NULL && cnp->cn_namelen == 0)) update_flags |= VNODE_UPDATE_NAME; if (vnode_parent(vp) != NFSTOV(dnp)) update_flags |= VNODE_UPDATE_PARENT; if (update_flags) { NFS_NODE_DBG("vnode_update_identity old name %s new name %.*s update flags = %x\n", vp->v_name, cnp->cn_namelen, cnp->cn_nameptr ? cnp->cn_nameptr : "", update_flags); vnode_update_identity(vp, NFSTOV(dnp), cnp->cn_nameptr, cnp->cn_namelen, 0, update_flags); } } *npp = np; } FSDBG_BOT(263, dnp, *npp, 0xcace0000, error); return(error); } FSDBG(263, mp, dnp, npp, 0xaaaaaaaa); if (flags & NG_NOCREATE) { lck_mtx_unlock(nfs_node_hash_mutex); *npp = 0; FSDBG_BOT(263, dnp, *npp, 0x80000001, ENOENT); return (ENOENT); } /* * allocate and initialize nfsnode and stick it in the hash * before calling getnewvnode(). Anyone finding it in the * hash before initialization is complete will wait for it. */ MALLOC_ZONE(np, nfsnode_t, sizeof *np, M_NFSNODE, M_WAITOK); if (!np) { lck_mtx_unlock(nfs_node_hash_mutex); *npp = 0; FSDBG_BOT(263, dnp, *npp, 0x80000001, ENOMEM); return (ENOMEM); } bzero(np, sizeof *np); np->n_hflag |= (NHINIT | NHLOCKED); np->n_mount = mp; np->n_auth = auth; TAILQ_INIT(&np->n_opens); TAILQ_INIT(&np->n_lock_owners); TAILQ_INIT(&np->n_locks); np->n_dlink.tqe_next = NFSNOLIST; np->n_dreturn.tqe_next = NFSNOLIST; np->n_monlink.le_next = NFSNOLIST; /* ugh... need to keep track of ".zfs" directories to workaround server bugs */ if ((nvap->nva_type == VDIR) && cnp && (cnp->cn_namelen == 4) && (cnp->cn_nameptr[0] == '.') && (cnp->cn_nameptr[1] == 'z') && (cnp->cn_nameptr[2] == 'f') && (cnp->cn_nameptr[3] == 's')) np->n_flag |= NISDOTZFS; if (dnp && (dnp->n_flag & NISDOTZFS)) np->n_flag |= NISDOTZFSCHILD; if (dnp && cnp && ((cnp->cn_namelen != 2) || (cnp->cn_nameptr[0] != '.') || (cnp->cn_nameptr[1] != '.'))) { vnode_t dvp = NFSTOV(dnp); if (!vnode_get(dvp)) { if (!vnode_ref(dvp)) np->n_parent = dvp; vnode_put(dvp); } } /* setup node's file handle */ if (fhsize > NFS_SMALLFH) { MALLOC_ZONE(np->n_fhp, u_char *, fhsize, M_NFSBIGFH, M_WAITOK); if (!np->n_fhp) { lck_mtx_unlock(nfs_node_hash_mutex); FREE_ZONE(np, sizeof *np, M_NFSNODE); *npp = 0; FSDBG_BOT(263, dnp, *npp, 0x80000002, ENOMEM); return (ENOMEM); } } else {
/*ARGSUSED*/ static int zfs_vfs_unmount(struct mount *mp, int mntflags, vfs_context_t context) { zfsvfs_t *zfsvfs = vfs_fsprivate(mp); objset_t *os = zfsvfs->z_os; znode_t *zp, *nextzp; int ret, i; int flags; /*XXX NOEL: delegation admin stuffs, add back if we use delg. admin */ #if 0 ret = 0; /* UNDEFINED: secpolicy_fs_unmount(cr, vfsp); */ if (ret) { ret = dsl_deleg_access((char *)refstr_value(vfsp->vfs_resource), ZFS_DELEG_PERM_MOUNT, cr); if (ret) return (ret); } /* * We purge the parent filesystem's vfsp as the parent filesystem * and all of its snapshots have their vnode's v_vfsp set to the * parent's filesystem's vfsp. Note, 'z_parent' is self * referential for non-snapshots. */ (void) dnlc_purge_vfsp(zfsvfs->z_parent->z_vfs, 0); #endif /* * Unmount any snapshots mounted under .zfs before unmounting the * dataset itself. */ #if 0 if (zfsvfs->z_ctldir != NULL && (ret = zfsctl_umount_snapshots(vfsp, fflag, cr)) != 0) { return (ret); #endif flags = SKIPSYSTEM; if (mntflags & MNT_FORCE) flags |= FORCECLOSE; ret = vflush(mp, NULLVP, flags); /* * Mac OS X needs a file system modify time * * We use the mtime of the "com.apple.system.mtime" * extended attribute, which is associated with the * file system root directory. * * Here we need to release the ref we took on z_mtime_vp during mount. */ if ((ret == 0) || (mntflags & MNT_FORCE)) { if (zfsvfs->z_mtime_vp != NULL) { struct vnode *mvp; mvp = zfsvfs->z_mtime_vp; zfsvfs->z_mtime_vp = NULL; if (vnode_get(mvp) == 0) { vnode_rele(mvp); vnode_recycle(mvp); vnode_put(mvp); } } } if (!(mntflags & MNT_FORCE)) { /* * Check the number of active vnodes in the file system. * Our count is maintained in the vfs structure, but the * number is off by 1 to indicate a hold on the vfs * structure itself. * * The '.zfs' directory maintains a reference of its * own, and any active references underneath are * reflected in the vnode count. */ if (ret) return (EBUSY); #if 0 if (zfsvfs->z_ctldir == NULL) { if (vfsp->vfs_count > 1) return (EBUSY); } else { if (vfsp->vfs_count > 2 || zfsvfs->z_ctldir->v_count > 1) { return (EBUSY); } } #endif } rw_enter(&zfsvfs->z_unmount_lock, RW_WRITER); rw_enter(&zfsvfs->z_unmount_inactive_lock, RW_WRITER); /* * At this point there are no vops active, and any new vops will * fail with EIO since we have z_unmount_lock for writer (only * relavent for forced unmount). * * Release all holds on dbufs. * Note, the dmu can still callback via znode_pageout_func() * which can zfs_znode_free() the znode. So we lock * z_all_znodes; search the list for a held dbuf; drop the lock * (we know zp can't disappear if we hold a dbuf lock) then * regrab the lock and restart. */ mutex_enter(&zfsvfs->z_znodes_lock); for (zp = list_head(&zfsvfs->z_all_znodes); zp; zp = nextzp) { nextzp = list_next(&zfsvfs->z_all_znodes, zp); if (zp->z_dbuf_held) { /* dbufs should only be held when force unmounting */ zp->z_dbuf_held = 0; mutex_exit(&zfsvfs->z_znodes_lock); dmu_buf_rele(zp->z_dbuf, NULL); /* Start again */ mutex_enter(&zfsvfs->z_znodes_lock); nextzp = list_head(&zfsvfs->z_all_znodes); } } mutex_exit(&zfsvfs->z_znodes_lock); /* * Set the unmounted flag and let new vops unblock. * zfs_inactive will have the unmounted behavior, and all other * vops will fail with EIO. */ zfsvfs->z_unmounted = B_TRUE; rw_exit(&zfsvfs->z_unmount_lock); rw_exit(&zfsvfs->z_unmount_inactive_lock); /* * Unregister properties. */ #ifndef __APPLE__ if (!dmu_objset_is_snapshot(os)) zfs_unregister_callbacks(zfsvfs); #endif /* * Close the zil. NB: Can't close the zil while zfs_inactive * threads are blocked as zil_close can call zfs_inactive. */ if (zfsvfs->z_log) { zil_close(zfsvfs->z_log); zfsvfs->z_log = NULL; } /* * Evict all dbufs so that cached znodes will be freed */ if (dmu_objset_evict_dbufs(os, B_TRUE)) { txg_wait_synced(dmu_objset_pool(zfsvfs->z_os), 0); (void) dmu_objset_evict_dbufs(os, B_FALSE); } /* * Finally close the objset */ dmu_objset_close(os); /* * We can now safely destroy the '.zfs' directory node. */ #if 0 if (zfsvfs->z_ctldir != NULL) zfsctl_destroy(zfsvfs); #endif /* * Note that this work is normally done in zfs_freevfs, but since * there is no VOP_FREEVFS in OSX, we free VFS items here */ OSDecrementAtomic((SInt32 *)&zfs_active_fs_count); for (i = 0; i != ZFS_OBJ_MTX_SZ; i++) mutex_destroy(&zfsvfs->z_hold_mtx[i]); mutex_destroy(&zfsvfs->z_znodes_lock); list_destroy(&zfsvfs->z_all_znodes); rw_destroy(&zfsvfs->z_unmount_lock); rw_destroy(&zfsvfs->z_unmount_inactive_lock); return (0); } struct vnode* vnode_getparent(struct vnode *vp); /* sys/vnode_internal.h */ static int zfs_vget_internal(zfsvfs_t *zfsvfs, ino64_t ino, struct vnode **vpp) { struct vnode *vp; struct vnode *dvp = NULL; znode_t *zp; int error; *vpp = NULL; /* * On Mac OS X we always export the root directory id as 2 * and its parent as 1 */ if (ino == 2 || ino == 1) ino = zfsvfs->z_root; if ((error = zfs_zget(zfsvfs, ino, &zp))) goto out; /* Don't expose EA objects! */ if (zp->z_phys->zp_flags & ZFS_XATTR) { vnode_put(ZTOV(zp)); error = ENOENT; goto out; } *vpp = vp = ZTOV(zp); if (vnode_isvroot(vp)) goto out; /* * If this znode didn't just come from the cache then * it won't have a valid identity (parent and name). * * Manually fix its identity here (normally done by namei lookup). */ if ((dvp = vnode_getparent(vp)) == NULL) { if (zp->z_phys->zp_parent != 0 && zfs_vget_internal(zfsvfs, zp->z_phys->zp_parent, &dvp)) { goto out; } if ( vnode_isdir(dvp) ) { char objname[ZAP_MAXNAMELEN]; /* 256 bytes */ int flags = VNODE_UPDATE_PARENT; /* Look for znode's name in its parent's zap */ if ( zap_value_search(zfsvfs->z_os, zp->z_phys->zp_parent, zp->z_id, ZFS_DIRENT_OBJ(-1ULL), objname) == 0 ) { flags |= VNODE_UPDATE_NAME; } /* Update the znode's parent and name */ vnode_update_identity(vp, dvp, objname, 0, 0, flags); } } /* All done with znode's parent */ vnode_put(dvp); out: return (error); } /* * Get a vnode from a file id (ignoring the generation) * * Use by NFS Server (readdirplus) and VFS (build_path) */ static int zfs_vfs_vget(struct mount *mp, ino64_t ino, struct vnode **vpp, __unused vfs_context_t context) { zfsvfs_t *zfsvfs = vfs_fsprivate(mp); int error; ZFS_ENTER(zfsvfs); /* * On Mac OS X we always export the root directory id as 2. * So we don't expect to see the real root directory id * from zfs_vfs_vget KPI (unless of course the real id was * already 2). */ if ((ino == zfsvfs->z_root) && (zfsvfs->z_root != 2)) { ZFS_EXIT(zfsvfs); return (ENOENT); } error = zfs_vget_internal(zfsvfs, ino, vpp); ZFS_EXIT(zfsvfs); return (error); }
/* * Search a pathname. * This is a very central and rather complicated routine. * * The pathname is pointed to by ni_ptr and is of length ni_pathlen. * The starting directory is taken from ni_startdir. The pathname is * descended until done, or a symbolic link is encountered. The variable * ni_more is clear if the path is completed; it is set to one if a * symbolic link needing interpretation is encountered. * * The flag argument is LOOKUP, CREATE, RENAME, or DELETE depending on * whether the name is to be looked up, created, renamed, or deleted. * When CREATE, RENAME, or DELETE is specified, information usable in * creating, renaming, or deleting a directory entry may be calculated. * If flag has LOCKPARENT or'ed into it, the parent directory is returned * locked. If flag has WANTPARENT or'ed into it, the parent directory is * returned unlocked. Otherwise the parent directory is not returned. If * the target of the pathname exists and LOCKLEAF is or'ed into the flag * the target is returned locked, otherwise it is returned unlocked. * When creating or renaming and LOCKPARENT is specified, the target may not * be ".". When deleting and LOCKPARENT is specified, the target may be ".". * * Overall outline of lookup: * * dirloop: * identify next component of name at ndp->ni_ptr * handle degenerate case where name is null string * if .. and crossing mount points and on mounted filesys, find parent * call VNOP_LOOKUP routine for next component name * directory vnode returned in ni_dvp, unlocked unless LOCKPARENT set * component vnode returned in ni_vp (if it exists), locked. * if result vnode is mounted on and crossing mount points, * find mounted on vnode * if more components of name, do next level at dirloop * return the answer in ni_vp, locked if LOCKLEAF set * if LOCKPARENT set, return locked parent in ni_dvp * if WANTPARENT set, return unlocked parent in ni_dvp * * Returns: 0 Success * ENOENT No such file or directory * EBADF Bad file descriptor * ENOTDIR Not a directory * EROFS Read-only file system [CREATE] * EISDIR Is a directory [CREATE] * cache_lookup_path:ERECYCLE (vnode was recycled from underneath us, redrive lookup again) * vnode_authorize:EROFS * vnode_authorize:EACCES * vnode_authorize:EPERM * vnode_authorize:??? * VNOP_LOOKUP:ENOENT No such file or directory * VNOP_LOOKUP:EJUSTRETURN Restart system call (INTERNAL) * VNOP_LOOKUP:??? * VFS_ROOT:ENOTSUP * VFS_ROOT:ENOENT * VFS_ROOT:??? */ int lookup(struct nameidata *ndp) { char *cp; /* pointer into pathname argument */ vnode_t tdp; /* saved dp */ vnode_t dp; /* the directory we are searching */ mount_t mp; /* mount table entry */ int docache = 1; /* == 0 do not cache last component */ int wantparent; /* 1 => wantparent or lockparent flag */ int rdonly; /* lookup read-only flag bit */ int trailing_slash = 0; int dp_authorized = 0; int error = 0; struct componentname *cnp = &ndp->ni_cnd; vfs_context_t ctx = cnp->cn_context; int mounted_on_depth = 0; int dont_cache_mp = 0; vnode_t mounted_on_dp = NULLVP; int current_mount_generation = 0; int vbusyflags = 0; int nc_generation = 0; vnode_t last_dp = NULLVP; /* * Setup: break out flag bits into variables. */ if (cnp->cn_flags & (NOCACHE | DOWHITEOUT)) { if ((cnp->cn_flags & NOCACHE) || (cnp->cn_nameiop == DELETE)) docache = 0; } wantparent = cnp->cn_flags & (LOCKPARENT | WANTPARENT); rdonly = cnp->cn_flags & RDONLY; cnp->cn_flags &= ~ISSYMLINK; cnp->cn_consume = 0; dp = ndp->ni_startdir; ndp->ni_startdir = NULLVP; if ((cnp->cn_flags & CN_NBMOUNTLOOK) != 0) vbusyflags = LK_NOWAIT; cp = cnp->cn_nameptr; if (*cp == '\0') { if ( (vnode_getwithref(dp)) ) { dp = NULLVP; error = ENOENT; goto bad; } goto emptyname; } dirloop: ndp->ni_vp = NULLVP; if ( (error = cache_lookup_path(ndp, cnp, dp, ctx, &trailing_slash, &dp_authorized, last_dp)) ) { dp = NULLVP; goto bad; } if ((cnp->cn_flags & ISLASTCN)) { if (docache) cnp->cn_flags |= MAKEENTRY; } else cnp->cn_flags |= MAKEENTRY; dp = ndp->ni_dvp; if (ndp->ni_vp != NULLVP) { /* * cache_lookup_path returned a non-NULL ni_vp then, * we're guaranteed that the dp is a VDIR, it's * been authorized, and vp is not ".." * * make sure we don't try to enter the name back into * the cache if this vp is purged before we get to that * check since we won't have serialized behind whatever * activity is occurring in the FS that caused the purge */ if (dp != NULLVP) nc_generation = dp->v_nc_generation - 1; goto returned_from_lookup_path; } /* * Handle "..": two special cases. * 1. If at root directory (e.g. after chroot) * or at absolute root directory * then ignore it so can't get out. * 2. If this vnode is the root of a mounted * filesystem, then replace it with the * vnode which was mounted on so we take the * .. in the other file system. */ if ( (cnp->cn_flags & ISDOTDOT) ) { for (;;) { if (dp == ndp->ni_rootdir || dp == rootvnode) { ndp->ni_dvp = dp; ndp->ni_vp = dp; /* * we're pinned at the root * we've already got one reference on 'dp' * courtesy of cache_lookup_path... take * another one for the ".." * if we fail to get the new reference, we'll * drop our original down in 'bad' */ if ( (vnode_get(dp)) ) { error = ENOENT; goto bad; } goto nextname; } if ((dp->v_flag & VROOT) == 0 || (cnp->cn_flags & NOCROSSMOUNT)) break; if (dp->v_mount == NULL) { /* forced umount */ error = EBADF; goto bad; } tdp = dp; dp = tdp->v_mount->mnt_vnodecovered; vnode_put(tdp); if ( (vnode_getwithref(dp)) ) { dp = NULLVP; error = ENOENT; goto bad; } ndp->ni_dvp = dp; dp_authorized = 0; } } /* * We now have a segment name to search for, and a directory to search. */ unionlookup: ndp->ni_vp = NULLVP; if (dp->v_type != VDIR) { error = ENOTDIR; goto lookup_error; } if ( (cnp->cn_flags & DONOTAUTH) != DONOTAUTH ) { if (!dp_authorized) { error = vnode_authorize(dp, NULL, KAUTH_VNODE_SEARCH, ctx); if (error) goto lookup_error; } #if CONFIG_MACF error = mac_vnode_check_lookup(ctx, dp, cnp); if (error) goto lookup_error; #endif /* CONFIG_MACF */ } nc_generation = dp->v_nc_generation; if ( (error = VNOP_LOOKUP(dp, &ndp->ni_vp, cnp, ctx)) ) { lookup_error: if ((error == ENOENT) && (dp->v_flag & VROOT) && (dp->v_mount != NULL) && (dp->v_mount->mnt_flag & MNT_UNION)) { if ((cnp->cn_flags & FSNODELOCKHELD)) { cnp->cn_flags &= ~FSNODELOCKHELD; unlock_fsnode(dp, NULL); } tdp = dp; dp = tdp->v_mount->mnt_vnodecovered; vnode_put(tdp); if ( (vnode_getwithref(dp)) ) { dp = NULLVP; error = ENOENT; goto bad; } ndp->ni_dvp = dp; dp_authorized = 0; goto unionlookup; } if (error != EJUSTRETURN) goto bad; if (ndp->ni_vp != NULLVP) panic("leaf should be empty"); /* * If creating and at end of pathname, then can consider * allowing file to be created. */ if (rdonly) { error = EROFS; goto bad; } if ((cnp->cn_flags & ISLASTCN) && trailing_slash && !(cnp->cn_flags & WILLBEDIR)) { error = ENOENT; goto bad; } /* * We return with ni_vp NULL to indicate that the entry * doesn't currently exist, leaving a pointer to the * referenced directory vnode in ndp->ni_dvp. */ if (cnp->cn_flags & SAVESTART) { if ( (vnode_get(ndp->ni_dvp)) ) { error = ENOENT; goto bad; } ndp->ni_startdir = ndp->ni_dvp; } if (!wantparent) vnode_put(ndp->ni_dvp); if (kdebug_enable) kdebug_lookup(ndp->ni_dvp, cnp); return (0); } returned_from_lookup_path: dp = ndp->ni_vp; /* * Take into account any additional components consumed by * the underlying filesystem. */ if (cnp->cn_consume > 0) { cnp->cn_nameptr += cnp->cn_consume; ndp->ni_next += cnp->cn_consume; ndp->ni_pathlen -= cnp->cn_consume; cnp->cn_consume = 0; } else { if (dp->v_name == NULL || dp->v_parent == NULLVP) { int isdot_or_dotdot; int update_flags = 0; isdot_or_dotdot = (cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.') || (cnp->cn_flags & ISDOTDOT); if (isdot_or_dotdot == 0) { if (dp->v_name == NULL) update_flags |= VNODE_UPDATE_NAME; if (ndp->ni_dvp != NULLVP && dp->v_parent == NULLVP) update_flags |= VNODE_UPDATE_PARENT; if (update_flags) vnode_update_identity(dp, ndp->ni_dvp, cnp->cn_nameptr, cnp->cn_namelen, cnp->cn_hash, update_flags); } } if ( (cnp->cn_flags & MAKEENTRY) && (dp->v_flag & VNCACHEABLE) && LIST_FIRST(&dp->v_nclinks) == NULL) { /* * missing from name cache, but should * be in it... this can happen if volfs * causes the vnode to be created or the * name cache entry got recycled but the * vnode didn't... * check to make sure that ni_dvp is valid * cache_lookup_path may return a NULL * do a quick check to see if the generation of the * directory matches our snapshot... this will get * rechecked behind the name cache lock, but if it * already fails to match, no need to go any further */ if (ndp->ni_dvp != NULLVP && (nc_generation == ndp->ni_dvp->v_nc_generation)) cache_enter_with_gen(ndp->ni_dvp, dp, cnp, nc_generation); } } mounted_on_dp = dp; mounted_on_depth = 0; dont_cache_mp = 0; current_mount_generation = mount_generation; /* * Check to see if the vnode has been mounted on... * if so find the root of the mounted file system. */ check_mounted_on: if ((dp->v_type == VDIR) && dp->v_mountedhere && ((cnp->cn_flags & NOCROSSMOUNT) == 0)) { vnode_lock(dp); if ((dp->v_type == VDIR) && (mp = dp->v_mountedhere)) { struct uthread *uth = (struct uthread *)get_bsdthread_info(current_thread()); mp->mnt_crossref++; vnode_unlock(dp); if (vfs_busy(mp, vbusyflags)) { mount_dropcrossref(mp, dp, 0); if (vbusyflags == LK_NOWAIT) { error = ENOENT; goto bad2; } goto check_mounted_on; } /* * XXX - if this is the last component of the * pathname, and it's either not a lookup operation * or the NOTRIGGER flag is set for the operation, * set a uthread flag to let VFS_ROOT() for autofs * know it shouldn't trigger a mount. */ if ((cnp->cn_flags & ISLASTCN) && (cnp->cn_nameiop != LOOKUP || (cnp->cn_flags & NOTRIGGER))) { uth->uu_notrigger = 1; dont_cache_mp = 1; } error = VFS_ROOT(mp, &tdp, ctx); /* XXX - clear the uthread flag */ uth->uu_notrigger = 0; /* * mount_dropcrossref does a vnode_put * on dp if the 3rd arg is non-zero */ mount_dropcrossref(mp, dp, 1); dp = NULL; vfs_unbusy(mp); if (error) { goto bad2; } ndp->ni_vp = dp = tdp; mounted_on_depth++; goto check_mounted_on; } vnode_unlock(dp); } #if CONFIG_MACF if (vfs_flags(vnode_mount(dp)) & MNT_MULTILABEL) { error = vnode_label(vnode_mount(dp), NULL, dp, NULL, VNODE_LABEL_NEEDREF, ctx); if (error) goto bad2; } #endif if (mounted_on_depth && !dont_cache_mp) { mp = mounted_on_dp->v_mountedhere; if (mp) { mount_lock(mp); mp->mnt_realrootvp_vid = dp->v_id; mp->mnt_realrootvp = dp; mp->mnt_generation = current_mount_generation; mount_unlock(mp); } } /* * Check for symbolic link */ if ((dp->v_type == VLNK) && ((cnp->cn_flags & FOLLOW) || trailing_slash || *ndp->ni_next == '/')) { cnp->cn_flags |= ISSYMLINK; return (0); } /* * Check for bogus trailing slashes. */ if (trailing_slash) { if (dp->v_type != VDIR) { error = ENOTDIR; goto bad2; } trailing_slash = 0; } nextname: /* * Not a symbolic link. If more pathname, * continue at next component, else return. */ if (*ndp->ni_next == '/') { cnp->cn_nameptr = ndp->ni_next + 1; ndp->ni_pathlen--; while (*cnp->cn_nameptr == '/') { cnp->cn_nameptr++; ndp->ni_pathlen--; } vnode_put(ndp->ni_dvp); cp = cnp->cn_nameptr; if (*cp == '\0') goto emptyname; /* * cache_lookup_path is now responsible for dropping io ref on dp * when it is called again in the dirloop. This ensures we hold * a ref on dp until we complete the next round of lookup. */ last_dp = dp; goto dirloop; } /* * Disallow directory write attempts on read-only file systems. */ if (rdonly && (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)) { error = EROFS; goto bad2; } if (cnp->cn_flags & SAVESTART) { /* * note that we already hold a reference * on both dp and ni_dvp, but for some reason * can't get another one... in this case we * need to do vnode_put on dp in 'bad2' */ if ( (vnode_get(ndp->ni_dvp)) ) { error = ENOENT; goto bad2; } ndp->ni_startdir = ndp->ni_dvp; } if (!wantparent && ndp->ni_dvp) { vnode_put(ndp->ni_dvp); ndp->ni_dvp = NULLVP; } if (cnp->cn_flags & AUDITVNPATH1) AUDIT_ARG(vnpath, dp, ARG_VNODE1); else if (cnp->cn_flags & AUDITVNPATH2) AUDIT_ARG(vnpath, dp, ARG_VNODE2); #if NAMEDRSRCFORK /* * Caller wants the resource fork. */ if ((cnp->cn_flags & CN_WANTSRSRCFORK) && (dp != NULLVP)) { vnode_t svp = NULLVP; enum nsoperation nsop; if (dp->v_type != VREG) { error = ENOENT; goto bad2; } switch (cnp->cn_nameiop) { case DELETE: nsop = NS_DELETE; break; case CREATE: nsop = NS_CREATE; break; case LOOKUP: /* Make sure our lookup of "/..namedfork/rsrc" is allowed. */ if (cnp->cn_flags & CN_ALLOWRSRCFORK) { nsop = NS_OPEN; } else { error = EPERM; goto bad2; } break; default: error = EPERM; goto bad2; } /* Ask the file system for the resource fork. */ error = vnode_getnamedstream(dp, &svp, XATTR_RESOURCEFORK_NAME, nsop, 0, ctx); /* During a create, it OK for stream vnode to be missing. */ if (error == ENOATTR || error == ENOENT) { error = (nsop == NS_CREATE) ? 0 : ENOENT; } if (error) { goto bad2; } /* The "parent" of the stream is the file. */ if (wantparent) { if (ndp->ni_dvp) { if (ndp->ni_cnd.cn_flags & FSNODELOCKHELD) { ndp->ni_cnd.cn_flags &= ~FSNODELOCKHELD; unlock_fsnode(ndp->ni_dvp, NULL); } vnode_put(ndp->ni_dvp); } ndp->ni_dvp = dp; } else { vnode_put(dp); } ndp->ni_vp = dp = svp; /* on create this may be null */ /* Restore the truncated pathname buffer (for audits). */ if (ndp->ni_pathlen == 1 && ndp->ni_next[0] == '\0') { ndp->ni_next[0] = '/'; } cnp->cn_flags &= ~MAKEENTRY; } #endif if (kdebug_enable) kdebug_lookup(dp, cnp); return (0); emptyname: cnp->cn_namelen = 0; /* * A degenerate name (e.g. / or "") which is a way of * talking about a directory, e.g. like "/." or ".". */ if (dp->v_type != VDIR) { error = ENOTDIR; goto bad; } if (cnp->cn_nameiop != LOOKUP) { error = EISDIR; goto bad; } if (wantparent) { /* * note that we already hold a reference * on dp, but for some reason can't * get another one... in this case we * need to do vnode_put on dp in 'bad' */ if ( (vnode_get(dp)) ) { error = ENOENT; goto bad; } ndp->ni_dvp = dp; } cnp->cn_flags &= ~ISDOTDOT; cnp->cn_flags |= ISLASTCN; ndp->ni_next = cp; ndp->ni_vp = dp; if (cnp->cn_flags & AUDITVNPATH1) AUDIT_ARG(vnpath, dp, ARG_VNODE1); else if (cnp->cn_flags & AUDITVNPATH2) AUDIT_ARG(vnpath, dp, ARG_VNODE2); if (cnp->cn_flags & SAVESTART) panic("lookup: SAVESTART"); return (0); bad2: if ((cnp->cn_flags & FSNODELOCKHELD)) { cnp->cn_flags &= ~FSNODELOCKHELD; unlock_fsnode(ndp->ni_dvp, NULL); } if (ndp->ni_dvp) vnode_put(ndp->ni_dvp); if (dp) vnode_put(dp); ndp->ni_vp = NULLVP; if (kdebug_enable) kdebug_lookup(dp, cnp); return (error); bad: if ((cnp->cn_flags & FSNODELOCKHELD)) { cnp->cn_flags &= ~FSNODELOCKHELD; unlock_fsnode(ndp->ni_dvp, NULL); } if (dp) vnode_put(dp); ndp->ni_vp = NULLVP; if (kdebug_enable) kdebug_lookup(dp, cnp); return (error); }