#pragma ident "@(#)auto_subr.c 1.95 05/12/19 SMI"
#include <mach/task.h>
#include <mach/host_priv.h>
#include <mach/host_special_ports.h>
#include <mach/task_special_ports.h>
#include <mach/thread_act.h>
#include <mach/vm_map.h>
#include <vm/vm_map.h>
#include <vm/vm_kern.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <sys/buf.h>
#include <sys/proc.h>
#include <sys/conf.h>
#include <sys/mount.h>
#include <sys/vnode.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/dirent.h>
#include <sys/namei.h>
#include <sys/kauth.h>
#include <sys/attr.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <sys/vm.h>
#include <sys/errno.h>
#include <vfs/vfs_support.h>
#include <kern/assert.h>
#include <kern/host.h>
#include <kern/locks.h>
#include <kern/clock.h>
#include <IOKit/IOLib.h>
#ifdef DEBUG
#include <stdarg.h>
#endif
#include "autofs.h"
#include "autofs_kern.h"
#include "autofs_protUser.h"
#define TYPICALPATH_MAX 64
static int auto_perform_actions(fninfo_t *, fnnode_t *,
action_list *, kauth_cred_t);
static int auto_lookup_request(fninfo_t *, char *, int,
vfs_context_t, boolean_t, boolean_t *);
static int auto_mount_request(fninfo_t *, char *, int,
action_list **, kauth_cred_t, mach_port_t, boolean_t);
void
auto_fninfo_lock_shared(fninfo_t *fnip, int pid)
{
if (!auto_is_automounter(pid))
lck_rw_lock_shared(fnip->fi_rwlock);
}
void
auto_fninfo_unlock_shared(fninfo_t *fnip, int pid)
{
if (!auto_is_automounter(pid))
lck_rw_unlock_shared(fnip->fi_rwlock);
}
int
auto_is_autofs(mount_t mp)
{
struct vfsstatfs *vfsstat;
size_t typename_len;
static const char autofs_typename[] = MNTTYPE_AUTOFS;
vfsstat = vfs_statfs(mp);
typename_len = strlen(vfsstat->f_fstypename) + 1;
if (typename_len != sizeof autofs_typename)
return (0);
if (bcmp(autofs_typename, vfsstat->f_fstypename, typename_len) != 0)
return (0);
return (1);
}
void
auto_unblock_others(
fnnode_t *fnp,
u_int operation)
{
assert(operation & (MF_INPROG | MF_LOOKUP));
fnp->fn_flags &= ~operation;
if (fnp->fn_flags & MF_WAITING) {
fnp->fn_flags &= ~MF_WAITING;
wakeup(&fnp->fn_flags);
}
}
int
auto_wait4mount(fnnode_t *fnp, vfs_context_t context)
{
int error;
int pid;
AUTOFS_DPRINT((4, "auto_wait4mount: fnp=%p\n", (void *)fnp));
pid = vfs_context_pid(context);
lck_mtx_lock(fnp->fn_lock);
while (fnp->fn_flags & (MF_INPROG | MF_LOOKUP)) {
if (fnp->fn_flags & MF_INPROG) {
if (auto_is_automounter(pid)) {
lck_mtx_unlock(fnp->fn_lock);
return (0);
}
if (auto_is_nowait_process(pid)) {
lck_mtx_unlock(fnp->fn_lock);
return (ENOENT);
}
}
fnp->fn_flags |= MF_WAITING;
error = msleep(&fnp->fn_flags, fnp->fn_lock, PSOCK|PCATCH,
"autofs_mount", NULL);
if (error == EINTR || error == ERESTART) {
lck_mtx_unlock(fnp->fn_lock);
return (EINTR);
}
}
error = fnp->fn_error;
if (error == EINTR) {
error = EAGAIN;
}
lck_mtx_unlock(fnp->fn_lock);
AUTOFS_DPRINT((5, "auto_wait4mount: fnp=%p error=%d\n", (void *)fnp,
error));
return (error);
}
int
auto_lookup_aux(fnnode_t *fnp, char *name, int namelen, vfs_context_t context)
{
struct fninfo *fnip;
boolean_t mountreq = FALSE;
int error = 0;
fnip = vfstofni(vnode_mount(fntovn(fnp)));
error = auto_lookup_request(fnip, name, namelen, context, TRUE, &mountreq);
if (!error) {
if (mountreq) {
lck_mtx_lock(fnp->fn_lock);
AUTOFS_BLOCK_OTHERS(fnp, MF_INPROG);
fnp->fn_error = 0;
AUTOFS_UNBLOCK_OTHERS(fnp, MF_LOOKUP);
lck_mtx_unlock(fnp->fn_lock);
error = auto_do_mount(fnp, name, namelen, context);
}
}
lck_mtx_lock(fnp->fn_lock);
fnp->fn_error = error;
if (mountreq) {
AUTOFS_UNBLOCK_OTHERS(fnp, MF_INPROG);
} else {
AUTOFS_UNBLOCK_OTHERS(fnp, MF_LOOKUP);
}
lck_mtx_unlock(fnp->fn_lock);
return (error);
}
struct autofs_callargs {
fnnode_t *fnc_fnp;
char *fnc_name;
int fnc_namelen;
thread_t fnc_origin;
kauth_cred_t fnc_cred;
mach_port_t fnc_gssd_port;
};
static void
auto_mount_thread(void *arg)
{
struct autofs_callargs *argsp = arg;
struct fninfo *fnip;
fnnode_t *fnp;
vnode_t vp;
char *name;
int namelen;
kauth_cred_t cred;
action_list *alp = NULL;
int error;
struct vfsstatfs *vfsstat;
mount_t mp;
fnp = argsp->fnc_fnp;
vp = fntovn(fnp);
fnip = vfstofni(vnode_mount(vp));
name = argsp->fnc_name;
namelen = argsp->fnc_namelen;
cred = argsp->fnc_cred;
fnp->fn_uid = kauth_cred_getuid(cred);
error = auto_mount_request(fnip, name, namelen, &alp, cred,
argsp->fnc_gssd_port, TRUE);
if (!error) {
error = auto_perform_actions(fnip, fnp, alp, cred);
mp = vnode_mountedhere(vp);
if (mp == NULL) {
fnp->fn_fsid_mounted.val[0] = 0;
fnp->fn_fsid_mounted.val[1] = 0;
} else {
vfsstat = vfs_statfs(mp);
fnp->fn_fsid_mounted = vfsstat->f_fsid;
}
}
lck_mtx_lock(fnp->fn_lock);
fnp->fn_error = error;
if (error) {
fnp->fn_uid = 0;
}
AUTOFS_UNBLOCK_OTHERS(fnp, MF_INPROG);
lck_mtx_unlock(fnp->fn_lock);
vnode_rele(vp);
kauth_cred_unref(&argsp->fnc_cred);
FREE(argsp->fnc_name, M_AUTOFS);
FREE(argsp, M_AUTOFS);
thread_terminate(current_thread());
}
static int autofs_thr_success = 0;
int
auto_do_mount(fnnode_t *fnp, char *name, int namelen, vfs_context_t context)
{
int error;
struct autofs_callargs *argsp;
kern_return_t ret;
error = vnode_ref(fntovn(fnp));
if (error != 0)
goto fail;
MALLOC(argsp, struct autofs_callargs *, sizeof (*argsp), M_AUTOFS, M_WAITOK);
argsp->fnc_fnp = fnp;
MALLOC(argsp->fnc_name, char *, namelen, M_AUTOFS, M_WAITOK);
bcopy(name, argsp->fnc_name, namelen);
argsp->fnc_namelen = namelen;
argsp->fnc_origin = current_thread();
argsp->fnc_cred = vfs_context_ucred(context);
kauth_cred_ref(argsp->fnc_cred);
ret = vfs_context_get_special_port(context, TASK_GSSD_PORT,
&argsp->fnc_gssd_port);
if (ret != KERN_SUCCESS) {
IOLog("auto_do_mount: can't get gssd port for process %d, status 0x%08x\n",
vfs_context_pid(context), ret);
error = EIO;
goto fail_thread;
}
ret = auto_new_thread(auto_mount_thread, argsp);
if (ret != KERN_SUCCESS) {
if (IPC_PORT_VALID(argsp->fnc_gssd_port))
auto_release_port(argsp->fnc_gssd_port);
IOLog("auto_do_mount: can't start new mounter thread, status 0x%08x",
ret);
error = EIO;
goto fail_thread;
}
autofs_thr_success++;
return (auto_wait4mount(fnp, context));
fail_thread:
vnode_rele(fntovn(fnp));
kauth_cred_unref(&argsp->fnc_cred);
FREE(argsp->fnc_name, M_AUTOFS);
FREE(argsp, M_AUTOFS);
fail:
lck_mtx_lock(fnp->fn_lock);
fnp->fn_error = error;
fnp->fn_uid = 0;
AUTOFS_UNBLOCK_OTHERS(fnp, MF_INPROG);
lck_mtx_unlock(fnp->fn_lock);
return (error);
}
int
auto_check_trigger_request(fninfo_t *fnip, char *key, int keylen,
boolean_t *istrigger)
{
int error;
mach_port_t automount_port;
char *name;
int namelen;
boolean_t isdirect;
kern_return_t ret;
if (fnip->fi_flags & MF_DIRECT) {
name = fnip->fi_key;
namelen = (int)strlen(fnip->fi_key);
} else {
name = key;
namelen = keylen;
}
isdirect = fnip->fi_flags & MF_DIRECT ? TRUE : FALSE;
error = auto_get_automountd_port(&automount_port);
if (error)
goto done;
ret = autofs_check_trigger(automount_port, fnip->fi_map,
fnip->fi_path, name, namelen, fnip->fi_subdir, fnip->fi_opts,
isdirect, &error, istrigger);
auto_release_port(automount_port);
if (ret != KERN_SUCCESS) {
IOLog("autofs: autofs_check_trigger failed, status 0x%08x\n",
ret);
error = EIO;
}
done:
if (error)
*istrigger = FALSE;
return (error);
}
int
auto_get_automountd_port(mach_port_t *automount_port)
{
kern_return_t ret;
*automount_port = MACH_PORT_NULL;
ret = host_get_automountd_port(host_priv_self(), automount_port);
if (ret != KERN_SUCCESS) {
IOLog("autofs: can't get automountd port, status 0x%08x\n",
ret);
return (ECONNREFUSED);
}
if (!IPC_PORT_VALID(*automount_port)) {
IOLog("autofs: automountd port not valid\n");
return (ECONNRESET);
}
return (0);
}
void
auto_release_port(mach_port_t port)
{
extern void ipc_port_release_send(ipc_port_t);
ipc_port_release_send(port);
}
static int
auto_lookup_request(
fninfo_t *fnip,
char *key,
int keylen,
vfs_context_t context,
__unused boolean_t hard,
boolean_t *mountreq)
{
mach_port_t automount_port;
struct autofs_globals *fngp;
char *name;
int namelen;
boolean_t isdirect;
int lu_action;
boolean_t lu_verbose;
kern_return_t ret;
int error = 0;
kauth_cred_t cred = vfs_context_ucred(context);
AUTOFS_DPRINT((4, "auto_lookup_request: path=%s name=%.*s\n",
fnip->fi_path, keylen, key));
fngp = vntofn(fnip->fi_rootvp)->fn_globals;
if (fnip->fi_flags & MF_DIRECT) {
name = fnip->fi_key;
namelen = (int)strlen(fnip->fi_key);
} else {
name = key;
namelen = keylen;
}
AUTOFS_DPRINT((4, "auto_lookup_request: using key=%.*s\n", namelen,
name));
isdirect = fnip->fi_flags & MF_DIRECT ? TRUE : FALSE;
error = auto_get_automountd_port(&automount_port);
if (error)
goto done;
ret = autofs_lookup(automount_port, fnip->fi_map, fnip->fi_path,
name, namelen, fnip->fi_subdir, fnip->fi_opts, isdirect,
kauth_cred_getuid(cred), &error, &lu_action, &lu_verbose);
auto_release_port(automount_port);
if (ret == KERN_SUCCESS) {
fngp->fng_verbose = lu_verbose;
if (error == 0) {
switch (lu_action) {
case AUTOFS_MOUNT_RQ:
*mountreq = TRUE;
break;
case AUTOFS_NONE:
break;
default:
IOLog(
"auto_lookup_request: bad action type %d\n",
lu_action);
error = ENOENT;
}
}
} else {
IOLog("autofs: autofs_lookup failed, status 0x%08x\n", ret);
error = EIO;
}
done:
AUTOFS_DPRINT((5, "auto_lookup_request: path=%s name=%.*s error=%d\n",
fnip->fi_path, keylen, key, error));
return (error);
}
static int
getstring(char **strp, uint8_t **inbufp, mach_msg_type_number_t *bytes_leftp)
{
uint32_t stringlen;
if (*bytes_leftp < sizeof (uint32_t)) {
IOLog("Action list too short for string length");
return (EIO);
}
memcpy(&stringlen, *inbufp, sizeof (uint32_t));
*inbufp += sizeof (uint32_t);
*bytes_leftp -= (mach_msg_type_number_t)sizeof (uint32_t);
if (stringlen == 0xFFFFFFFF) {
*strp = NULL;
} else {
if (*bytes_leftp < stringlen) {
IOLog("Action list too short for string data");
return (EIO);
}
MALLOC(*strp, char *, stringlen + 1, M_AUTOFS, M_WAITOK);
if (*strp == NULL) {
IOLog("No space for string data in action list");
return (ENOMEM);
}
memcpy(*strp, *inbufp, stringlen);
(*strp)[stringlen] = '\0';
*inbufp += stringlen;
*bytes_leftp -= stringlen;
}
return (0);
}
static void
free_action_list(action_list *alp)
{
action_list *action, *next_action;
for (action = alp; action != NULL; action = next_action) {
if (action->mounta.dir != NULL)
FREE(action->mounta.dir, M_AUTOFS);
if (action->mounta.opts != NULL)
FREE(action->mounta.opts, M_AUTOFS);
if (action->mounta.path != NULL)
FREE(action->mounta.path, M_AUTOFS);
if (action->mounta.map != NULL)
FREE(action->mounta.map, M_AUTOFS);
if (action->mounta.subdir != NULL)
FREE(action->mounta.subdir, M_AUTOFS);
if (action->mounta.key != NULL)
FREE(action->mounta.key, M_AUTOFS);
next_action = action->next;
FREE(action, M_AUTOFS);
}
}
static int
auto_mount_request(
fninfo_t *fnip,
char *key,
int keylen,
action_list **alpp,
kauth_cred_t cred,
mach_port_t gssd_port,
__unused boolean_t hard)
{
mach_port_t automount_port;
int error;
struct autofs_globals *fngp;
char *name;
int namelen;
boolean_t isdirect;
int mr_type;
boolean_t mr_verbose;
kern_return_t ret;
byte_buffer actions_buffer;
mach_msg_type_number_t actions_bufcount;
vm_map_offset_t map_data;
vm_offset_t data;
uint8_t *inbuf;
mach_msg_type_number_t bytes_left;
action_list *alp, *prevalp;
AUTOFS_DPRINT((4, "auto_mount_request: path=%s name=%.*s\n",
fnip->fi_path, keylen, key));
fngp = vntofn(fnip->fi_rootvp)->fn_globals;
if (fnip->fi_flags & MF_DIRECT) {
name = fnip->fi_key;
namelen = (int)strlen(fnip->fi_key);
} else {
name = key;
namelen = keylen;
}
AUTOFS_DPRINT((4, "auto_mount_request: using key=%.*s\n", namelen,
name));
isdirect = fnip->fi_flags & MF_DIRECT ? TRUE : FALSE;
*alpp = NULL;
error = auto_get_automountd_port(&automount_port);
if (error) {
if (IPC_PORT_VALID(gssd_port))
auto_release_port(gssd_port);
goto done;
}
ret = autofs_mount(automount_port, fnip->fi_map, fnip->fi_path, name,
namelen, fnip->fi_subdir, fnip->fi_opts, isdirect,
kauth_cred_getuid(cred), gssd_port,
&mr_type, &actions_buffer, &actions_bufcount, &error, &mr_verbose);
auto_release_port(automount_port);
if (ret == KERN_SUCCESS) {
fngp->fng_verbose = mr_verbose;
switch (mr_type) {
case AUTOFS_ACTION:
error = 0;
ret = vm_map_copyout(kernel_map, &map_data,
(vm_map_copy_t)actions_buffer);
if (ret != KERN_SUCCESS) {
IOLog("autofs: vm_map_copyout failed, status 0x%08x\n",
ret);
error = EIO;
goto done;
}
data = CAST_DOWN(vm_offset_t, map_data);
prevalp = NULL;
inbuf = (uint8_t *)data;
bytes_left = actions_bufcount;
while (bytes_left != 0) {
MALLOC(alp, action_list *, sizeof(*alp),
M_AUTOFS, M_WAITOK);
if (prevalp == NULL)
*alpp = alp;
else
prevalp->next = alp;
bzero(alp, sizeof *alp);
error = getstring(&alp->mounta.dir, &inbuf,
&bytes_left);
if (error)
break;
error = getstring(&alp->mounta.opts, &inbuf,
&bytes_left);
if (error)
break;
error = getstring(&alp->mounta.path, &inbuf,
&bytes_left);
if (error)
break;
error = getstring(&alp->mounta.map, &inbuf,
&bytes_left);
if (error)
break;
error = getstring(&alp->mounta.subdir, &inbuf,
&bytes_left);
if (error)
break;
if (bytes_left < sizeof (uint32_t)) {
IOLog("Action list too short for isdirect");
error = EIO;
break;
}
memcpy(&alp->mounta.isdirect, inbuf,
sizeof (uint32_t));
inbuf += sizeof (uint32_t);
bytes_left -= (mach_msg_type_number_t)sizeof (uint32_t);
error = getstring(&alp->mounta.key, &inbuf,
&bytes_left);
if (error)
break;
prevalp = alp;
}
vm_deallocate(kernel_map, data, actions_bufcount);
if (error) {
free_action_list(*alpp);
*alpp = NULL;
goto done;
}
break;
case AUTOFS_DONE:
break;
default:
error = ENOENT;
IOLog("auto_mount_request: unknown status %d\n",
mr_type);
break;
}
} else {
IOLog("autofs: autofs_mount failed, status 0x%08x\n", ret);
error = EIO;
}
done:
AUTOFS_DPRINT((5, "auto_mount_request: path=%s name=%.*s error=%d\n",
fnip->fi_path, keylen, key, error));
return (error);
}
static int
auto_send_unmount_request(
fsid_t fsid,
char *mntresource,
char *mntpnt,
char *fstype,
char *mntopts,
__unused boolean_t hard)
{
int error;
mach_port_t automount_port;
int status;
kern_return_t ret;
AUTOFS_DPRINT((4, "\tauto_send_unmount_request: fstype=%s "
" mntpnt=%s\n", fstype, mntpnt));
error = auto_get_automountd_port(&automount_port);
if (error)
goto done;
ret = autofs_unmount(automount_port, fsid.val[0], fsid.val[1],
mntresource, mntpnt, fstype, mntopts, &status);
auto_release_port(automount_port);
if (ret == KERN_SUCCESS)
error = status;
else {
IOLog("autofs: autofs_unmount failed, status 0x%08x\n", ret);
error = EIO;
}
done:
AUTOFS_DPRINT((5, "\tauto_send_unmount_request: error=%d\n", error));
return (error);
}
static int
auto_send_mount_trigger_request(
char *mntpt,
char *submntpt,
char *path,
char *opts,
char *map,
char *subdir,
char *key,
uint32_t flags,
uint32_t mntflags,
int32_t mount_to,
int32_t mach_to,
int32_t direct,
fsid_t *fsid,
boolean_t *top_level,
__unused boolean_t hard)
{
int error;
mach_port_t automount_port;
kern_return_t ret;
int32_t fsid_val0, fsid_val1;
error = auto_get_automountd_port(&automount_port);
if (error)
return (error);
ret = autofs_mount_trigger(automount_port, mntpt, submntpt, path,
opts, map, subdir, key, flags, mntflags, mount_to, mach_to,
direct, &fsid_val0, &fsid_val1, top_level, &error);
auto_release_port(automount_port);
if (ret == KERN_SUCCESS) {
fsid->val[0] = fsid_val0;
fsid->val[1] = fsid_val1;
} else {
IOLog("autofs: autofs_mount_trigger failed, status 0x%08x\n",
ret);
error = EIO;
}
return (error);
}
static int
auto_perform_actions(
fninfo_t *dfnip,
fnnode_t *dfnp,
action_list *alp,
kauth_cred_t cred)
{
action_list *p;
struct mounta *m;
int32_t mount_to, mach_to = 0;
fsid_t fsid;
boolean_t top_level;
int error, success = 0;
vnode_t mvp, dvp, newvp;
fninfo_t *fnip;
fnnode_t *newfnp, *mfnp;
int save_triggers = 0;
int update_times = 0;
char *mntpnt;
char buff[PATH_MAX];
struct timeval now;
struct autofs_globals *fngp;
AUTOFS_DPRINT((4, "auto_perform_actions: alp=%p\n", (void *)alp));
fngp = dfnp->fn_globals;
dvp = fntovn(dfnp);
if (vnode_mountedhere(dvp) != NULL) {
lck_mtx_lock(dfnp->fn_lock);
dfnp->fn_flags |= MF_MOUNTPOINT;
assert(dfnp->fn_dirents == NULL);
lck_mtx_unlock(dfnp->fn_lock);
success++;
} else {
lck_mtx_lock(dfnp->fn_lock);
if (dfnp->fn_flags & MF_MOUNTPOINT) {
AUTOFS_DPRINT((10, "autofs: clearing mountpoint "
"flag on %s.", dfnp->fn_name));
assert(dfnp->fn_dirents == NULL);
assert(dfnp->fn_trigger == NULL);
}
dfnp->fn_flags &= ~MF_MOUNTPOINT;
lck_mtx_unlock(dfnp->fn_lock);
}
for (p = alp; p != NULL; p = p->next) {
mount_t mvfsp;
m = &p->mounta;
mount_to = dfnip->fi_mount_to;
assert(m->dir[0] == '.');
if (m->dir[0] == '.' && m->dir[1] == '\0') {
mvp = dvp;
mntpnt = ".";
vnode_get(mvp);
error = 0;
goto mount;
}
assert(m->dir[1] == '/');
mntpnt = m->dir + 2;
AUTOFS_DPRINT((10, "\tdfnip->fi_path=%s\n", dfnip->fi_path));
AUTOFS_DPRINT((10, "\tdfnip->fi_flags=%x\n", dfnip->fi_flags));
AUTOFS_DPRINT((10, "\tmntpnt=%s\n", mntpnt));
if (dfnip->fi_flags & MF_DIRECT) {
AUTOFS_DPRINT((10, "\tDIRECT\n"));
(void) snprintf(buff, PATH_MAX, "%s/%s",
dfnip->fi_path, mntpnt);
} else {
AUTOFS_DPRINT((10, "\tINDIRECT\n"));
(void) snprintf(buff, PATH_MAX, "%s/%s/%s",
dfnip->fi_path, dfnp->fn_name, mntpnt);
}
if (vnode_mountedhere(dvp) == NULL) {
error = auto_makefnnode(&mfnp, VDIR,
vnode_mount(dvp), NULL, mntpnt, dvp,
0, cred, dfnp->fn_globals);
if (error) {
IOLog("autofs: mount of %s "
"failed - can't create mountpoint (auto_makefnnode failed).\n",
buff);
continue;
}
lck_rw_lock_exclusive(dfnp->fn_rwlock);
error = auto_enter(dfnp, NULL, mntpnt, &mfnp);
if (error) {
if (error == EEXIST) {
if (vnode_mountedhere(fntovn(mfnp)) != NULL) {
panic("auto_perform_actions: "
"mfnp=%p covered", (void *)mfnp);
}
error = 0;
}
} else {
assert((dfnp->fn_flags & MF_MOUNTPOINT) == 0);
assert(mfnp->fn_linkcnt == 1);
}
if (!error)
update_times = 1;
lck_rw_unlock_exclusive(dfnp->fn_rwlock);
if (!error) {
mvp = fntovn(mfnp);
} else {
IOLog("autofs: mount of %s "
"failed - can't create mountpoint.\n",
buff);
continue;
}
} else {
mvp = NULL;
}
mount:
error = auto_send_mount_trigger_request(m->path, mntpnt,
m->path, m->opts, m->map, m->subdir, m->key,
m->flags, m->mntflags, mount_to, mach_to, m->isdirect,
&fsid, &top_level, FALSE);
if (error != 0) {
IOLog(
"autofs: autofs mount of %s failed error=%d\n",
buff, error);
if (mvp != NULL)
vnode_put(mvp);
continue;
}
mvfsp = vfs_getvfs(&fsid);
if (mvfsp != NULL) {
fnip = vfstofni(mvfsp);
newvp = fnip->fi_rootvp;
error = vnode_get(newvp);
if (error) {
error = vfs_unmountbyfsid(&fsid, 0,
vfs_context_current());
if (error && error != ENOENT) {
IOLog(
"autofs: could not "
"unmount vfs=%p\n",
(void *)mvfsp);
}
if (mvp != NULL)
vnode_put(mvp);
continue;
}
} else {
if (mvp != NULL)
vnode_put(mvp);
continue;
}
newfnp = vntofn(newvp);
newfnp->fn_parent = dfnp;
if (!top_level) {
save_triggers++;
newfnp->fn_flags |= MF_TRIGGER;
lck_rw_lock_exclusive(newfnp->fn_rwlock);
newfnp->fn_next = dfnp->fn_trigger;
lck_rw_unlock_exclusive(newfnp->fn_rwlock);
lck_rw_lock_exclusive(dfnp->fn_rwlock);
dfnp->fn_trigger = newfnp;
lck_rw_unlock_exclusive(dfnp->fn_rwlock);
error = vnode_ref(newvp);
if (error != 0) {
panic("vnode_ref failed with %d: %s, line %u",
error, __FILE__, __LINE__);
}
#ifdef DEBUG
AUTOFS_DPRINT((10, "\tadding trigger %s to %s\n",
newfnp->fn_name, dfnp->fn_name));
AUTOFS_DPRINT((10, "\tfirst trigger is %s\n",
dfnp->fn_trigger->fn_name));
if (newfnp->fn_next != NULL)
AUTOFS_DPRINT((10, "\tnext trigger is %s\n",
newfnp->fn_next->fn_name));
else
AUTOFS_DPRINT((10, "\tno next trigger\n"));
#endif
}
vnode_put(newvp);
success++;
if (update_times) {
microtime(&now);
dfnp->fn_atime = dfnp->fn_mtime = now;
}
if (mvp != NULL)
vnode_put(mvp);
}
if (save_triggers) {
error = vnode_ref(dvp);
if (error != 0) {
panic("vnode_ref failed with %d: %s, line %u",
error, __FILE__, __LINE__);
}
}
error = success ? 0 : ENOENT;
if (alp != NULL) {
if ((error == 0) && save_triggers) {
lck_mtx_lock(dfnp->fn_lock);
assert(dfnp->fn_alp == NULL);
dfnp->fn_alp = alp;
lck_mtx_unlock(dfnp->fn_lock);
} else {
free_action_list(alp);
}
}
AUTOFS_DPRINT((5, "auto_perform_actions: error=%d\n", error));
return (error);
}
int
auto_makefnnode(
fnnode_t **fnpp,
enum vtype type,
mount_t mp,
struct componentname *cnp,
const char *name,
vnode_t parent,
int markroot,
kauth_cred_t cred,
struct autofs_globals *fngp)
{
int namelen;
fnnode_t *fnp;
errno_t error;
struct vnode_fsparam vfsp;
vnode_t vp;
char *tmpname;
struct timeval now;
static ino_t nodeid = 3;
#ifdef DEBUG
lck_attr_t *lckattr;
#endif
if (cnp != NULL) {
name = cnp->cn_nameptr;
namelen = cnp->cn_namelen;
} else
namelen = (int)strlen(name);
MALLOC(fnp, fnnode_t *, sizeof(fnnode_t), M_AUTOFS, M_WAITOK);
bzero(fnp, sizeof(*fnp));
fnp->fn_namelen = namelen;
MALLOC(tmpname, char *, fnp->fn_namelen + 1, M_AUTOFS, M_WAITOK);
bcopy(name, tmpname, namelen);
tmpname[namelen] = '\0';
fnp->fn_name = tmpname;
if (cred) {
fnp->fn_uid = kauth_cred_getuid(cred);
fnp->fn_gid = kauth_cred_getgid(cred);
} else {
fnp->fn_uid = 0;
fnp->fn_gid = 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_istrigger = FN_TRIGGER_UNKNOWN;
lck_mtx_lock(autofs_nodeid_lock);
fnp->fn_nodeid = nodeid;
nodeid += 2;
fnp->fn_globals = fngp;
fngp->fng_fnnode_count++;
lck_mtx_unlock(autofs_nodeid_lock);
bzero(&vfsp, sizeof(struct vnode_fsparam));
vfsp.vnfs_mp = mp;
vfsp.vnfs_vtype = type;
vfsp.vnfs_str = MNTTYPE_AUTOFS;
vfsp.vnfs_dvp = parent;
vfsp.vnfs_fsnode = fnp;
vfsp.vnfs_vops = autofs_vnodeop_p;
vfsp.vnfs_markroot = markroot;
vfsp.vnfs_marksystem = 0;
vfsp.vnfs_rdev = 0;
vfsp.vnfs_filesize = 0;
vfsp.vnfs_cnp = cnp;
vfsp.vnfs_flags = VNFS_NOCACHE | VNFS_CANTCACHE;
error = vnode_create(VNCREATE_FLAVOR, VCREATESIZE, &vfsp, &vp);
if (error != 0) {
AUTOFS_DPRINT((5, "auto_makefnnode failed with vnode_create error code %d\n", error));
FREE(fnp->fn_name, M_TEMP);
FREE(fnp, M_TEMP);
return error;
}
fnp->fn_vnode = vp;
if (vnode_ref(vp) == 0)
vnode_rele(vp);
#ifdef DEBUG
lckattr = lck_attr_alloc_init();
lck_attr_setdebug(lckattr);
fnp->fn_lock = lck_mtx_alloc_init(autofs_lck_grp, lckattr);
fnp->fn_rwlock = lck_rw_alloc_init(autofs_lck_grp, lckattr);
lck_attr_free(lckattr);
#else
fnp->fn_lock = lck_mtx_alloc_init(autofs_lck_grp, NULL);
fnp->fn_rwlock = lck_rw_alloc_init(autofs_lck_grp, NULL);
#endif
*fnpp = fnp;
return (0);
}
void
auto_freefnnode(fnnode_t *fnp)
{
vnode_t vp = fntovn(fnp);
AUTOFS_DPRINT((4, "auto_freefnnode: fnp=%p\n", (void *)fnp));
assert(fnp->fn_linkcnt == 0);
assert(!vnode_isinuse(vp, 1));
assert(!vnode_isdir(vp) || fnp->fn_dirents == NULL);
assert(fnp->fn_parent == NULL);
FREE(fnp->fn_name, M_TEMP);
if (vnode_islnk(vp))
FREE(fnp->fn_symlink, M_AUTOFS);
lck_mtx_free(fnp->fn_lock, autofs_lck_grp);
lck_rw_free(fnp->fn_rwlock, autofs_lck_grp);
lck_mtx_lock(autofs_nodeid_lock);
fnp->fn_globals->fng_fnnode_count--;
lck_mtx_unlock(autofs_nodeid_lock);
FREE(fnp, M_AUTOFS);
}
void
auto_disconnect(
fnnode_t *dfnp,
fnnode_t *fnp)
{
fnnode_t *tmp, **fnpp;
vnode_t vp = fntovn(fnp);
int isdir = vnode_isdir(vp);
struct timeval now;
AUTOFS_DPRINT((4,
"auto_disconnect: dfnp=%p fnp=%p linkcnt=%d\n",
(void *)dfnp, (void *)fnp, fnp->fn_linkcnt));
assert(lck_rw_held_exclusive(dfnp->fn_rwlock));
assert(fnp->fn_linkcnt == 1);
fnp->fn_linkcnt--;
fnp->fn_parent = NULL;
fnpp = &dfnp->fn_dirents;
for (;;) {
tmp = *fnpp;
if (tmp == NULL) {
panic(
"auto_disconnect: %p not in %p dirent list",
(void *)fnp, (void *)dfnp);
}
if (tmp == fnp) {
*fnpp = tmp->fn_next;
assert(!vnode_isinuse(vp, 1));
if (isdir) {
dfnp->fn_linkcnt--;
}
dfnp->fn_direntcnt--;
break;
}
fnpp = &tmp->fn_next;
}
lck_mtx_lock(fnp->fn_lock);
microtime(&now);
fnp->fn_atime = fnp->fn_mtime = now;
lck_mtx_unlock(fnp->fn_lock);
AUTOFS_DPRINT((5, "auto_disconnect: done\n"));
}
int
auto_enter(fnnode_t *dfnp, struct componentname *cnp, const char *name,
fnnode_t **fnpp)
{
int namelen;
struct fnnode *cfnp, **spp = NULL;
off_t offset = 0;
off_t diff;
errno_t error;
struct timeval now;
if (cnp != NULL) {
name = cnp->cn_nameptr;
namelen = cnp->cn_namelen;
} else
namelen = (int)strlen(name);
AUTOFS_DPRINT((4, "auto_enter: dfnp=%p, name=%.*s ", (void *)dfnp,
namelen, name));
assert(lck_rw_held_exclusive(dfnp->fn_rwlock));
cfnp = dfnp->fn_dirents;
if (cfnp == NULL) {
spp = &dfnp->fn_dirents;
offset = 2;
}
for (; cfnp; cfnp = cfnp->fn_next) {
if (cfnp->fn_namelen == namelen &&
bcmp(cfnp->fn_name, name, namelen) == 0) {
vnode_put(fntovn(*fnpp));
vnode_recycle(fntovn(*fnpp));
error = vnode_get(fntovn(cfnp));
if (error == 0) {
*fnpp = cfnp;
error = EEXIST;
}
return (error);
}
if (cfnp->fn_next != NULL) {
diff = (off_t)
(cfnp->fn_next->fn_offset - cfnp->fn_offset);
assert(diff != 0);
if (diff > 1 && offset == 0) {
offset = cfnp->fn_offset + 1;
spp = &cfnp->fn_next;
}
} else if (offset == 0) {
offset = cfnp->fn_offset + 1;
spp = &cfnp->fn_next;
}
}
(*fnpp)->fn_offset = offset;
(*fnpp)->fn_next = *spp;
*spp = *fnpp;
(*fnpp)->fn_parent = dfnp;
(*fnpp)->fn_linkcnt++;
if (vnode_isdir(fntovn(*fnpp))) {
dfnp->fn_linkcnt++;
}
dfnp->fn_direntcnt++;
microtime(&now);
dfnp->fn_ref_time = now.tv_sec;
AUTOFS_DPRINT((5, "*fnpp=%p\n", (void *)*fnpp));
return (0);
}
fnnode_t *
auto_search(fnnode_t *dfnp, char *name, int namelen)
{
vnode_t dvp;
fnnode_t *p;
AUTOFS_DPRINT((4, "auto_search: dfnp=%p, name=%.*s...\n",
(void *)dfnp, namelen, name));
dvp = fntovn(dfnp);
if (!vnode_isdir(dvp)) {
panic("auto_search: dvp=%p not a directory", dvp);
}
assert(lck_rw_held(dfnp->fn_rwlock));
for (p = dfnp->fn_dirents; p != NULL; p = p->fn_next) {
if (p->fn_namelen == namelen &&
bcmp(p->fn_name, name, namelen) == 0) {
AUTOFS_DPRINT((5, "auto_search: success\n"));
return (p);
}
}
AUTOFS_DPRINT((5, "auto_search: failure\n"));
return (NULL);
}
#define DEEPER(x) (((x)->fn_dirents != NULL) || \
(vnode_mountedhere(fntovn((x)))) != NULL)
static int
auto_inkernel_unmount(mount_t mp)
{
#if 0
vnode_t *cvp = vfsp->vfs_vnodecovered;
#endif
int error;
AUTOFS_DPRINT((4,
"auto_inkernel_unmount: mntpnt(%p)\n", mp));
#if 0
assert(vn_vfswlock_held(cvp));
#endif
error = vfs_unmountbyfsid(&(vfs_statfs(mp)->f_fsid), 0,
vfs_context_current());
if (error == ENOENT)
error = 0;
AUTOFS_DPRINT((5, "auto_inkernel_unmount: exit\n"));
return (error);
}
static void
unmount_triggers(fnnode_t *fnp, action_list **alp)
{
fnnode_t *tp, *next;
int error = 0;
mount_t mp;
vnode_t tvp;
AUTOFS_DPRINT((4, "unmount_triggers: fnp=%p\n", (void *)fnp));
assert(lck_rw_held_exclusive(fnp->fn_rwlock));
*alp = fnp->fn_alp;
next = fnp->fn_trigger;
while ((tp = next) != NULL) {
tvp = fntovn(tp);
assert(vnode_isinuse(tvp, 1));
next = tp->fn_next;
lck_rw_unlock_exclusive(fnp->fn_rwlock);
mp = vnode_mount(tvp);
vnode_rele(tvp);
if ((error = auto_inkernel_unmount(mp)) != 0) {
panic("unmount_triggers: "
"unmount of vp=%p failed error=%d",
(void *)tvp, error);
}
lck_rw_lock_exclusive(fnp->fn_rwlock);
}
vnode_rele(fntovn(fnp));
fnp->fn_trigger = NULL;
fnp->fn_alp = NULL;
AUTOFS_DPRINT((5, "unmount_triggers: finished\n"));
}
static int
triggers_busy(fnnode_t *fnp, fnnode_t **nfnp)
{
int error = 0, done;
int lck_error = 0;
fnnode_t *tp, *t1p;
mount_t mp;
assert(lck_rw_held_exclusive(fnp->fn_rwlock));
*nfnp = NULL;
for (tp = fnp->fn_trigger; tp != NULL; tp = tp->fn_next) {
AUTOFS_DPRINT((10, "\ttrigger: %s\n", tp->fn_name));
mp = vnode_mount(fntovn(tp));
error = 0;
#if 0
lck_error = vfs_busy(mp, LK_NOWAIT);
#else
lck_error = 0;
#endif
if (lck_error == 0) {
lck_mtx_lock(tp->fn_lock);
assert((tp->fn_flags & MF_LOOKUP) == 0);
if (tp->fn_flags & MF_INPROG) {
error = EBUSY;
}
lck_mtx_unlock(tp->fn_lock);
}
if (lck_error || error || DEEPER(tp) ||
vnode_isinuse(fntovn(tp), 2)) {
AUTOFS_DPRINT((10, "\ttrigger busy\n"));
if ((lck_error == 0) && (error == 0)) {
*nfnp = tp;
vnode_get(fntovn(*nfnp));
}
for (done = 0, t1p = fnp->fn_trigger; !done;
t1p = t1p->fn_next) {
if (t1p != tp || !lck_error) {
mp = vnode_mount(fntovn(t1p));
#if 0
vfs_unbusy(mp);
#endif
}
done = (t1p == tp);
}
error = EBUSY;
break;
}
}
AUTOFS_DPRINT((4, "triggers_busy: error=%d\n", error));
return (error);
}
static int
triggers_unlock(fnnode_t *fnp)
{
fnnode_t *tp;
mount_t mp;
assert(lck_rw_held_exclusive(fnp->fn_rwlock));
for (tp = fnp->fn_trigger; tp != NULL; tp = tp->fn_next) {
AUTOFS_DPRINT((10, "\tunlock trigger: %s\n", tp->fn_name));
mp = vnode_mount(fntovn(tp));
#if 0
vfs_unbusy(mp);
#endif
}
return (0);
}
static int
unmount_node(vnode_t cvp, int nonotify)
{
int error = 0;
fnnode_t *cfnp;
mount_t mp;
struct vfsstatfs *vfsstat;
char *mntopts;
AUTOFS_DPRINT((4, "\tunmount_node cvp=%p\n", (void *)cvp));
#if 0
assert(vn_vfswlock_held(cvp));
#endif
cfnp = vntofn(cvp);
mp = vnode_mountedhere(cvp);
vfsstat = vfs_statfs(mp);
if (cfnp->fn_fsid_mounted.val[0] != vfsstat->f_fsid.val[0] ||
cfnp->fn_fsid_mounted.val[1] != vfsstat->f_fsid.val[1])
return (EBUSY);
if (nonotify || auto_is_autofs(mp)) {
error = auto_inkernel_unmount(mp);
} else {
mntopts = "";
#if 0
vfs_unbusy(mp);
#endif
error = auto_send_unmount_request(vfsstat->f_fsid,
vfsstat->f_mntfromname, vfsstat->f_mntonname,
vfsstat->f_fstypename, mntopts, FALSE);
}
AUTOFS_DPRINT((5, "\tunmount_node cvp=%p error=%d\n", (void *)cvp,
error));
return (error);
}
static int
check_auto_node(vnode_t vp)
{
fnnode_t *fnp, *childfnp;
int error = 0;
u_int count;
AUTOFS_DPRINT((4, "\tcheck_auto_node vp=%p ", (void *)vp));
fnp = vntofn(vp);
assert(fnp->fn_flags & MF_INPROG);
assert((fnp->fn_flags & MF_LOOKUP) == 0);
count = 0;
if (fnp->fn_flags & MF_TRIGGER) {
count++;
}
if (fnp->fn_trigger != NULL) {
count++;
}
if (vnode_isvroot(vp))
count++;
for (childfnp = fnp->fn_dirents; childfnp != NULL;
childfnp = childfnp->fn_next)
count++;
assert(vnode_isinuse(vp, 0));
if (vnode_isinuse(vp, count))
error = EBUSY;
AUTOFS_DPRINT((5, "\tcheck_auto_node error=%d ", error));
return (error);
}
static int
unmount_autofs(vnode_t auto_rootvp)
{
fnnode_t *fnp, *rootfnp, *nfnp;
vnode_t vp;
int error;
AUTOFS_DPRINT((4, "\tunmount_autofs auto_rootvp=%p ", (void *)auto_rootvp));
rootfnp = vntofn(auto_rootvp);
lck_rw_lock_exclusive(rootfnp->fn_rwlock);
error = check_auto_node(auto_rootvp);
if (error == 0) {
nfnp = NULL;
for (fnp = rootfnp->fn_dirents; fnp != NULL; fnp = nfnp) {
vp = fntovn(fnp);
assert(!vnode_isinuse(vp, 1));
auto_disconnect(rootfnp, fnp);
nfnp = fnp->fn_next;
vnode_recycle(fntovn(fnp));
}
}
lck_rw_unlock_exclusive(rootfnp->fn_rwlock);
AUTOFS_DPRINT((5, "\tunmount_autofs error=%d ", error));
return (error);
}
static int autofs_unmount_threads = 5;
void
unmount_tree(struct autofs_globals *fngp, fsid_t *fsidp, int flags)
{
vnode_t vp, newvp;
mount_t mp;
fnnode_t *fnp, *nfnp, *pfnp;
action_list *alp;
int error, ilocked_it = 0;
fninfo_t *fnip;
time_t ref_time;
int autofs_busy_root, unmount_as_unit, unmount_done = 0;
struct timeval now;
lck_rw_lock_shared(fngp->fng_rootfnnodep->fn_rwlock);
if ((fnp = fngp->fng_rootfnnodep->fn_dirents) == NULL) {
assert(fngp->fng_fnnode_count == 1);
lck_rw_unlock_shared(fngp->fng_rootfnnodep->fn_rwlock);
goto done;
}
error = vnode_get(fntovn(fnp));
lck_rw_unlock_shared(fngp->fng_rootfnnodep->fn_rwlock);
vp = fntovn(fnp);
fnip = vfstofni(vnode_mount(vp));
microtime(&now);
ref_time = now.tv_sec;
lck_mtx_lock(fnp->fn_lock);
if ((flags & UNMOUNT_TREE_IMMEDIATE) &&
fnp->fn_unmount_ref_time >= ref_time)
ref_time = fnp->fn_unmount_ref_time + 1;
lck_mtx_unlock(fnp->fn_lock);
AUTOFS_DPRINT((4, "unmount_tree (ID=%ld)\n", (long)ref_time));
top:
AUTOFS_DPRINT((10, "unmount_tree: %s\n", fnp->fn_name));
assert(fnp);
vp = fntovn(fnp);
if (vnode_islnk(vp)) {
goto next;
}
mp = vnode_mount(vp);
if (fnp->fn_parent == fngp->fng_rootfnnodep &&
fsidp != NULL) {
struct vfsstatfs *vfsstat = vfs_statfs(mp);
if (vfsstat->f_fsid.val[0] != fsidp->val[0] ||
vfsstat->f_fsid.val[1] != fsidp->val[1]) {
goto next;
}
}
fnip = vfstofni(mp);
assert(vnode_isinuse(vp, 0));
error = 0;
autofs_busy_root = unmount_as_unit = 0;
alp = NULL;
ilocked_it = 0;
lck_mtx_lock(fnp->fn_lock);
if (fnp->fn_flags & (MF_INPROG | MF_LOOKUP)) {
lck_mtx_unlock(fnp->fn_lock);
error = EBUSY;
goto next;
}
if (fnp->fn_unmount_ref_time >= ref_time) {
lck_mtx_unlock(fnp->fn_lock);
error = EBUSY;
goto next;
}
fnp->fn_unmount_ref_time = ref_time;
if (!(flags & UNMOUNT_TREE_IMMEDIATE)) {
microtime(&now);
if (fnp->fn_ref_time + fnip->fi_mount_to >
now.tv_sec) {
lck_mtx_unlock(fnp->fn_lock);
AUTOFS_DPRINT((10, "fn_ref_time within range\n"));
lck_rw_lock_shared(fnp->fn_rwlock);
if (fnp->fn_dirents) {
nfnp = fnp->fn_dirents;
error = vnode_get(fntovn(nfnp));
lck_rw_unlock_shared(fnp->fn_rwlock);
vnode_put(vp);
fnp = nfnp;
goto top;
}
lck_rw_unlock_shared(fnp->fn_rwlock);
error = EBUSY;
goto next;
}
}
AUTOFS_BLOCK_OTHERS(fnp, MF_INPROG);
fnp->fn_error = 0;
lck_mtx_unlock(fnp->fn_lock);
ilocked_it = 1;
lck_rw_lock_exclusive(fnp->fn_rwlock);
if (fnp->fn_trigger != NULL) {
unmount_as_unit = 1;
if ((vnode_mountedhere(vp) == NULL) && (check_auto_node(vp))) {
autofs_busy_root = 1;
}
if (triggers_busy(fnp, &nfnp)) {
lck_rw_unlock_exclusive(fnp->fn_rwlock);
if (nfnp == NULL) {
error = EBUSY;
goto next;
}
lck_mtx_lock(fnp->fn_lock);
AUTOFS_UNBLOCK_OTHERS(fnp, MF_INPROG);
lck_mtx_unlock(fnp->fn_lock);
vnode_put(vp);
assert(vnode_isinuse(fntovn(nfnp), 1));
fnp = nfnp;
goto top;
}
if (autofs_busy_root) {
(void) triggers_unlock(fnp);
} else {
unmount_triggers(fnp, &alp);
}
}
lck_rw_unlock_exclusive(fnp->fn_rwlock);
if (autofs_busy_root)
goto next;
mp = vnode_mountedhere(vp);
if (mp != NULL) {
AUTOFS_DPRINT((10, "\tNode is mounted on\n"));
if (auto_is_autofs(mp)) {
AUTOFS_DPRINT((10, "\t\tAUTOFS mounted here\n"));
fnip = vfstofni(mp);
newvp = fnip->fi_rootvp;
error = vnode_get(newvp);
if (error) {
panic("unmount_tree: vnode_get on root of vfs=%p failed",
(void *)mp);
}
nfnp = vntofn(newvp);
if (DEEPER(nfnp)) {
#if 0
vfs_unbusy(mp);
#endif
lck_mtx_lock(fnp->fn_lock);
AUTOFS_UNBLOCK_OTHERS(fnp, MF_INPROG);
lck_mtx_unlock(fnp->fn_lock);
vnode_put(vp);
fnp = nfnp;
goto top;
}
vnode_put(newvp);
}
error = unmount_node(vp, (flags & UNMOUNT_TREE_NONOTIFY));
if (error == ECONNRESET) {
AUTOFS_DPRINT((10, "\tConnection dropped\n"));
if (vnode_mountedhere(vp) == NULL) {
error = 0;
IOLog(
"unmount_tree: automountd connection "
"dropped\n");
if (fnip->fi_flags & MF_DIRECT) {
IOLog("unmount_tree: "
"%s successfully unmounted - "
"do not remount triggers\n",
fnip->fi_path);
} else {
IOLog("unmount_tree: "
"%s/%s successfully unmounted - "
"do not remount triggers\n",
fnip->fi_path, fnp->fn_name);
}
}
}
} else {
#if 0
vfs_unbusy(mp);
#endif
AUTOFS_DPRINT((10, "\tNode is AUTOFS\n"));
if (unmount_as_unit) {
AUTOFS_DPRINT((10, "\tunmount as unit\n"));
error = unmount_autofs(vp);
} else {
AUTOFS_DPRINT((10, "\tunmount one at a time\n"));
lck_rw_lock_shared(fnp->fn_rwlock);
if (fnp->fn_dirents != NULL) {
nfnp = fnp->fn_dirents;
error = vnode_get(fntovn(nfnp));
lck_rw_unlock_shared(fnp->fn_rwlock);
lck_mtx_lock(fnp->fn_lock);
AUTOFS_UNBLOCK_OTHERS(fnp, MF_INPROG);
lck_mtx_unlock(fnp->fn_lock);
vnode_put(vp);
fnp = nfnp;
goto top;
}
lck_rw_unlock_shared(fnp->fn_rwlock);
goto next;
}
}
if (error) {
AUTOFS_DPRINT((10, "\tUnmount failed\n"));
if (alp != NULL) {
error = auto_perform_actions(fnip, fnp, alp, NULL);
if (error) {
IOLog("autofs: can't remount "
"triggers fnp=%p error=%d\n", (void *)fnp,
error);
error = 0;
alp = NULL;
}
}
} else {
unmount_done = 1;
if ((fnip->fi_flags & MF_DIRECT) == 0) {
microtime(&now);
if (fnp->fn_parent == fngp->fng_rootfnnodep)
fnp->fn_atime = fnp->fn_mtime = now;
else
fnp->fn_parent->fn_atime =
fnp->fn_parent->fn_mtime = now;
}
if (alp != NULL) {
free_action_list(alp);
alp = NULL;
}
}
microtime(&now);
fnp->fn_ref_time = now.tv_sec;
next:
pfnp = fnp->fn_parent;
assert(pfnp != NULL);
lck_rw_lock_shared(pfnp->fn_rwlock);
if ((nfnp = fnp->fn_next) != NULL)
error = vnode_get(fntovn(nfnp));
lck_rw_unlock_shared(pfnp->fn_rwlock);
if (ilocked_it) {
lck_mtx_lock(fnp->fn_lock);
if (unmount_done) {
if (fnp->fn_flags & MF_WAITING)
fnp->fn_error = EAGAIN;
unmount_done = 0;
}
AUTOFS_UNBLOCK_OTHERS(fnp, MF_INPROG);
lck_mtx_unlock(fnp->fn_lock);
ilocked_it = 0;
}
if (nfnp != NULL) {
vnode_put(vp);
fnp = nfnp;
goto top;
}
assert(pfnp != fnp);
if (pfnp != fngp->fng_rootfnnodep) {
error = vnode_get(fntovn(pfnp));
vnode_put(vp);
fnp = pfnp;
goto top;
}
vnode_put(vp);
done:
;
}
static void
unmount_zone_tree(void *arg)
{
struct autofs_globals *fngp = arg;
unmount_tree(fngp, NULL, 0);
lck_mtx_lock(fngp->fng_unmount_threads_lock);
fngp->fng_unmount_threads--;
lck_mtx_unlock(fngp->fng_unmount_threads_lock);
AUTOFS_DPRINT((5, "unmount_tree done. Thread exiting.\n"));
thread_terminate(current_thread());
}
static struct timespec autofs_unmount_thread_timer = {
120,
0
};
void
auto_do_unmount(void *arg)
{
struct autofs_globals *fngp = arg;
for (;;) {
lck_mtx_lock(fngp->fng_unmount_threads_lock);
newthread:
msleep(&fngp->fng_terminate_do_unmount_thread,
fngp->fng_unmount_threads_lock, PWAIT,
"unmount timeout", &autofs_unmount_thread_timer);
if (fngp->fng_terminate_do_unmount_thread) {
fngp->fng_do_unmount_thread_terminated = 1;
wakeup(&fngp->fng_do_unmount_thread_terminated);
lck_mtx_unlock(fngp->fng_unmount_threads_lock);
thread_terminate(current_thread());
}
if (fngp->fng_unmount_threads < autofs_unmount_threads) {
fngp->fng_unmount_threads++;
lck_mtx_unlock(fngp->fng_unmount_threads_lock);
(void) auto_new_thread(unmount_zone_tree, fngp);
} else
goto newthread;
}
}
int
auto_dont_trigger(vnode_t vp, vfs_context_t context)
{
fnnode_t *fnp;
int pid;
fnp = vntofn(vp);
if (vnode_mountedhere(vp) != NULL) {
return (1);
}
pid = vfs_context_pid(context);
if (auto_is_automounter(pid)) {
return (1);
}
return (0);
}
kern_return_t
auto_new_thread(void (*start)(void *), void *arg)
{
kern_return_t result;
thread_t thread;
result = kernel_thread_start((thread_continue_t)start, arg, &thread);
if (result != KERN_SUCCESS)
return (result);
thread_deallocate(thread);
return (KERN_SUCCESS);
}
#ifdef DEBUG
static int autofs_debug = 0;
void
auto_dprint(int level, const char *fmt, ...)
{
va_list args;
if (autofs_debug == level ||
(autofs_debug > 10 && (autofs_debug - 10) >= level)) {
va_start(args, fmt);
IOLogv(fmt, args);
va_end(args);
}
}
void
auto_debug_set(int level)
{
autofs_debug = level;
}
#endif