#pragma ident "@(#)auto_vfsops.c 1.58 06/03/24 SMI"
#include <sys/param.h>
#include <sys/queue.h>
#include <sys/systm.h>
#include <sys/namei.h>
#include <sys/filedesc.h>
#include <sys/proc.h>
#include <sys/kernel.h>
#include <mach/machine/vm_types.h>
#include <sys/vnode.h>
#include <sys/vfs_context.h>
#include <sys/socket.h>
#include <sys/mount.h>
#include <sys/errno.h>
#include <sys/malloc.h>
#include <sys/attr.h>
#include <sys/syslog.h>
#include <sys/sysctl.h>
#include <sys/conf.h>
#include <miscfs/devfs/devfs.h>
#include <kern/locks.h>
#include <kern/assert.h>
#include "autofs.h"
#include "autofs_kern.h"
lck_grp_t *autofs_lck_grp;
static lck_mtx_t *autofs_global_lock;
lck_mtx_t *autofs_nodeid_lock;
static u_int autofs_mounts;
static int auto_mount(mount_t, vnode_t, user_addr_t, vfs_context_t);
static int auto_start(mount_t, int, vfs_context_t);
static int auto_unmount(mount_t, int, vfs_context_t);
static int auto_root(mount_t, vnode_t *, vfs_context_t);
static int auto_vfs_getattr(mount_t, struct vfs_attr *, vfs_context_t);
static int auto_sync(mount_t, int, vfs_context_t);
static int auto_vget(mount_t, ino64_t, vnode_t *, vfs_context_t);
static int autofs_sysctl(int *, u_int, user_addr_t, size_t *, user_addr_t, size_t, vfs_context_t);
static struct vfsops autofs_vfsops = {
auto_mount,
auto_start,
auto_unmount,
auto_root,
NULL,
auto_vfs_getattr,
auto_sync,
auto_vget,
NULL,
NULL,
NULL,
autofs_sysctl
};
extern struct vnodeopv_desc autofsfs_vnodeop_opv_desc;
struct vnodeopv_desc * autofs_vnodeop_opv_descs[] =
{&autofsfs_vnodeop_opv_desc, NULL};
struct vfs_fsentry autofs_fsentry = {
&autofs_vfsops,
1,
autofs_vnodeop_opv_descs,
0,
MNTTYPE_AUTOFS,
VFS_TBLTHREADSAFE | VFS_TBLNOTYPENUM | VFS_TBL64BITREADY,
{ NULL, NULL }
};
static vfstable_t auto_vfsconf;
static struct autofs_globals *fngp;
static struct autofs_globals *
autofs_zone_init(void)
{
struct autofs_globals *fngp = NULL;
fnnode_t *fnp = NULL;
static const char fnnode_name[] = "root_fnnode";
struct timeval now;
kern_return_t ret;
MALLOC(fngp, struct autofs_globals *, sizeof (*fngp), M_AUTOFS,
M_WAITOK);
if (fngp == NULL) {
log(LOG_ERR, "autofs_zone_init: Couldn't create autofs globals structure\n");
goto fail;
}
bzero(fngp, sizeof (*fngp));
MALLOC(fnp, fnnode_t *, sizeof(fnnode_t), M_AUTOFS, M_WAITOK);
if (fnp == NULL) {
log(LOG_ERR, "autofs_zone_init: Couldn't create autofs global fnnode\n");
goto fail;
}
bzero(fnp, sizeof(*fnp));
fnp->fn_namelen = sizeof fnnode_name;
MALLOC(fnp->fn_name, char *, fnp->fn_namelen + 1, M_AUTOFS, M_WAITOK);
if (fnp->fn_name == NULL) {
log(LOG_ERR, "autofs_zone_init: Couldn't create autofs global fnnode name\n");
goto fail;
}
bcopy(fnnode_name, fnp->fn_name, sizeof fnnode_name);
fnp->fn_name[sizeof fnnode_name] = '\0';
fnp->fn_mode = AUTOFS_MODE;
microtime(&now);
fnp->fn_crtime = fnp->fn_atime = fnp->fn_mtime = fnp->fn_ctime = now;
fnp->fn_ref_time = now.tv_sec;
fnp->fn_nodeid = 1;
fnp->fn_globals = fngp;
fnp->fn_lock = lck_mtx_alloc_init(autofs_lck_grp, NULL);
if (fnp->fn_lock == NULL) {
log(LOG_ERR, "autofs_zone_init: Couldn't create autofs global fnnode mutex\n");
goto fail;
}
fnp->fn_rwlock = lck_rw_alloc_init(autofs_lck_grp, NULL);
if (fnp->fn_rwlock == NULL) {
log(LOG_ERR, "autofs_zone_init: Couldn't create autofs global fnnode rwlock\n");
goto fail;
}
fngp->fng_rootfnnodep = fnp;
fngp->fng_fnnode_count = 1;
fngp->fng_printed_not_running_msg = 0;
fngp->fng_unmount_threads_lock = lck_mtx_alloc_init(autofs_lck_grp,
LCK_ATTR_NULL);
if (fngp->fng_unmount_threads_lock == NULL) {
log(LOG_ERR, "autofs_zone_init: Couldn't create autofs global unmount threads lock\n");
goto fail;
}
fngp->fng_unmount_threads = 0;
fngp->fng_terminate_do_unmount_thread = 0;
fngp->fng_do_unmount_thread_terminated = 0;
fngp->fng_flush_notification_lock = lck_mtx_alloc_init(autofs_lck_grp,
LCK_ATTR_NULL);
if (fngp->fng_flush_notification_lock == NULL) {
log(LOG_ERR, "autofs_zone_init: Couldn't create autofs global flush notification lock\n");
goto fail;
}
ret = auto_new_thread(auto_do_unmount, fngp);
if (ret != KERN_SUCCESS) {
log(LOG_ERR, "autofs_zone_init: Couldn't create unmounter thread, status 0x%08x", ret);
goto fail;
}
return (fngp);
fail:
if (fnp != NULL) {
if (fnp->fn_rwlock != NULL)
lck_rw_free(fnp->fn_rwlock, autofs_lck_grp);
if (fnp->fn_lock != NULL)
lck_mtx_free(fnp->fn_lock, autofs_lck_grp);
if (fnp->fn_name != NULL)
FREE(fnp->fn_name, M_AUTOFS);
FREE(fnp, M_AUTOFS);
}
if (fngp != NULL) {
if (fngp->fng_flush_notification_lock != NULL)
lck_mtx_free(fngp->fng_flush_notification_lock,
autofs_lck_grp);
if (fngp->fng_unmount_threads_lock != NULL)
lck_mtx_free(fngp->fng_unmount_threads_lock,
autofs_lck_grp);
FREE(fngp, M_AUTOFS);
}
return (NULL);
}
static char *
strstr(const char *s, const char *find)
{
char c, sc;
size_t len;
if ((c = *find++) != 0) {
len = strlen(find);
do {
do {
if ((sc = *s++) == 0)
return (NULL);
} while (sc != c);
} while (strncmp(s, find, len) != 0);
s--;
}
return ((char *)s);
}
static const struct mntopt restropts[] = {
RESTRICTED_MNTOPTS
};
#define NROPTS ((sizeof (restropts)/sizeof (restropts[0])) - 1)
static u_int32_t
optionisset(mount_t mp, const struct mntopt *opt)
{
u_int32_t flags;
flags = vfs_flags(mp) & opt->m_flag;
if (opt->m_inverse)
return !flags;
else
return flags;
}
static int
autofs_restrict_opts(mount_t mp, char *buf, size_t maxlen, size_t *curlen)
{
fninfo_t *fnip = vfstofni(mp);
int i;
char *p;
size_t len = *curlen - 1;
if (!(fnip->fi_mntflags & AUTOFS_MNT_RESTRICT))
return (0);
for (i = 0; i < NROPTS; i++) {
size_t olen = strlen(restropts[i].m_option);
if ((i == 0 || optionisset(mp, &restropts[i])) &&
((p = strstr(buf, restropts[i].m_option)) == NULL ||
!((p == buf || p[-1] == ',') &&
(p[olen] == '\0' || p[olen] == ',')))) {
if (len + olen + 1 > maxlen)
return (-1);
if (*buf != '\0')
buf[len++] = ',';
bcopy(restropts[i].m_option, &buf[len], olen);
len += olen;
}
}
if (len + 1 > maxlen)
return (-1);
buf[len++] = '\0';
*curlen = len;
return (0);
}
static int
auto_mount(mount_t mp, vnode_t devvp, user_addr_t data, vfs_context_t context)
{
int error;
size_t len;
int argsvers;
struct autofs_args_64 args;
fninfo_t *fnip = NULL;
vnode_t rootvp = NULL;
fnnode_t *rootfnp = NULL;
char strbuff[PATH_MAX+1];
uint64_t flags;
#ifdef DEBUG
lck_attr_t *lckattr;
#endif
AUTOFS_DPRINT((4, "auto_mount: mp %p devvp %p\n", mp, devvp));
lck_mtx_lock(autofs_global_lock);
if (fngp == NULL)
fngp = autofs_zone_init();
lck_mtx_unlock(autofs_global_lock);
if (fngp == NULL)
return (ENOMEM);
error = copyin(data, (caddr_t)&argsvers, sizeof (argsvers));
if (error)
return (error);
switch (argsvers) {
case 1:
if (vfs_context_is64bit(context))
error = copyin(data, &args, sizeof (args));
else {
struct autofs_args args32;
error = copyin(data, &args32, sizeof (args32));
if (error == 0) {
args.path = CAST_USER_ADDR_T(args32.path);
args.opts = CAST_USER_ADDR_T(args32.opts);
args.map = CAST_USER_ADDR_T(args32.map);
args.subdir = CAST_USER_ADDR_T(args32.subdir);
args.key = CAST_USER_ADDR_T(args32.key);
args.mntflags = args32.mntflags;
args.mount_to = args32.mount_to;
args.mach_to = args32.mach_to;
args.direct = args32.direct;
args.trigger = args32.trigger;
}
}
if (error)
return (error);
break;
default:
return (EINVAL);
}
flags = vfs_flags(mp) & MNT_CMDFLAGS;
if (flags & MNT_UPDATE)
return (EINVAL);
MALLOC(fnip, fninfo_t *, sizeof(*fnip), M_AUTOFS, M_WAITOK);
if (fnip == NULL)
return (ENOMEM);
bzero(fnip, sizeof(*fnip));
fnip->fi_flags |= MF_DONTTRIGGER;
fnip->fi_mount_to = args.mount_to;
fnip->fi_mach_to = args.mach_to;
vfs_getnewfsid(mp);
vfs_setfsprivate(mp, fnip);
error = copyinstr(args.path, strbuff, sizeof (strbuff), &len);
if (error) {
error = EFAULT;
goto errout;
}
MALLOC(fnip->fi_path, char *, len, M_AUTOFS, M_WAITOK);
if (fnip->fi_path == NULL) {
error = ENOMEM;
goto errout;
}
fnip->fi_pathlen = (int)len;
bcopy(strbuff, fnip->fi_path, len);
error = copyinstr(args.opts, strbuff, sizeof (strbuff), &len);
if (error != 0)
goto errout;
if (autofs_restrict_opts(mp, strbuff, sizeof (strbuff), &len) != 0) {
error = EFAULT;
goto errout;
}
MALLOC(fnip->fi_opts, char *, len, M_AUTOFS, M_WAITOK);
if (fnip->fi_opts == NULL) {
error = ENOMEM;
goto errout;
}
fnip->fi_optslen = (int)len;
bcopy(strbuff, fnip->fi_opts, len);
error = copyinstr(args.map, strbuff, sizeof (strbuff), &len);
if (error)
goto errout;
MALLOC(fnip->fi_map, char *, len, M_AUTOFS, M_WAITOK);
if (fnip->fi_map == NULL) {
error = ENOMEM;
goto errout;
}
fnip->fi_maplen = (int)len;
bcopy(strbuff, fnip->fi_map, len);
if (args.trigger) {
strncpy(vfs_statfs(mp)->f_mntfromname, "trigger",
sizeof(vfs_statfs(mp)->f_mntfromname)-1);
vfs_statfs(mp)->f_mntfromname[sizeof(vfs_statfs(mp)->f_mntfromname)-1] = (char)0;
} else {
snprintf(vfs_statfs(mp)->f_mntfromname, sizeof(vfs_statfs(mp)->f_mntfromname),
"map %.*s", fnip->fi_maplen, fnip->fi_map);
}
error = copyinstr(args.subdir, strbuff, sizeof (strbuff), &len);
if (error)
goto errout;
MALLOC(fnip->fi_subdir, char *, len, M_AUTOFS, M_WAITOK);
if (fnip->fi_subdir == NULL) {
error = ENOMEM;
goto errout;
}
fnip->fi_subdirlen = (int)len;
bcopy(strbuff, fnip->fi_subdir, len);
error = copyinstr(args.key, strbuff, sizeof (strbuff), &len);
if (error)
goto errout;
MALLOC(fnip->fi_key, char *, len, M_AUTOFS, M_WAITOK);
if (fnip->fi_key == NULL) {
error = ENOMEM;
goto errout;
}
fnip->fi_keylen = (int)len;
bcopy(strbuff, fnip->fi_key, len);
fnip->fi_mntflags = args.mntflags;
if (args.direct == 1)
fnip->fi_flags |= MF_DIRECT;
#ifdef DEBUG
lckattr = lck_attr_alloc_init();
lck_attr_setdebug(lckattr);
fnip->fi_rwlock = lck_rw_alloc_init(autofs_lck_grp, lckattr);
lck_attr_free(lckattr);
#else
fnip->fi_rwlock = lck_rw_alloc_init(autofs_lck_grp, NULL);
#endif
error = auto_makefnnode(&rootfnp, VDIR, mp, NULL, fnip->fi_path,
NULL, 1, vfs_context_ucred(context), fngp);
if (error)
goto errout;
rootvp = fntovn(rootfnp);
rootfnp->fn_mode = AUTOFS_MODE;
rootfnp->fn_parent = rootfnp;
rootfnp->fn_linkcnt = 1;
error = vnode_ref(rootvp);
if (error) {
vnode_put(rootvp);
vnode_recycle(rootvp);
goto errout;
}
fnip->fi_rootvp = rootvp;
if (!args.trigger) {
lck_rw_lock_exclusive(fngp->fng_rootfnnodep->fn_rwlock);
rootfnp->fn_parent = fngp->fng_rootfnnodep;
rootfnp->fn_next = fngp->fng_rootfnnodep->fn_dirents;
fngp->fng_rootfnnodep->fn_dirents = rootfnp;
lck_rw_unlock_exclusive(fngp->fng_rootfnnodep->fn_rwlock);
}
lck_mtx_lock(autofs_global_lock);
autofs_mounts++;
lck_mtx_unlock(autofs_global_lock);
vnode_put(rootvp);
AUTOFS_DPRINT((5, "auto_mount: mp %p root %p fnip %p return %d\n",
mp, rootvp, fnip, error));
return (0);
errout:
assert(fnip != NULL);
if (fnip->fi_rwlock != NULL)
lck_rw_free(fnip->fi_rwlock, autofs_lck_grp);
if (fnip->fi_path != NULL)
FREE(fnip->fi_path, M_AUTOFS);
if (fnip->fi_opts != NULL)
FREE(fnip->fi_opts, M_AUTOFS);
if (fnip->fi_map != NULL)
FREE(fnip->fi_map, M_AUTOFS);
if (fnip->fi_subdir != NULL)
FREE(fnip->fi_subdir, M_AUTOFS);
if (fnip->fi_key != NULL)
FREE(fnip->fi_key, M_AUTOFS);
FREE(fnip, sizeof M_AUTOFS);
AUTOFS_DPRINT((5, "auto_mount: vfs %p root %p fnip %p return %d\n",
(void *)mp, (void *)rootvp, (void *)fnip, error));
return (error);
}
static int
auto_update_options(struct autofs_update_args_64 *update_argsp)
{
mount_t mp;
fninfo_t *fnip;
fnnode_t *fnp;
char strbuff[PATH_MAX+1];
int error;
char *opts, *map;
size_t optslen, maplen;
mp = vfs_getvfs(&update_argsp->fsid);
if (mp == NULL)
return (ENOENT);
if (!auto_is_autofs(mp))
return (EINVAL);
fnip = vfstofni(mp);
if (fnip == NULL)
return (EINVAL);
if ((update_argsp->direct == 1 && !(fnip->fi_flags & MF_DIRECT)) ||
(update_argsp->direct != 1 && (fnip->fi_flags & MF_DIRECT))) {
fnp = vntofn(fnip->fi_rootvp);
if (fnp->fn_dirents != NULL)
return (EINVAL);
}
error = copyinstr(update_argsp->opts, strbuff, sizeof (strbuff),
&optslen);
if (error)
return (error);
if (autofs_restrict_opts(mp, strbuff, sizeof (strbuff), &optslen) != 0)
return (EFAULT);
MALLOC(opts, char *, optslen, M_AUTOFS, M_WAITOK);
bcopy(strbuff, opts, optslen);
error = copyinstr(update_argsp->map, strbuff, sizeof (strbuff),
&maplen);
if (error) {
FREE(opts, M_AUTOFS);
return (error);
}
MALLOC(map, char *, maplen, M_AUTOFS, M_WAITOK);
bcopy(strbuff, map, maplen);
lck_rw_lock_exclusive(fnip->fi_rwlock);
FREE(fnip->fi_opts, M_AUTOFS);
fnip->fi_opts = opts;
fnip->fi_optslen = (int)optslen;
FREE(fnip->fi_map, M_AUTOFS);
fnip->fi_map = map;
fnip->fi_maplen = (int)maplen;
if (update_argsp->direct == 1)
fnip->fi_flags |= MF_DIRECT;
else
fnip->fi_flags &= ~MF_DIRECT;
fnip->fi_flags &= ~MF_DONTTRIGGER;
fnip->fi_mntflags = update_argsp->mntflags;
fnip->fi_mount_to = update_argsp->mount_to;
fnip->fi_mach_to = update_argsp->mach_to;
snprintf(vfs_statfs(mp)->f_mntfromname, sizeof(vfs_statfs(mp)->f_mntfromname),
"map %.*s", fnip->fi_maplen, fnip->fi_map);
lck_rw_unlock_exclusive(fnip->fi_rwlock);
AUTO_KNOTE(fnip->fi_rootvp, NOTE_WRITE);
return (0);
}
int
auto_start(mount_t mp, int flags, vfs_context_t context)
{
fninfo_t *fnip;
fnip = vfstofni(mp);
lck_rw_lock_exclusive(fnip->fi_rwlock);
fnip->fi_flags &= ~MF_DONTTRIGGER;
lck_rw_unlock_exclusive(fnip->fi_rwlock);
return (0);
}
static int
auto_unmount(mount_t mp, int mntflags, vfs_context_t context)
{
fninfo_t *fnip;
vnode_t rvp;
fnnode_t *rfnp, *fnp, *pfnp;
fnnode_t *myrootfnnodep;
int error;
fnip = vfstofni(mp);
AUTOFS_DPRINT((4, "auto_unmount mp %p fnip %p\n", (void *)mp,
(void *)fnip));
if (mntflags & MNT_FORCE)
return (ENOTSUP);
#if 0
assert(vn_vfswlock_held(mp->mnt_vnodecovered));
#endif
rvp = fnip->fi_rootvp;
rfnp = vntofn(rvp);
error = vflush(mp, rvp, 0);
if (error)
return (error);
if (vnode_isinuse(rvp, 1) || rfnp->fn_dirents != NULL)
return (EBUSY);
myrootfnnodep = rfnp->fn_globals->fng_rootfnnodep;
pfnp = NULL;
lck_rw_lock_exclusive(myrootfnnodep->fn_rwlock);
fnp = myrootfnnodep->fn_dirents;
while (fnp != NULL) {
if (fnp == rfnp) {
if (vnode_isinuse(rvp, 1) || rfnp->fn_dirents != NULL) {
lck_rw_unlock_exclusive(myrootfnnodep->fn_rwlock);
return (EBUSY);
}
if (pfnp)
pfnp->fn_next = fnp->fn_next;
else
myrootfnnodep->fn_dirents = fnp->fn_next;
fnp->fn_next = NULL;
break;
}
pfnp = fnp;
fnp = fnp->fn_next;
}
lck_rw_unlock_exclusive(myrootfnnodep->fn_rwlock);
assert(vnode_isinuse(rvp, 0) && !vnode_isinuse(rvp, 1));
assert(rfnp->fn_direntcnt == 0);
assert(rfnp->fn_linkcnt == 1);
rfnp->fn_linkcnt--;
vnode_rele(rvp);
vflush(mp, NULLVP, 0);
lck_rw_free(fnip->fi_rwlock, autofs_lck_grp);
FREE(fnip->fi_path, M_AUTOFS);
FREE(fnip->fi_map, M_AUTOFS);
FREE(fnip->fi_subdir, M_AUTOFS);
FREE(fnip->fi_key, M_AUTOFS);
FREE(fnip->fi_opts, M_AUTOFS);
FREE(fnip, M_AUTOFS);
lck_mtx_lock(autofs_global_lock);
autofs_mounts--;
lck_mtx_unlock(autofs_global_lock);
AUTOFS_DPRINT((5, "auto_unmount: return=0\n"));
return (0);
}
extern int thread_notrigger(void);
static int
auto_root(mount_t mp, vnode_t *vpp, vfs_context_t context)
{
int error;
fninfo_t *fnip;
fnnode_t *fnp;
struct timeval now;
fnip = vfstofni(mp);
*vpp = fnip->fi_rootvp;
error = vnode_get(*vpp);
if (error)
goto done;
auto_fninfo_lock_shared(fnip, context);
if ((fnip->fi_flags & (MF_DIRECT|MF_DONTTRIGGER)) == MF_DIRECT &&
!thread_notrigger()) {
fnp = vntofn(*vpp);
retry:
lck_mtx_lock(fnp->fn_lock);
if (auto_dont_trigger(*vpp, context)) {
lck_mtx_unlock(fnp->fn_lock);
error = 0;
goto unlock;
}
if (fnp->fn_flags & (MF_LOOKUP | MF_INPROG)) {
lck_mtx_unlock(fnp->fn_lock);
error = auto_wait4mount(fnp, context);
if (error && error != EAGAIN) {
vnode_put(*vpp);
goto unlock;
}
error = 0;
goto retry;
}
if ((fnp->fn_flags & MF_MOUNTPOINT) &&
fnp->fn_trigger != NULL) {
assert(fnp->fn_dirents == NULL);
lck_mtx_unlock(fnp->fn_lock);
error = EIO;
vnode_put(*vpp);
goto unlock;
}
if (fnp->fn_dirents == NULL) {
AUTOFS_BLOCK_OTHERS(fnp, MF_INPROG);
fnp->fn_error = 0;
lck_mtx_unlock(fnp->fn_lock);
microtime(&now);
fnp->fn_ref_time = now.tv_sec;
auto_new_mount_thread(fnp, ".", 1, context);
error = auto_wait4mount(fnp, context);
if (error) {
if (error == EAGAIN)
goto retry;
vnode_put(*vpp);
goto unlock;
}
} else {
lck_mtx_unlock(fnp->fn_lock);
error = 0;
}
}
unlock:
auto_fninfo_unlock_shared(fnip, context);
done:
AUTOFS_DPRINT((5, "auto_root: mp %p, *vpp %p, error %d\n",
mp, *vpp, error));
return (error);
}
static int
auto_vfs_getattr(mount_t mp, struct vfs_attr *vfap, vfs_context_t context)
{
AUTOFS_DPRINT((4, "auto_vfs_getattr %p\n", (void *)mp));
VFSATTR_RETURN(vfap, f_bsize, AUTOFS_BLOCKSIZE);
VFSATTR_RETURN(vfap, f_iosize, 512);
VFSATTR_RETURN(vfap, f_blocks, 0);
VFSATTR_RETURN(vfap, f_bfree, 0);
VFSATTR_RETURN(vfap, f_bavail, 0);
VFSATTR_RETURN(vfap, f_files, 0);
VFSATTR_RETURN(vfap, f_ffree, 0);
return (0);
}
static int
auto_sync(mount_t mp, int waitfor, vfs_context_t context)
{
return (0);
}
static int
auto_vget(mount_t mp, ino64_t ino, vnode_t *vpp, vfs_context_t context)
{
return (ENOTSUP);
}
static int
autofs_sysctl(int *name, u_int namelen, user_addr_t oldp, size_t *oldlenp, user_addr_t newp, size_t newlen, vfs_context_t context)
{
int error;
#ifdef DEBUG
struct sysctl_req *req = NULL;
uint32_t debug;
#endif
if (namelen > 1)
return ENOTDIR;
error = 0;
#ifdef DEBUG
req = CAST_DOWN(struct sysctl_req *, oldp);
#endif
AUTOFS_DPRINT((4, "autofs_sysctl called.\n"));
switch (name[0]) {
#ifdef DEBUG
case AUTOFS_CTL_DEBUG:
error = SYSCTL_IN(req, &debug, sizeof(debug));
if (error)
break;
auto_debug_set(debug);
break;
#endif
default:
error = ENOTSUP;
break;
}
return (error);
}
static d_open_t autofs_dev_open;
static d_close_t autofs_dev_close;
static d_ioctl_t autofs_ioctl;
static struct cdevsw autofs_cdevsw = {
autofs_dev_open,
autofs_dev_close,
eno_rdwrt,
eno_rdwrt,
autofs_ioctl,
eno_stop,
eno_reset,
0,
eno_select,
eno_mmap,
eno_strat,
eno_getc,
eno_putc,
0
};
static int autofs_major = -1;
static void *autofs_devfs;
static pid_t automounter_pid;
static lck_rw_t *autofs_automounter_pid_rwlock;
static int
autofs_dev_open(__unused dev_t dev, __unused int oflags, __unused int devtype,
struct proc *p)
{
lck_rw_lock_exclusive(autofs_automounter_pid_rwlock);
if (automounter_pid != 0) {
lck_rw_unlock_exclusive(autofs_automounter_pid_rwlock);
return (EBUSY);
}
automounter_pid = proc_pid(p);
lck_rw_unlock_exclusive(autofs_automounter_pid_rwlock);
return (0);
}
static int
autofs_dev_close(__unused dev_t dev, __unused int flag, __unused int fmt,
__unused struct proc *p)
{
lck_rw_lock_exclusive(autofs_automounter_pid_rwlock);
automounter_pid = 0;
lck_rw_unlock_exclusive(autofs_automounter_pid_rwlock);
return (0);
}
static int
autofs_ioctl(__unused dev_t dev, u_long cmd, __unused caddr_t data,
__unused int flag, __unused struct proc *p)
{
int error;
switch (cmd) {
case AUTOFS_NOTIFYCHANGE:
if (fngp != NULL) {
lck_mtx_lock(fngp->fng_flush_notification_lock);
fngp->fng_flush_notification_pending = 1;
wakeup(&fngp->fng_flush_notification_pending);
lck_mtx_unlock(fngp->fng_flush_notification_lock);
}
error = 0;
break;
case AUTOFS_WAITFORFLUSH:
lck_mtx_lock(autofs_global_lock);
if (fngp == NULL)
fngp = autofs_zone_init();
lck_mtx_unlock(autofs_global_lock);
if (fngp == NULL)
return (ENOMEM);
error = 0;
lck_mtx_lock(fngp->fng_flush_notification_lock);
while (error == 0 && !fngp->fng_flush_notification_pending) {
error = msleep(&fngp->fng_flush_notification_pending,
fngp->fng_flush_notification_lock, PWAIT | PCATCH,
"flush notification", NULL);
}
fngp->fng_flush_notification_pending = 0;
lck_mtx_unlock(fngp->fng_flush_notification_lock);
break;
default:
error = EINVAL;
break;
}
return (error);
}
static d_open_t autofs_nowait_dev_open;
static d_close_t autofs_nowait_dev_close;
static struct cdevsw autofs_nowait_cdevsw = {
autofs_nowait_dev_open,
autofs_nowait_dev_close,
eno_rdwrt,
eno_rdwrt,
eno_ioctl,
eno_stop,
eno_reset,
0,
eno_select,
eno_mmap,
eno_strat,
eno_getc,
eno_putc,
0
};
static int autofs_nowait_major = -1;
static void *autofs_nowait_devfs;
struct nowait_process {
LIST_ENTRY(nowait_process) entries;
pid_t pid;
int minor;
};
static LIST_HEAD(nowaitproclist, nowait_process) nowait_processes;
static lck_rw_t *autofs_nowait_processes_rwlock;
static int
autofs_nowait_dev_clone(__unused dev_t dev, int action)
{
int minor;
struct nowait_process *nowait_process;
if (action == DEVFS_CLONE_ALLOC) {
minor = 0;
lck_rw_lock_exclusive(autofs_nowait_processes_rwlock);
LIST_FOREACH(nowait_process, &nowait_processes, entries) {
if (minor < nowait_process->minor) {
break;
}
minor = nowait_process->minor + 1;
}
lck_rw_unlock_exclusive(autofs_nowait_processes_rwlock);
return (minor);
}
return (-1);
}
static int
autofs_nowait_dev_open(dev_t dev, __unused int oflags, __unused int devtype,
struct proc *p)
{
struct nowait_process *newnowait_process, *nowait_process, *lastnowait_process;
MALLOC(newnowait_process, struct nowait_process *, sizeof(*newnowait_process),
M_AUTOFS, M_WAITOK);
if (newnowait_process == NULL)
return (ENOMEM);
newnowait_process->pid = proc_pid(p);
newnowait_process->minor = minor(dev);
lck_rw_lock_exclusive(autofs_nowait_processes_rwlock);
if (LIST_EMPTY(&nowait_processes)) {
LIST_INSERT_HEAD(&nowait_processes, newnowait_process, entries);
} else {
LIST_FOREACH(nowait_process, &nowait_processes, entries) {
if (newnowait_process->minor < nowait_process->minor) {
LIST_INSERT_BEFORE(nowait_process,
newnowait_process, entries);
goto done;
}
lastnowait_process = nowait_process;
}
LIST_INSERT_AFTER(lastnowait_process, newnowait_process,
entries);
}
done:
lck_rw_unlock_exclusive(autofs_nowait_processes_rwlock);
return (0);
}
static int
autofs_nowait_dev_close(dev_t dev, __unused int flag, __unused int fmt,
__unused struct proc *p)
{
struct nowait_process *nowait_process;
lck_rw_lock_exclusive(autofs_nowait_processes_rwlock);
LIST_FOREACH(nowait_process, &nowait_processes, entries) {
if (minor(dev) == nowait_process->minor) {
LIST_REMOVE(nowait_process, entries);
FREE(nowait_process, M_AUTOFS);
break;
}
}
lck_rw_unlock_exclusive(autofs_nowait_processes_rwlock);
return (0);
}
static d_open_t auto_control_dev_open;
static d_close_t auto_control_dev_close;
static d_ioctl_t auto_control_ioctl;
static struct cdevsw autofs_control_cdevsw = {
auto_control_dev_open,
auto_control_dev_close,
eno_rdwrt,
eno_rdwrt,
auto_control_ioctl,
eno_stop,
eno_reset,
0,
eno_select,
eno_mmap,
eno_strat,
eno_getc,
eno_putc,
0
};
static int autofs_control_major = -1;
static void *autofs_control_devfs;
static int autofs_control_isopen;
static lck_mtx_t *autofs_control_isopen_lock;
static int
auto_control_dev_open(__unused dev_t dev, __unused int oflags,
__unused int devtype, struct proc *p)
{
lck_mtx_lock(autofs_control_isopen_lock);
if (autofs_control_isopen) {
lck_mtx_unlock(autofs_control_isopen_lock);
return (EBUSY);
}
autofs_control_isopen = 1;
lck_mtx_unlock(autofs_control_isopen_lock);
return (0);
}
static int
auto_control_dev_close(__unused dev_t dev, __unused int flag,
__unused int fmt, __unused struct proc *p)
{
lck_mtx_lock(autofs_control_isopen_lock);
autofs_control_isopen = 0;
lck_mtx_unlock(autofs_control_isopen_lock);
return (0);
}
static int
auto_control_ioctl(__unused dev_t dev, u_long cmd, caddr_t data,
__unused int flag, proc_t p)
{
int error;
struct autofs_update_args *update_argsp_32;
struct autofs_update_args_64 update_args;
mount_t mp;
fninfo_t *fnip;
switch (cmd) {
case AUTOFS_UPDATE_OPTIONS:
update_argsp_32 = (struct autofs_update_args *)data;
update_args.fsid = update_argsp_32->fsid;
update_args.opts = CAST_USER_ADDR_T(update_argsp_32->opts);
update_args.map = CAST_USER_ADDR_T(update_argsp_32->map);
update_args.mntflags = update_argsp_32->mntflags;
update_args.mount_to = update_argsp_32->mount_to;
update_args.mach_to = update_argsp_32->mach_to;
update_args.direct = update_argsp_32->direct;
error = auto_update_options(&update_args);
break;
case AUTOFS_UPDATE_OPTIONS_64:
error = auto_update_options((struct autofs_update_args_64 *)data);
break;
case AUTOFS_NOTIFYCHANGE:
if (fngp != NULL) {
lck_mtx_lock(fngp->fng_flush_notification_lock);
fngp->fng_flush_notification_pending = 1;
wakeup(&fngp->fng_flush_notification_pending);
lck_mtx_unlock(fngp->fng_flush_notification_lock);
}
error = 0;
break;
case AUTOFS_UNMOUNT:
error = 0;
if (fngp != NULL) {
unmount_tree(fngp, (fsid_t *)data,
UNMOUNT_TREE_IMMEDIATE);
mp = vfs_getvfs((fsid_t *)data);
if (mp == NULL) {
error = ENOENT;
break;
}
if (!auto_is_autofs(mp)) {
error = EINVAL;
break;
}
fnip = vfstofni(mp);
lck_rw_lock_exclusive(fnip->fi_rwlock);
fnip->fi_flags |= MF_DONTTRIGGER;
lck_rw_unlock_exclusive(fnip->fi_rwlock);
auto_wait4mount(vntofn(fnip->fi_rootvp),
vfs_context_current());
error = vfs_unmountbyfsid((fsid_t *)data, 0,
vfs_context_current());
}
break;
default:
error = EINVAL;
break;
}
return (error);
}
int
auto_is_automounter(pid_t pid)
{
int is_automounter;
lck_rw_lock_shared(autofs_automounter_pid_rwlock);
is_automounter = (automounter_pid != 0 &&
(pid == automounter_pid || proc_isinferior(pid, automounter_pid)));
lck_rw_unlock_shared(autofs_automounter_pid_rwlock);
return (is_automounter);
}
int
auto_is_nowait_process(pid_t pid)
{
struct nowait_process *nowait_process;
lck_rw_lock_shared(autofs_nowait_processes_rwlock);
LIST_FOREACH(nowait_process, &nowait_processes, entries) {
if (pid == nowait_process->pid) {
lck_rw_unlock_shared(autofs_nowait_processes_rwlock);
return (1);
}
}
lck_rw_unlock_shared(autofs_nowait_processes_rwlock);
return (0);
}
__private_extern__ int
auto_module_start(kmod_info_t *ki, void *data)
{
errno_t error;
autofs_lck_grp = lck_grp_alloc_init("autofs", NULL);
if (autofs_lck_grp == NULL) {
log(LOG_ERR, "auto_module_start: Couldn't create autofs lock group\n");
goto fail;
}
autofs_global_lock = lck_mtx_alloc_init(autofs_lck_grp, LCK_ATTR_NULL);
if (autofs_global_lock == NULL) {
log(LOG_ERR, "auto_module_start: Couldn't create autofs global lock\n");
goto fail;
}
autofs_nodeid_lock = lck_mtx_alloc_init(autofs_lck_grp, LCK_ATTR_NULL);
if (autofs_nodeid_lock == NULL) {
log(LOG_ERR, "auto_module_start: Couldn't create autofs node ID lock\n");
goto fail;
}
autofs_automounter_pid_rwlock = lck_rw_alloc_init(autofs_lck_grp, NULL);
if (autofs_automounter_pid_rwlock == NULL) {
log(LOG_ERR, "auto_module_start: Couldn't create autofs automounter pid lock\n");
goto fail;
}
autofs_nowait_processes_rwlock = lck_rw_alloc_init(autofs_lck_grp, NULL);
if (autofs_nowait_processes_rwlock == NULL) {
log(LOG_ERR, "auto_module_start: Couldn't create autofs nowait_processes list lock\n");
goto fail;
}
autofs_control_isopen_lock = lck_mtx_alloc_init(autofs_lck_grp, LCK_ATTR_NULL);
if (autofs_control_isopen_lock == NULL) {
log(LOG_ERR, "auto_module_start: Couldn't create autofs control device lock\n");
goto fail;
}
autofs_major = cdevsw_add(-1, &autofs_cdevsw);
if (autofs_major == -1) {
log(LOG_ERR, "auto_module_start: cdevsw_add failed on autofs device\n");
goto fail;
}
autofs_devfs = devfs_make_node(makedev(autofs_major, 0),
DEVFS_CHAR, UID_ROOT, GID_WHEEL, 0600, AUTOFS_DEVICE);
if (autofs_devfs == NULL) {
log(LOG_ERR, "auto_module_start: devfs_make_node failed on autofs device\n");
goto fail;
}
autofs_nowait_major = cdevsw_add(-1, &autofs_nowait_cdevsw);
if (autofs_nowait_major == -1) {
log(LOG_ERR, "auto_module_start: cdevsw_add failed on autofs_nowait device\n");
goto fail;
}
autofs_nowait_devfs = devfs_make_node_clone(makedev(autofs_nowait_major, 0),
DEVFS_CHAR, UID_ROOT, GID_WHEEL, 0666, autofs_nowait_dev_clone,
AUTOFS_NOWAIT_DEVICE);
if (autofs_nowait_devfs == NULL) {
log(LOG_ERR, "auto_module_start: devfs_make_node failed on autofs nowait device\n");
goto fail;
}
autofs_control_major = cdevsw_add(-1, &autofs_control_cdevsw);
if (autofs_control_major == -1) {
log(LOG_ERR, "auto_module_start: cdevsw_add failed on autofs control device\n");
goto fail;
}
autofs_control_devfs = devfs_make_node(makedev(autofs_control_major, 0),
DEVFS_CHAR, UID_ROOT, GID_WHEEL, 0600, AUTOFS_CONTROL_DEVICE);
if (autofs_control_devfs == NULL) {
log(LOG_ERR, "auto_module_start: devfs_make_node failed on autofs control device\n");
goto fail;
}
error = vfs_fsadd(&autofs_fsentry, &auto_vfsconf);
if (error != 0) {
log(LOG_ERR, "auto_module_start: Error %d from vfs_fsadd\n",
error);
goto fail;
}
return (KERN_SUCCESS);
fail:
if (autofs_control_devfs != NULL)
devfs_remove(autofs_control_devfs);
if (autofs_control_major != -1) {
if (cdevsw_remove(autofs_control_major, &autofs_control_cdevsw) == -1)
panic("auto_module_start: can't remove autofs control device from cdevsw");
}
if (autofs_nowait_devfs != NULL)
devfs_remove(autofs_nowait_devfs);
if (autofs_nowait_major != -1) {
if (cdevsw_remove(autofs_nowait_major, &autofs_nowait_cdevsw) == -1)
panic("auto_module_start: can't remove autofs nowait device from cdevsw");
}
if (autofs_devfs != NULL)
devfs_remove(autofs_devfs);
if (autofs_major != -1) {
if (cdevsw_remove(autofs_major, &autofs_cdevsw) == -1)
panic("auto_module_start: can't remove autofs device from cdevsw");
}
if (autofs_control_isopen_lock != NULL)
lck_mtx_free(autofs_control_isopen_lock, autofs_lck_grp);
if (autofs_nowait_processes_rwlock != NULL)
lck_rw_free(autofs_nowait_processes_rwlock, autofs_lck_grp);
if (autofs_automounter_pid_rwlock != NULL)
lck_rw_free(autofs_automounter_pid_rwlock, autofs_lck_grp);
if (autofs_nodeid_lock != NULL)
lck_mtx_free(autofs_nodeid_lock, autofs_lck_grp);
if (autofs_global_lock != NULL)
lck_mtx_free(autofs_global_lock, autofs_lck_grp);
if (autofs_lck_grp != NULL)
lck_grp_free(autofs_lck_grp);
return (KERN_FAILURE);
}
__private_extern__ int
auto_module_stop(kmod_info_t *ki, void *data)
{
int error;
lck_mtx_lock(autofs_global_lock);
lck_rw_lock_exclusive(autofs_automounter_pid_rwlock);
lck_rw_lock_exclusive(autofs_nowait_processes_rwlock);
lck_mtx_lock(autofs_control_isopen_lock);
if (autofs_mounts != 0) {
AUTOFS_DPRINT((2, "auto_module_stop: Can't remove, still %u mounts active\n", autofs_mounts));
lck_mtx_unlock(autofs_control_isopen_lock);
lck_rw_unlock_exclusive(autofs_nowait_processes_rwlock);
lck_rw_unlock_exclusive(autofs_automounter_pid_rwlock);
lck_mtx_unlock(autofs_global_lock);
return (KERN_NO_ACCESS);
}
if (automounter_pid != 0) {
AUTOFS_DPRINT((2, "auto_module_stop: Can't remove, automounter still running\n"));
lck_mtx_unlock(autofs_control_isopen_lock);
lck_rw_unlock_exclusive(autofs_nowait_processes_rwlock);
lck_rw_unlock_exclusive(autofs_automounter_pid_rwlock);
lck_mtx_unlock(autofs_global_lock);
return (KERN_NO_ACCESS);
}
if (!LIST_EMPTY(&nowait_processes)) {
AUTOFS_DPRINT((2, "auto_module_stop: Can't remove, still nowait processes running\n"));
lck_mtx_unlock(autofs_control_isopen_lock);
lck_rw_unlock_exclusive(autofs_nowait_processes_rwlock);
lck_rw_unlock_exclusive(autofs_automounter_pid_rwlock);
lck_mtx_unlock(autofs_global_lock);
return (KERN_NO_ACCESS);
}
if (autofs_control_isopen) {
AUTOFS_DPRINT((2, "auto_module_stop: Can't remove, automount command is running\n"));
lck_mtx_unlock(autofs_control_isopen_lock);
lck_rw_unlock_exclusive(autofs_nowait_processes_rwlock);
lck_rw_unlock_exclusive(autofs_automounter_pid_rwlock);
lck_mtx_unlock(autofs_global_lock);
return (KERN_NO_ACCESS);
}
AUTOFS_DPRINT((10, "auto_module_stop: removing autofs from vfs conf. list...\n"));
if (fngp) {
assert(fngp->fng_fnnode_count == 1);
assert(fngp->fng_unmount_threads == 0);
}
error = vfs_fsremove(auto_vfsconf);
if (error) {
log(LOG_ERR, "auto_module_stop: Error %d from vfs_remove\n",
error);
return (KERN_FAILURE);
}
devfs_remove(autofs_devfs);
autofs_devfs = NULL;
if (cdevsw_remove(autofs_major, &autofs_cdevsw) == -1)
panic("auto_module_stop: can't remove autofs device from cdevsw");
autofs_major = -1;
devfs_remove(autofs_nowait_devfs);
autofs_nowait_devfs = NULL;
if (cdevsw_remove(autofs_nowait_major, &autofs_nowait_cdevsw) == -1)
panic("auto_module_stop: can't remove autofs nowait device from cdevsw");
autofs_nowait_major = -1;
devfs_remove(autofs_control_devfs);
autofs_control_devfs = NULL;
if (cdevsw_remove(autofs_control_major, &autofs_control_cdevsw) == -1)
panic("auto_module_start: can't remove autofs control device from cdevsw");
if (fngp) {
lck_mtx_lock(fngp->fng_unmount_threads_lock);
fngp->fng_terminate_do_unmount_thread = 1;
wakeup(&fngp->fng_terminate_do_unmount_thread);
while (!fngp->fng_do_unmount_thread_terminated) {
msleep(&fngp->fng_do_unmount_thread_terminated,
fngp->fng_unmount_threads_lock, PWAIT,
"unmount thread terminated", NULL);
}
lck_mtx_unlock(fngp->fng_unmount_threads_lock);
FREE(fngp->fng_rootfnnodep->fn_name, M_AUTOFS);
FREE(fngp->fng_rootfnnodep, M_AUTOFS);
lck_mtx_free(fngp->fng_unmount_threads_lock, autofs_lck_grp);
lck_mtx_free(fngp->fng_flush_notification_lock, autofs_lck_grp);
FREE(fngp, M_AUTOFS);
}
lck_mtx_unlock(autofs_nodeid_lock);
lck_mtx_free(autofs_nodeid_lock, autofs_lck_grp);
lck_mtx_unlock(autofs_global_lock);
lck_mtx_free(autofs_global_lock, autofs_lck_grp);
lck_rw_unlock_exclusive(autofs_automounter_pid_rwlock);
lck_rw_free(autofs_automounter_pid_rwlock, autofs_lck_grp);
lck_rw_unlock_exclusive(autofs_nowait_processes_rwlock);
lck_rw_free(autofs_nowait_processes_rwlock, autofs_lck_grp);
lck_mtx_unlock(autofs_control_isopen_lock);
lck_mtx_free(autofs_control_isopen_lock, autofs_lck_grp);
lck_grp_free(autofs_lck_grp);
return (KERN_SUCCESS);
}