/*ARGSUSED*/ static void autofs_zone_destructor(zoneid_t zoneid, void *arg) { struct autofs_globals *fngp = arg; vnode_t *vp; if (fngp == NULL) return; ASSERT(fngp->fng_fnnode_count == 1); ASSERT(fngp->fng_unmount_threads == 0); /* * vn_alloc() initialized the rootnode with a count of 1; we need to * make this 0 to placate auto_freefnnode(). */ vp = fntovn(fngp->fng_rootfnnodep); ASSERT(vp->v_count == 1); vp->v_count--; auto_freefnnode(fngp->fng_rootfnnodep); mutex_destroy(&fngp->fng_unmount_threads_lock); kmem_free(fngp, sizeof (*fngp)); }
/* ARGSUSED */ static int auto_readdir( vnode_t *vp, uio_t *uiop, cred_t *cred, int *eofp, caller_context_t *ct, int flags) { struct autofs_rddirargs rda; autofs_rddirres rd; fnnode_t *fnp = vntofn(vp); fnnode_t *cfnp, *nfnp; dirent64_t *dp; ulong_t offset; ulong_t outcount = 0, count = 0; size_t namelen; ulong_t alloc_count; void *outbuf = NULL; fninfo_t *fnip = vfstofni(vp->v_vfsp); struct iovec *iovp; int error = 0; int reached_max = 0; int myeof = 0; int this_reclen; struct autofs_globals *fngp = vntofn(fnip->fi_rootvp)->fn_globals; AUTOFS_DPRINT((4, "auto_readdir vp=%p offset=%lld\n", (void *)vp, uiop->uio_loffset)); if (eofp != NULL) *eofp = 0; if (uiop->uio_iovcnt != 1) return (EINVAL); iovp = uiop->uio_iov; alloc_count = iovp->iov_len; gethrestime(&fnp->fn_atime); fnp->fn_ref_time = fnp->fn_atime.tv_sec; dp = outbuf = kmem_zalloc(alloc_count, KM_SLEEP); /* * Held when getdents calls VOP_RWLOCK.... */ ASSERT(RW_READ_HELD(&fnp->fn_rwlock)); if (uiop->uio_offset >= AUTOFS_DAEMONCOOKIE) { again: /* * Do readdir of daemon contents only * Drop readers lock and reacquire after reply. */ rw_exit(&fnp->fn_rwlock); bzero(&rd, sizeof (struct autofs_rddirres)); count = 0; rda.rda_map = fnip->fi_map; rda.rda_offset = (uint_t)uiop->uio_offset; rd.rd_rddir.rddir_entries = dp; rda.rda_count = rd.rd_rddir.rddir_size = (uint_t)alloc_count; rda.uid = crgetuid(cred); error = auto_calldaemon(fngp->fng_zoneid, AUTOFS_READDIR, xdr_autofs_rddirargs, &rda, xdr_autofs_rddirres, (void *)&rd, sizeof (autofs_rddirres), TRUE); /* * reacquire previously dropped lock */ rw_enter(&fnp->fn_rwlock, RW_READER); if (!error) { error = rd.rd_status; dp = rd.rd_rddir.rddir_entries; } if (error) { if (error == AUTOFS_SHUTDOWN) { /* * treat as empty directory */ error = 0; myeof = 1; if (eofp) *eofp = 1; } goto done; } if (rd.rd_rddir.rddir_size) { dirent64_t *odp = dp; /* next in output buffer */ dirent64_t *cdp = dp; /* current examined entry */ /* * Check for duplicates here */ do { this_reclen = cdp->d_reclen; if (auto_search(fnp, cdp->d_name, NULL, cred)) { /* * entry not found in kernel list, * include it in readdir output. * * If we are skipping entries. then * we need to copy this entry to the * correct position in the buffer * to be copied out. */ if (cdp != odp) bcopy(cdp, odp, (size_t)this_reclen); odp = nextdp(odp); outcount += this_reclen; } else { /* * Entry was found in the kernel * list. If it is the first entry * in this buffer, then just skip it */ if (odp == dp) { dp = nextdp(dp); odp = dp; } } count += this_reclen; cdp = (struct dirent64 *) ((char *)cdp + this_reclen); } while (count < rd.rd_rddir.rddir_size); if (outcount) error = uiomove(dp, outcount, UIO_READ, uiop); uiop->uio_offset = rd.rd_rddir.rddir_offset; } else { if (rd.rd_rddir.rddir_eof == 0) { /* * alloc_count not large enough for one * directory entry */ error = EINVAL; } } if (rd.rd_rddir.rddir_eof && !error) { myeof = 1; if (eofp) *eofp = 1; } if (!error && !myeof && outcount == 0) { /* * call daemon with new cookie, all previous * elements happened to be duplicates */ dp = outbuf; goto again; } goto done; } if (uiop->uio_offset == 0) { /* * first time: so fudge the . and .. */ this_reclen = DIRENT64_RECLEN(1); if (alloc_count < this_reclen) { error = EINVAL; goto done; } dp->d_ino = (ino64_t)fnp->fn_nodeid; dp->d_off = (off64_t)1; dp->d_reclen = (ushort_t)this_reclen; /* use strncpy(9f) to zero out uninitialized bytes */ (void) strncpy(dp->d_name, ".", DIRENT64_NAMELEN(this_reclen)); outcount += dp->d_reclen; dp = nextdp(dp); this_reclen = DIRENT64_RECLEN(2); if (alloc_count < outcount + this_reclen) { error = EINVAL; goto done; } dp->d_reclen = (ushort_t)this_reclen; dp->d_ino = (ino64_t)fnp->fn_parent->fn_nodeid; dp->d_off = (off64_t)2; /* use strncpy(9f) to zero out uninitialized bytes */ (void) strncpy(dp->d_name, "..", DIRENT64_NAMELEN(this_reclen)); outcount += dp->d_reclen; dp = nextdp(dp); } offset = 2; cfnp = fnp->fn_dirents; while (cfnp != NULL) { nfnp = cfnp->fn_next; offset = cfnp->fn_offset; if ((offset >= uiop->uio_offset) && (!(cfnp->fn_flags & MF_LOOKUP))) { int reclen; /* * include node only if its offset is greater or * equal to the one required and it is not in * transient state (not being looked-up) */ namelen = strlen(cfnp->fn_name); reclen = (int)DIRENT64_RECLEN(namelen); if (outcount + reclen > alloc_count) { reached_max = 1; break; } dp->d_reclen = (ushort_t)reclen; dp->d_ino = (ino64_t)cfnp->fn_nodeid; if (nfnp != NULL) { /* * get the offset of the next element */ dp->d_off = (off64_t)nfnp->fn_offset; } else { /* * This is the last element, make * offset one plus the current */ dp->d_off = (off64_t)cfnp->fn_offset + 1; } /* use strncpy(9f) to zero out uninitialized bytes */ (void) strncpy(dp->d_name, cfnp->fn_name, DIRENT64_NAMELEN(reclen)); outcount += dp->d_reclen; dp = nextdp(dp); } cfnp = nfnp; } if (outcount) error = uiomove(outbuf, outcount, UIO_READ, uiop); if (!error) { if (reached_max) { /* * This entry did not get added to the buffer on this, * call. We need to add it on the next call therefore * set uio_offset to this entry's offset. If there * wasn't enough space for one dirent, return EINVAL. */ uiop->uio_offset = offset; if (outcount == 0) error = EINVAL; } else if (autofs_nobrowse || auto_nobrowse_option(fnip->fi_opts) || (fnip->fi_flags & MF_DIRECT) || (fnp->fn_trigger != NULL) || (((vp->v_flag & VROOT) == 0) && ((fntovn(fnp->fn_parent))->v_flag & VROOT) && (fnp->fn_dirents == NULL))) { /* * done reading directory entries */ uiop->uio_offset = offset + 1; if (eofp) *eofp = 1; } else { /* * Need to get the rest of the entries from the daemon. */ uiop->uio_offset = AUTOFS_DAEMONCOOKIE; } } done: kmem_free(outbuf, alloc_count); AUTOFS_DPRINT((5, "auto_readdir vp=%p offset=%lld eof=%d\n", (void *)vp, uiop->uio_loffset, myeof)); return (error); }
static int auto_lookup( vnode_t *dvp, char *nm, vnode_t **vpp, pathname_t *pnp, int flags, vnode_t *rdir, cred_t *cred, caller_context_t *ct, int *direntflags, pathname_t *realpnp) { int error = 0; vnode_t *newvp = NULL; vfs_t *vfsp; fninfo_t *dfnip; fnnode_t *dfnp = NULL; fnnode_t *fnp = NULL; char *searchnm; int operation; /* either AUTOFS_LOOKUP or AUTOFS_MOUNT */ dfnip = vfstofni(dvp->v_vfsp); AUTOFS_DPRINT((3, "auto_lookup: dvp=%p (%s) name=%s\n", (void *)dvp, dfnip->fi_map, nm)); if (nm[0] == 0) { VN_HOLD(dvp); *vpp = dvp; return (0); } if (error = VOP_ACCESS(dvp, VEXEC, 0, cred, ct)) return (error); if (nm[0] == '.' && nm[1] == 0) { VN_HOLD(dvp); *vpp = dvp; return (0); } if (nm[0] == '.' && nm[1] == '.' && nm[2] == 0) { fnnode_t *pdfnp; pdfnp = (vntofn(dvp))->fn_parent; ASSERT(pdfnp != NULL); /* * Since it is legitimate to have the VROOT flag set for the * subdirectories of the indirect map in autofs filesystem, * rootfnnodep is checked against fnnode of dvp instead of * just checking whether VROOT flag is set in dvp */ if (pdfnp == pdfnp->fn_globals->fng_rootfnnodep) { vnode_t *vp; vfs_rlock_wait(dvp->v_vfsp); if (dvp->v_vfsp->vfs_flag & VFS_UNMOUNTED) { vfs_unlock(dvp->v_vfsp); return (EIO); } vp = dvp->v_vfsp->vfs_vnodecovered; VN_HOLD(vp); vfs_unlock(dvp->v_vfsp); error = VOP_LOOKUP(vp, nm, vpp, pnp, flags, rdir, cred, ct, direntflags, realpnp); VN_RELE(vp); return (error); } else { *vpp = fntovn(pdfnp); VN_HOLD(*vpp); return (0); } } top: dfnp = vntofn(dvp); searchnm = nm; operation = 0; ASSERT(vn_matchops(dvp, auto_vnodeops)); AUTOFS_DPRINT((3, "auto_lookup: dvp=%p dfnp=%p\n", (void *)dvp, (void *)dfnp)); /* * If a lookup or mount of this node is in progress, wait for it * to finish, and return whatever result it got. */ mutex_enter(&dfnp->fn_lock); if (dfnp->fn_flags & (MF_LOOKUP | MF_INPROG)) { mutex_exit(&dfnp->fn_lock); error = auto_wait4mount(dfnp); if (error == AUTOFS_SHUTDOWN) error = ENOENT; if (error == EAGAIN) goto top; if (error) return (error); } else mutex_exit(&dfnp->fn_lock); error = vn_vfsrlock_wait(dvp); if (error) return (error); vfsp = vn_mountedvfs(dvp); if (vfsp != NULL) { error = VFS_ROOT(vfsp, &newvp); vn_vfsunlock(dvp); if (!error) { error = VOP_LOOKUP(newvp, nm, vpp, pnp, flags, rdir, cred, ct, direntflags, realpnp); VN_RELE(newvp); } return (error); } vn_vfsunlock(dvp); rw_enter(&dfnp->fn_rwlock, RW_READER); error = auto_search(dfnp, nm, &fnp, cred); if (error) { if (dfnip->fi_flags & MF_DIRECT) { /* * direct map. */ if (dfnp->fn_dirents) { /* * Mount previously triggered. * 'nm' not found */ error = ENOENT; } else { /* * I need to contact the daemon to trigger * the mount. 'dfnp' will be the mountpoint. */ operation = AUTOFS_MOUNT; VN_HOLD(fntovn(dfnp)); fnp = dfnp; error = 0; } } else if (dvp == dfnip->fi_rootvp) { /* * 'dfnp' is the root of the indirect AUTOFS. */ if (rw_tryupgrade(&dfnp->fn_rwlock) == 0) { /* * Could not acquire writer lock, release * reader, and wait until available. We * need to search for 'nm' again, since we * had to release the lock before reacquiring * it. */ rw_exit(&dfnp->fn_rwlock); rw_enter(&dfnp->fn_rwlock, RW_WRITER); error = auto_search(dfnp, nm, &fnp, cred); } ASSERT(RW_WRITE_HELD(&dfnp->fn_rwlock)); if (error) { /* * create node being looked-up and request * mount on it. */ error = auto_enter(dfnp, nm, &fnp, kcred); if (!error) operation = AUTOFS_LOOKUP; } } else if ((dfnp->fn_dirents == NULL) && ((dvp->v_flag & VROOT) == 0) && ((fntovn(dfnp->fn_parent))->v_flag & VROOT)) { /* * dfnp is the actual 'mountpoint' of indirect map, * it is the equivalent of a direct mount, * ie, /home/'user1' */ operation = AUTOFS_MOUNT; VN_HOLD(fntovn(dfnp)); fnp = dfnp; error = 0; searchnm = dfnp->fn_name; } } if (error == EAGAIN) { rw_exit(&dfnp->fn_rwlock); goto top; } if (error) { rw_exit(&dfnp->fn_rwlock); return (error); } /* * We now have the actual fnnode we're interested in. * The 'MF_LOOKUP' indicates another thread is currently * performing a daemon lookup of this node, therefore we * wait for its completion. * The 'MF_INPROG' indicates another thread is currently * performing a daemon mount of this node, we wait for it * to be done if we are performing a MOUNT. We don't * wait for it if we are performing a LOOKUP. * We can release the reader/writer lock as soon as we acquire * the mutex, since the state of the lock can only change by * first acquiring the mutex. */ mutex_enter(&fnp->fn_lock); rw_exit(&dfnp->fn_rwlock); if ((fnp->fn_flags & MF_LOOKUP) || ((operation == AUTOFS_MOUNT) && (fnp->fn_flags & MF_INPROG))) { mutex_exit(&fnp->fn_lock); error = auto_wait4mount(fnp); VN_RELE(fntovn(fnp)); if (error == AUTOFS_SHUTDOWN) error = ENOENT; if (error && error != EAGAIN) return (error); goto top; } if (operation == 0) { /* * got the fnnode, check for any errors * on the previous operation on that node. */ error = fnp->fn_error; if ((error == EINTR) || (error == EAGAIN)) { /* * previous operation on this node was * not completed, do a lookup now. */ operation = AUTOFS_LOOKUP; } else { /* * previous operation completed. Return * a pointer to the node only if there was * no error. */ mutex_exit(&fnp->fn_lock); if (!error) *vpp = fntovn(fnp); else VN_RELE(fntovn(fnp)); return (error); } } /* * Since I got to this point, it means I'm the one * responsible for triggering the mount/look-up of this node. */ switch (operation) { case AUTOFS_LOOKUP: AUTOFS_BLOCK_OTHERS(fnp, MF_LOOKUP); fnp->fn_error = 0; mutex_exit(&fnp->fn_lock); error = auto_lookup_aux(fnp, searchnm, cred); if (!error) { /* * Return this vnode */ *vpp = fntovn(fnp); } else { /* * release our reference to this vnode * and return error */ VN_RELE(fntovn(fnp)); } break; case AUTOFS_MOUNT: AUTOFS_BLOCK_OTHERS(fnp, MF_INPROG); fnp->fn_error = 0; mutex_exit(&fnp->fn_lock); /* * auto_new_mount_thread fires up a new thread which * calls automountd finishing up the work */ auto_new_mount_thread(fnp, searchnm, cred); /* * At this point, we are simply another thread * waiting for the mount to complete */ error = auto_wait4mount(fnp); if (error == AUTOFS_SHUTDOWN) error = ENOENT; /* * now release our reference to this vnode */ VN_RELE(fntovn(fnp)); if (!error) goto top; break; default: auto_log(dfnp->fn_globals->fng_verbose, dfnp->fn_globals->fng_zoneid, CE_WARN, "auto_lookup: unknown operation %d", operation); } AUTOFS_DPRINT((5, "auto_lookup: name=%s *vpp=%p return=%d\n", nm, (void *)*vpp, error)); return (error); }
/* * Triggers the mount if needed. If the mount has been triggered by * another thread, it will wait for its return status, and return it. * Whether the mount is triggered by this thread, another thread, or * if the vnode was already covered, '*newvp' is a * VN_HELD vnode pointing to the root of the filesystem covering 'vp'. * If the node is not mounted on, and should not be mounted on, '*newvp' * will be NULL. * The calling routine may use '*newvp' to do the filesystem jump. */ static int auto_trigger_mount(vnode_t *vp, cred_t *cred, vnode_t **newvp) { fnnode_t *fnp = vntofn(vp); fninfo_t *fnip = vfstofni(vp->v_vfsp); vnode_t *dvp; vfs_t *vfsp; int delayed_ind; char name[AUTOFS_MAXPATHLEN]; int error; AUTOFS_DPRINT((4, "auto_trigger_mount: vp=%p\n", (void *)vp)); *newvp = NULL; /* * Cross-zone mount triggering is disallowed. */ if (fnip->fi_zoneid != getzoneid()) return (EPERM); /* Not owner of mount */ retry: error = 0; delayed_ind = 0; mutex_enter(&fnp->fn_lock); while (fnp->fn_flags & (MF_LOOKUP | MF_INPROG)) { /* * Mount or lookup in progress, * wait for it before proceeding. */ mutex_exit(&fnp->fn_lock); error = auto_wait4mount(fnp); if (error == AUTOFS_SHUTDOWN) { error = 0; goto done; } if (error && error != EAGAIN) goto done; error = 0; mutex_enter(&fnp->fn_lock); } /* * If the vfslock can't be acquired for the first time. * drop the fn_lock and retry next time in blocking mode. */ if (vn_vfswlock(vp)) { /* * Lock held by another thread. * Perform blocking by dropping the * fn_lock. */ mutex_exit(&fnp->fn_lock); error = vn_vfswlock_wait(vp); if (error) goto done; /* * Because fn_lock wasn't held, the state * of the trigger node might have changed. * Need to run through the checks on trigger * node again. */ vn_vfsunlock(vp); goto retry; } vfsp = vn_mountedvfs(vp); if (vfsp != NULL) { mutex_exit(&fnp->fn_lock); error = VFS_ROOT(vfsp, newvp); vn_vfsunlock(vp); goto done; } else { vn_vfsunlock(vp); if ((fnp->fn_flags & MF_MOUNTPOINT) && fnp->fn_trigger != NULL) { ASSERT(fnp->fn_dirents == NULL); mutex_exit(&fnp->fn_lock); /* * The filesystem that used to sit here has been * forcibly unmounted. Do our best to recover. * Try to unmount autofs subtree below this node * and retry the action. */ if (unmount_subtree(fnp, B_TRUE) != 0) { error = EIO; goto done; } goto retry; } } ASSERT(vp->v_type == VDIR); dvp = fntovn(fnp->fn_parent); if ((fnp->fn_dirents == NULL) && ((fnip->fi_flags & MF_DIRECT) == 0) && ((vp->v_flag & VROOT) == 0) && (dvp->v_flag & VROOT)) { /* * If the parent of this node is the root of an indirect * AUTOFS filesystem, this node is remountable. */ delayed_ind = 1; } if (delayed_ind || ((fnip->fi_flags & MF_DIRECT) && (fnp->fn_dirents == NULL))) { /* * Trigger mount since: * direct mountpoint with no subdirs or * delayed indirect. */ AUTOFS_BLOCK_OTHERS(fnp, MF_INPROG); fnp->fn_error = 0; mutex_exit(&fnp->fn_lock); if (delayed_ind) (void) strcpy(name, fnp->fn_name); else (void) strcpy(name, "."); fnp->fn_ref_time = gethrestime_sec(); auto_new_mount_thread(fnp, name, cred); /* * At this point we're simply another thread waiting * for the mount to finish. */ error = auto_wait4mount(fnp); if (error == EAGAIN) goto retry; if (error == AUTOFS_SHUTDOWN) { error = 0; goto done; } if (error == 0) { if (error = vn_vfsrlock_wait(vp)) goto done; /* Reacquire after dropping locks */ vfsp = vn_mountedvfs(vp); if (vfsp != NULL) { error = VFS_ROOT(vfsp, newvp); vn_vfsunlock(vp); } else { vn_vfsunlock(vp); goto retry; } } } else mutex_exit(&fnp->fn_lock); done: AUTOFS_DPRINT((5, "auto_trigger_mount: error=%d\n", error)); return (error); }