auto_vnops.c revision 84d17760f67f1fb6b9f93821deb22c90c87bea7f
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 1992, 2010, Oracle and/or its affiliates. All rights reserved.
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/proc.h>
#include <sys/vnode.h>
#include <sys/vfs.h>
#include <sys/vfs_opreg.h>
#include <sys/uio.h>
#include <sys/cred.h>
#include <sys/pathname.h>
#include <sys/dirent.h>
#include <sys/debug.h>
#include <sys/sysmacros.h>
#include <sys/tiuser.h>
#include <sys/cmn_err.h>
#include <sys/stat.h>
#include <sys/mode.h>
#include <sys/policy.h>
#include <rpc/types.h>
#include <rpc/auth.h>
#include <rpc/clnt.h>
#include <sys/fs/autofs.h>
#include <rpcsvc/autofs_prot.h>
#include <fs/fs_subr.h>
/*
* Vnode ops for autofs
*/
static int auto_open(vnode_t **, int, cred_t *, caller_context_t *);
static int auto_close(vnode_t *, int, int, offset_t, cred_t *,
caller_context_t *);
static int auto_getattr(vnode_t *, vattr_t *, int, cred_t *,
caller_context_t *);
static int auto_setattr(vnode_t *, vattr_t *, int, cred_t *,
caller_context_t *);
static int auto_access(vnode_t *, int, int, cred_t *, caller_context_t *);
static int auto_lookup(vnode_t *, char *, vnode_t **,
pathname_t *, int, vnode_t *, cred_t *, caller_context_t *, int *,
pathname_t *);
static int auto_create(vnode_t *, char *, vattr_t *, vcexcl_t,
int, vnode_t **, cred_t *, int, caller_context_t *, vsecattr_t *);
static int auto_remove(vnode_t *, char *, cred_t *, caller_context_t *, int);
static int auto_link(vnode_t *, vnode_t *, char *, cred_t *,
caller_context_t *, int);
static int auto_rename(vnode_t *, char *, vnode_t *, char *, cred_t *,
caller_context_t *, int);
static int auto_mkdir(vnode_t *, char *, vattr_t *, vnode_t **, cred_t *,
caller_context_t *, int, vsecattr_t *);
static int auto_rmdir(vnode_t *, char *, vnode_t *, cred_t *,
caller_context_t *, int);
static int auto_readdir(vnode_t *, uio_t *, cred_t *, int *,
caller_context_t *, int);
static int auto_symlink(vnode_t *, char *, vattr_t *, char *, cred_t *,
caller_context_t *, int);
static int auto_readlink(vnode_t *, struct uio *, cred_t *,
caller_context_t *);
static int auto_fsync(vnode_t *, int, cred_t *, caller_context_t *);
static void auto_inactive(vnode_t *, cred_t *, caller_context_t *);
static int auto_rwlock(vnode_t *, int, caller_context_t *);
static void auto_rwunlock(vnode_t *vp, int, caller_context_t *);
static int auto_seek(vnode_t *vp, offset_t, offset_t *, caller_context_t *);
static int auto_trigger_mount(vnode_t *, cred_t *, vnode_t **);
vnodeops_t *auto_vnodeops;
const fs_operation_def_t auto_vnodeops_template[] = {
VOPNAME_OPEN, { .vop_open = auto_open },
VOPNAME_CLOSE, { .vop_close = auto_close },
VOPNAME_GETATTR, { .vop_getattr = auto_getattr },
VOPNAME_SETATTR, { .vop_setattr = auto_setattr },
VOPNAME_ACCESS, { .vop_access = auto_access },
VOPNAME_LOOKUP, { .vop_lookup = auto_lookup },
VOPNAME_CREATE, { .vop_create = auto_create },
VOPNAME_REMOVE, { .vop_remove = auto_remove },
VOPNAME_LINK, { .vop_link = auto_link },
VOPNAME_RENAME, { .vop_rename = auto_rename },
VOPNAME_MKDIR, { .vop_mkdir = auto_mkdir },
VOPNAME_RMDIR, { .vop_rmdir = auto_rmdir },
VOPNAME_READDIR, { .vop_readdir = auto_readdir },
VOPNAME_SYMLINK, { .vop_symlink = auto_symlink },
VOPNAME_READLINK, { .vop_readlink = auto_readlink },
VOPNAME_FSYNC, { .vop_fsync = auto_fsync },
VOPNAME_INACTIVE, { .vop_inactive = auto_inactive },
VOPNAME_RWLOCK, { .vop_rwlock = auto_rwlock },
VOPNAME_RWUNLOCK, { .vop_rwunlock = auto_rwunlock },
VOPNAME_SEEK, { .vop_seek = auto_seek },
VOPNAME_FRLOCK, { .error = fs_error },
VOPNAME_DISPOSE, { .error = fs_error },
VOPNAME_SHRLOCK, { .error = fs_error },
VOPNAME_VNEVENT, { .vop_vnevent = fs_vnevent_support },
NULL, NULL
};
/* ARGSUSED */
static int
auto_open(vnode_t **vpp, int flag, cred_t *cred, caller_context_t *ct)
{
vnode_t *newvp;
int error;
AUTOFS_DPRINT((4, "auto_open: *vpp=%p\n", (void *)*vpp));
error = auto_trigger_mount(*vpp, cred, &newvp);
if (error)
goto done;
if (newvp != NULL) {
/*
* Node is now mounted on.
*/
VN_RELE(*vpp);
*vpp = newvp;
error = VOP_ACCESS(*vpp, VREAD, 0, cred, ct);
if (!error)
error = VOP_OPEN(vpp, flag, cred, ct);
}
done:
AUTOFS_DPRINT((5, "auto_open: *vpp=%p error=%d\n", (void *)*vpp,
error));
return (error);
}
/* ARGSUSED */
static int
auto_close(
vnode_t *vp,
int flag,
int count,
offset_t offset,
cred_t *cred,
caller_context_t *ct)
{
return (0);
}
static int
auto_getattr(
vnode_t *vp,
vattr_t *vap,
int flags,
cred_t *cred,
caller_context_t *ct)
{
fnnode_t *fnp = vntofn(vp);
vnode_t *newvp;
vfs_t *vfsp;
int error;
AUTOFS_DPRINT((4, "auto_getattr vp %p\n", (void *)vp));
if (flags & ATTR_TRIGGER) {
/*
* Pre-trigger the mount
*/
error = auto_trigger_mount(vp, cred, &newvp);
if (error)
return (error);
if (newvp == NULL)
goto defattr;
if (error = vn_vfsrlock_wait(vp)) {
VN_RELE(newvp);
return (error);
}
vfsp = newvp->v_vfsp;
VN_RELE(newvp);
} else {
/*
* Recursive auto_getattr/mount; go to the vfsp == NULL
* case.
*/
if (vn_vfswlock_held(vp))
goto defattr;
if (error = vn_vfsrlock_wait(vp))
return (error);
vfsp = vn_mountedvfs(vp);
}
if (vfsp != NULL) {
/*
* Node is mounted on.
*/
error = VFS_ROOT(vfsp, &newvp);
vn_vfsunlock(vp);
if (error)
return (error);
mutex_enter(&fnp->fn_lock);
if (fnp->fn_seen == newvp && fnp->fn_thread == curthread) {
/*
* Recursive auto_getattr(); just release newvp and drop
* into the vfsp == NULL case.
*/
mutex_exit(&fnp->fn_lock);
VN_RELE(newvp);
} else {
while (fnp->fn_thread && fnp->fn_thread != curthread) {
fnp->fn_flags |= MF_ATTR_WAIT;
cv_wait(&fnp->fn_cv_mount, &fnp->fn_lock);
}
fnp->fn_thread = curthread;
fnp->fn_seen = newvp;
mutex_exit(&fnp->fn_lock);
error = VOP_GETATTR(newvp, vap, flags, cred, ct);
VN_RELE(newvp);
mutex_enter(&fnp->fn_lock);
fnp->fn_seen = 0;
fnp->fn_thread = 0;
if (fnp->fn_flags & MF_ATTR_WAIT) {
fnp->fn_flags &= ~MF_ATTR_WAIT;
cv_broadcast(&fnp->fn_cv_mount);
}
mutex_exit(&fnp->fn_lock);
return (error);
}
} else {
vn_vfsunlock(vp);
}
defattr:
ASSERT(vp->v_type == VDIR || vp->v_type == VLNK);
vap->va_uid = 0;
vap->va_gid = 0;
vap->va_nlink = fnp->fn_linkcnt;
vap->va_nodeid = (u_longlong_t)fnp->fn_nodeid;
vap->va_size = fnp->fn_size;
vap->va_atime = fnp->fn_atime;
vap->va_mtime = fnp->fn_mtime;
vap->va_ctime = fnp->fn_ctime;
vap->va_type = vp->v_type;
vap->va_mode = fnp->fn_mode;
vap->va_fsid = vp->v_vfsp->vfs_dev;
vap->va_rdev = 0;
vap->va_blksize = MAXBSIZE;
vap->va_nblocks = (fsblkcnt64_t)btod(vap->va_size);
vap->va_seq = 0;
return (0);
}
/*ARGSUSED4*/
static int
auto_setattr(
vnode_t *vp,
struct vattr *vap,
int flags,
cred_t *cred,
caller_context_t *ct)
{
vnode_t *newvp;
int error;
AUTOFS_DPRINT((4, "auto_setattr vp %p\n", (void *)vp));
if (error = auto_trigger_mount(vp, cred, &newvp))
goto done;
if (newvp != NULL) {
/*
* Node is mounted on.
*/
if (vn_is_readonly(newvp))
error = EROFS;
else
error = VOP_SETATTR(newvp, vap, flags, cred, ct);
VN_RELE(newvp);
} else
error = ENOSYS;
done:
AUTOFS_DPRINT((5, "auto_setattr: error=%d\n", error));
return (error);
}
/* ARGSUSED */
static int
auto_access(
vnode_t *vp,
int mode,
int flags,
cred_t *cred,
caller_context_t *ct)
{
fnnode_t *fnp = vntofn(vp);
vnode_t *newvp;
int error;
AUTOFS_DPRINT((4, "auto_access: vp=%p\n", (void *)vp));
if (error = auto_trigger_mount(vp, cred, &newvp))
goto done;
if (newvp != NULL) {
/*
* Node is mounted on.
*/
error = VOP_ACCESS(newvp, mode, 0, cred, ct);
VN_RELE(newvp);
} else {
int shift = 0;
/*
* really interested in the autofs node, check the
* access on it
*/
ASSERT(error == 0);
if (crgetuid(cred) != fnp->fn_uid) {
shift += 3;
if (groupmember(fnp->fn_gid, cred) == 0)
shift += 3;
}
error = secpolicy_vnode_access2(cred, vp, fnp->fn_uid,
fnp->fn_mode << shift, mode);
}
done:
AUTOFS_DPRINT((5, "auto_access: error=%d\n", error));
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);
}
static int
auto_create(
vnode_t *dvp,
char *nm,
vattr_t *va,
vcexcl_t excl,
int mode,
vnode_t **vpp,
cred_t *cred,
int flag,
caller_context_t *ct,
vsecattr_t *vsecp)
{
vnode_t *newvp;
int error;
AUTOFS_DPRINT((4, "auto_create dvp %p nm %s\n", (void *)dvp, nm));
if (error = auto_trigger_mount(dvp, cred, &newvp))
goto done;
if (newvp != NULL) {
/*
* Node is now mounted on.
*/
if (vn_is_readonly(newvp))
error = EROFS;
else
error = VOP_CREATE(newvp, nm, va, excl,
mode, vpp, cred, flag, ct, vsecp);
VN_RELE(newvp);
} else
error = ENOSYS;
done:
AUTOFS_DPRINT((5, "auto_create: error=%d\n", error));
return (error);
}
static int
auto_remove(
vnode_t *dvp,
char *nm,
cred_t *cred,
caller_context_t *ct,
int flags)
{
vnode_t *newvp;
int error;
AUTOFS_DPRINT((4, "auto_remove dvp %p nm %s\n", (void *)dvp, nm));
if (error = auto_trigger_mount(dvp, cred, &newvp))
goto done;
if (newvp != NULL) {
/*
* Node is now mounted on.
*/
if (vn_is_readonly(newvp))
error = EROFS;
else
error = VOP_REMOVE(newvp, nm, cred, ct, flags);
VN_RELE(newvp);
} else
error = ENOSYS;
done:
AUTOFS_DPRINT((5, "auto_remove: error=%d\n", error));
return (error);
}
static int
auto_link(
vnode_t *tdvp,
vnode_t *svp,
char *nm,
cred_t *cred,
caller_context_t *ct,
int flags)
{
vnode_t *newvp;
int error;
AUTOFS_DPRINT((4, "auto_link tdvp %p svp %p nm %s\n", (void *)tdvp,
(void *)svp, nm));
if (error = auto_trigger_mount(tdvp, cred, &newvp))
goto done;
if (newvp == NULL) {
/*
* an autonode can not be a link to another node
*/
error = ENOSYS;
goto done;
}
if (vn_is_readonly(newvp)) {
error = EROFS;
VN_RELE(newvp);
goto done;
}
if (vn_matchops(svp, auto_vnodeops)) {
/*
* source vp can't be an autonode
*/
error = ENOSYS;
VN_RELE(newvp);
goto done;
}
error = VOP_LINK(newvp, svp, nm, cred, ct, flags);
VN_RELE(newvp);
done:
AUTOFS_DPRINT((5, "auto_link error=%d\n", error));
return (error);
}
static int
auto_rename(
vnode_t *odvp,
char *onm,
vnode_t *ndvp,
char *nnm,
cred_t *cr,
caller_context_t *ct,
int flags)
{
vnode_t *o_newvp, *n_newvp;
int error;
AUTOFS_DPRINT((4, "auto_rename odvp %p onm %s to ndvp %p nnm %s\n",
(void *)odvp, onm, (void *)ndvp, nnm));
/*
* we know odvp is an autonode, otherwise this function
* could not have ever been called.
*/
ASSERT(vn_matchops(odvp, auto_vnodeops));
if (error = auto_trigger_mount(odvp, cr, &o_newvp))
goto done;
if (o_newvp == NULL) {
/*
* can't rename an autonode
*/
error = ENOSYS;
goto done;
}
if (vn_matchops(ndvp, auto_vnodeops)) {
/*
* directory is AUTOFS, need to trigger the
* mount of the real filesystem.
*/
if (error = auto_trigger_mount(ndvp, cr, &n_newvp)) {
VN_RELE(o_newvp);
goto done;
}
if (n_newvp == NULL) {
/*
* target can't be an autonode
*/
error = ENOSYS;
VN_RELE(o_newvp);
goto done;
}
} else {
/*
* destination directory mount had been
* triggered prior to the call to this function.
*/
n_newvp = ndvp;
}
ASSERT(!vn_matchops(n_newvp, auto_vnodeops));
if (vn_is_readonly(n_newvp)) {
error = EROFS;
VN_RELE(o_newvp);
if (n_newvp != ndvp)
VN_RELE(n_newvp);
goto done;
}
error = VOP_RENAME(o_newvp, onm, n_newvp, nnm, cr, ct, flags);
VN_RELE(o_newvp);
if (n_newvp != ndvp)
VN_RELE(n_newvp);
done:
AUTOFS_DPRINT((5, "auto_rename error=%d\n", error));
return (error);
}
static int
auto_mkdir(
vnode_t *dvp,
char *nm,
vattr_t *va,
vnode_t **vpp,
cred_t *cred,
caller_context_t *ct,
int flags,
vsecattr_t *vsecp)
{
vnode_t *newvp;
int error;
AUTOFS_DPRINT((4, "auto_mkdir dvp %p nm %s\n", (void *)dvp, nm));
if (error = auto_trigger_mount(dvp, cred, &newvp))
goto done;
if (newvp != NULL) {
/*
* Node is now mounted on.
*/
if (vn_is_readonly(newvp))
error = EROFS;
else
error = VOP_MKDIR(newvp, nm, va, vpp, cred, ct,
flags, vsecp);
VN_RELE(newvp);
} else
error = ENOSYS;
done:
AUTOFS_DPRINT((5, "auto_mkdir: error=%d\n", error));
return (error);
}
static int
auto_rmdir(
vnode_t *dvp,
char *nm,
vnode_t *cdir,
cred_t *cred,
caller_context_t *ct,
int flags)
{
vnode_t *newvp;
int error;
AUTOFS_DPRINT((4, "auto_rmdir: vp=%p nm=%s\n", (void *)dvp, nm));
if (error = auto_trigger_mount(dvp, cred, &newvp))
goto done;
if (newvp != NULL) {
/*
* Node is now mounted on.
*/
if (vn_is_readonly(newvp))
error = EROFS;
else
error = VOP_RMDIR(newvp, nm, cdir, cred, ct, flags);
VN_RELE(newvp);
} else
error = ENOSYS;
done:
AUTOFS_DPRINT((5, "auto_rmdir: error=%d\n", error));
return (error);
}
static int autofs_nobrowse = 0;
#ifdef nextdp
#undef nextdp
#endif
#define nextdp(dp) ((struct dirent64 *)((char *)(dp) + (dp)->d_reclen))
/* 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_symlink(
vnode_t *dvp,
char *lnknm, /* new entry */
vattr_t *tva,
char *tnm, /* existing entry */
cred_t *cred,
caller_context_t *ct,
int flags)
{
vnode_t *newvp;
int error;
AUTOFS_DPRINT((4, "auto_symlink: dvp=%p lnknm=%s tnm=%s\n",
(void *)dvp, lnknm, tnm));
if (error = auto_trigger_mount(dvp, cred, &newvp))
goto done;
if (newvp != NULL) {
/*
* Node is mounted on.
*/
if (vn_is_readonly(newvp))
error = EROFS;
else
error = VOP_SYMLINK(newvp, lnknm, tva, tnm, cred,
ct, flags);
VN_RELE(newvp);
} else
error = ENOSYS;
done:
AUTOFS_DPRINT((5, "auto_symlink: error=%d\n", error));
return (error);
}
/* ARGSUSED */
static int
auto_readlink(vnode_t *vp, struct uio *uiop, cred_t *cr, caller_context_t *ct)
{
fnnode_t *fnp = vntofn(vp);
int error;
timestruc_t now;
AUTOFS_DPRINT((4, "auto_readlink: vp=%p\n", (void *)vp));
gethrestime(&now);
fnp->fn_ref_time = now.tv_sec;
if (vp->v_type != VLNK)
error = EINVAL;
else {
ASSERT(!(fnp->fn_flags & (MF_INPROG | MF_LOOKUP)));
fnp->fn_atime = now;
error = uiomove(fnp->fn_symlink, MIN(fnp->fn_symlinklen,
uiop->uio_resid), UIO_READ, uiop);
}
AUTOFS_DPRINT((5, "auto_readlink: error=%d\n", error));
return (error);
}
/* ARGSUSED */
static int
auto_fsync(vnode_t *cp, int syncflag, cred_t *cred, caller_context_t *ct)
{
return (0);
}
/* ARGSUSED */
static void
auto_inactive(vnode_t *vp, cred_t *cred, caller_context_t *ct)
{
fnnode_t *fnp = vntofn(vp);
fnnode_t *dfnp = fnp->fn_parent;
int count;
AUTOFS_DPRINT((4, "auto_inactive: vp=%p v_count=%u fn_link=%d\n",
(void *)vp, vp->v_count, fnp->fn_linkcnt));
/*
* The rwlock should not be already held by this thread.
* The assert relies on the fact that the owner field is cleared
* when the lock is released.
*/
ASSERT(dfnp != NULL);
ASSERT(rw_owner(&dfnp->fn_rwlock) != curthread);
rw_enter(&dfnp->fn_rwlock, RW_WRITER);
mutex_enter(&vp->v_lock);
ASSERT(vp->v_count > 0);
count = --vp->v_count;
mutex_exit(&vp->v_lock);
if (count == 0) {
/*
* Free only if node has no subdirectories.
*/
if (fnp->fn_linkcnt == 1) {
auto_disconnect(dfnp, fnp);
rw_exit(&dfnp->fn_rwlock);
auto_freefnnode(fnp);
AUTOFS_DPRINT((5, "auto_inactive: (exit) vp=%p freed\n",
(void *)vp));
return;
}
}
rw_exit(&dfnp->fn_rwlock);
AUTOFS_DPRINT((5, "auto_inactive: (exit) vp=%p v_count=%u fn_link=%d\n",
(void *)vp, vp->v_count, fnp->fn_linkcnt));
}
/* ARGSUSED2 */
static int
auto_rwlock(vnode_t *vp, int write_lock, caller_context_t *ct)
{
fnnode_t *fnp = vntofn(vp);
if (write_lock)
rw_enter(&fnp->fn_rwlock, RW_WRITER);
else
rw_enter(&fnp->fn_rwlock, RW_READER);
return (write_lock);
}
/* ARGSUSED */
static void
auto_rwunlock(vnode_t *vp, int write_lock, caller_context_t *ct)
{
fnnode_t *fnp = vntofn(vp);
rw_exit(&fnp->fn_rwlock);
}
/* ARGSUSED */
static int
auto_seek(
struct vnode *vp,
offset_t ooff,
offset_t *noffp,
caller_context_t *ct)
{
/*
* Return 0 unconditionally, since we expect
* a VDIR all the time
*/
return (0);
}
/*
* 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);
}