#include <sys/param.h>
#include <sys/kmem.h>
#include <sys/errno.h>
#include <sys/proc.h>
#include <sys/disp.h>
#include <sys/vfs.h>
#include <sys/vnode.h>
#include <sys/pathname.h>
#include <sys/cred.h>
#include <sys/mount.h>
#include <sys/cmn_err.h>
#include <sys/debug.h>
#include <sys/systm.h>
#include <sys/dirent.h>
#include <fs/fs_subr.h>
#include <sys/fs/autofs.h>
#include <sys/callb.h>
#include <sys/sysmacros.h>
#include <sys/zone.h>
#include <sys/door.h>
#include <sys/fs/mntdata.h>
#include <nfs/mount.h>
#include <rpc/clnt.h>
#include <rpcsvc/autofs_prot.h>
#include <nfs/rnode.h>
#include <sys/utsname.h>
#include <sys/schedctl.h>
#define TYPICALMAXPATHLEN 64
static kmutex_t autofs_nodeid_lock;
static int autofs_unmount_threads = 5;
static int autofs_unmount_thread_timer = 120;
static int auto_perform_link(fnnode_t *, struct linka *, cred_t *);
static int auto_perform_actions(fninfo_t *, fnnode_t *,
action_list *, cred_t *);
static int auto_getmntpnt(vnode_t *, char *, vnode_t **, cred_t *);
static int auto_lookup_request(fninfo_t *, char *, struct linka *,
bool_t, bool_t *, cred_t *);
static int auto_mount_request(fninfo_t *, char *, action_list **, cred_t *,
bool_t);
void
auto_unblock_others(
fnnode_t *fnp,
uint_t operation)
{
ASSERT(operation & (MF_INPROG | MF_LOOKUP));
fnp->fn_flags &= ~operation;
if (fnp->fn_flags & MF_WAITING) {
fnp->fn_flags &= ~MF_WAITING;
cv_broadcast(&fnp->fn_cv_mount);
}
}
int
auto_wait4mount(fnnode_t *fnp)
{
int error;
k_sigset_t smask;
AUTOFS_DPRINT((4, "auto_wait4mount: fnp=%p\n", (void *)fnp));
mutex_enter(&fnp->fn_lock);
while (fnp->fn_flags & (MF_INPROG | MF_LOOKUP)) {
fnp->fn_flags |= MF_WAITING;
sigintr(&smask, 1);
if (!cv_wait_sig(&fnp->fn_cv_mount, &fnp->fn_lock)) {
sigunintr(&smask);
mutex_exit(&fnp->fn_lock);
return (EINTR);
}
sigunintr(&smask);
}
error = fnp->fn_error;
if (error == EINTR) {
error = EAGAIN;
}
mutex_exit(&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, cred_t *cred)
{
struct fninfo *fnip;
struct linka link;
bool_t mountreq = FALSE;
int error = 0;
fnip = vfstofni(fntovn(fnp)->v_vfsp);
bzero(&link, sizeof (link));
error = auto_lookup_request(fnip, name, &link, TRUE, &mountreq, cred);
if (!error) {
if (link.link != NULL) {
error = ENOENT;
if (*link.link != '\0')
error = auto_perform_link(fnp, &link, cred);
} else if (mountreq) {
mutex_enter(&fnp->fn_lock);
AUTOFS_BLOCK_OTHERS(fnp, MF_INPROG);
fnp->fn_error = 0;
AUTOFS_UNBLOCK_OTHERS(fnp, MF_LOOKUP);
mutex_exit(&fnp->fn_lock);
auto_new_mount_thread(fnp, name, cred);
error = auto_wait4mount(fnp);
if (error == AUTOFS_SHUTDOWN)
error = ENOENT;
}
}
if (link.link)
kmem_free(link.link, strlen(link.link) + 1);
if (link.dir)
kmem_free(link.dir, strlen(link.dir) + 1);
mutex_enter(&fnp->fn_lock);
fnp->fn_error = error;
if (mountreq) {
AUTOFS_UNBLOCK_OTHERS(fnp, MF_INPROG);
} else {
AUTOFS_UNBLOCK_OTHERS(fnp, MF_LOOKUP);
}
mutex_exit(&fnp->fn_lock);
return (error);
}
static void
auto_mount_thread(struct autofs_callargs *argsp)
{
struct fninfo *fnip;
fnnode_t *fnp;
vnode_t *vp;
char *name;
size_t namelen;
cred_t *cred;
action_list *alp = NULL;
int error;
callb_cpr_t cprinfo;
kmutex_t auto_mount_thread_cpr_lock;
mutex_init(&auto_mount_thread_cpr_lock, NULL, MUTEX_DEFAULT, NULL);
CALLB_CPR_INIT(&cprinfo, &auto_mount_thread_cpr_lock,
callb_generic_cpr, "auto_mount_thread");
fnp = argsp->fnc_fnp;
vp = fntovn(fnp);
fnip = vfstofni(vp->v_vfsp);
name = argsp->fnc_name;
cred = argsp->fnc_cred;
ASSERT(crgetzoneid(argsp->fnc_cred) == fnip->fi_zoneid);
error = auto_mount_request(fnip, name, &alp, cred, TRUE);
if (!error)
error = auto_perform_actions(fnip, fnp, alp, cred);
mutex_enter(&fnp->fn_lock);
fnp->fn_error = error;
AUTOFS_UNBLOCK_OTHERS(fnp, MF_INPROG);
mutex_exit(&fnp->fn_lock);
VN_RELE(vp);
crfree(argsp->fnc_cred);
namelen = strlen(argsp->fnc_name) + 1;
kmem_free(argsp->fnc_name, namelen);
kmem_free(argsp, sizeof (*argsp));
mutex_enter(&auto_mount_thread_cpr_lock);
CALLB_CPR_EXIT(&cprinfo);
mutex_destroy(&auto_mount_thread_cpr_lock);
zthread_exit();
}
static int autofs_thr_success = 0;
void
auto_new_mount_thread(fnnode_t *fnp, char *name, cred_t *cred)
{
struct autofs_callargs *argsp;
argsp = kmem_alloc(sizeof (*argsp), KM_SLEEP);
VN_HOLD(fntovn(fnp));
argsp->fnc_fnp = fnp;
argsp->fnc_name = kmem_alloc(strlen(name) + 1, KM_SLEEP);
(void) strcpy(argsp->fnc_name, name);
argsp->fnc_origin = curthread;
crhold(cred);
argsp->fnc_cred = cred;
(void) zthread_create(NULL, 0, auto_mount_thread, argsp, 0,
minclsyspri);
autofs_thr_success++;
}
#define DOOR_BUF_ALIGN (1024*1024)
#define DOOR_BUF_MULTIPLIER 3
#define DOOR_BUF_DEFAULT_SZ (DOOR_BUF_MULTIPLIER * DOOR_BUF_ALIGN)
int doorbuf_defsz = DOOR_BUF_DEFAULT_SZ;
int
auto_calldaemon(
zoneid_t zoneid,
int which,
xdrproc_t xarg_func,
void *argsp,
xdrproc_t xresp_func,
void *resp,
int reslen,
bool_t hard)
{
int retry;
int error = 0;
k_sigset_t smask;
door_arg_t door_args;
door_handle_t dh;
XDR xdrarg;
XDR xdrres;
struct autofs_globals *fngp = NULL;
void *orp = NULL;
int orl;
int rlen = 0;
autofs_door_args_t *xdr_argsp;
int xdr_len = 0;
int printed_not_running_msg = 0;
klwp_t *lwp = ttolwp(curthread);
ASSERT(zoneid == getzoneid());
if (zone_status_get(curproc->p_zone) >= ZONE_IS_SHUTTING_DOWN) {
return (ECONNREFUSED);
}
do {
retry = 0;
mutex_enter(&autofs_minor_lock);
fngp = zone_getspecific(autofs_key, curproc->p_zone);
mutex_exit(&autofs_minor_lock);
if (fngp == NULL) {
if (hard) {
AUTOFS_DPRINT((5,
"auto_calldaemon: "\
"failed to get door handle\n"));
if (!printed_not_running_msg) {
printed_not_running_msg = 1;
zprintf(zoneid, "automountd not "\
"running, retrying\n");
}
delay(hz);
retry = 1;
} else {
return (ECONNREFUSED);
}
}
} while (retry);
if (printed_not_running_msg) {
fngp->fng_printed_not_running_msg = printed_not_running_msg;
}
ASSERT(fngp != NULL);
if (argsp != NULL && (xdr_len = xdr_sizeof(xarg_func, argsp)) == 0)
return (EINVAL);
xdr_argsp = kmem_zalloc(xdr_len + sizeof (*xdr_argsp), KM_SLEEP);
xdr_argsp->xdr_len = xdr_len;
xdr_argsp->cmd = which;
if (argsp) {
xdrmem_create(&xdrarg, (char *)&xdr_argsp->xdr_arg,
xdr_argsp->xdr_len, XDR_ENCODE);
if (!(*xarg_func)(&xdrarg, argsp)) {
kmem_free(xdr_argsp, xdr_len + sizeof (*xdr_argsp));
return (EINVAL);
}
}
if (resp)
rlen = xdr_sizeof(xresp_func, resp);
orl = (rlen == 0) ? doorbuf_defsz : MAX(rlen, doorbuf_defsz);
orp = kmem_zalloc(orl, KM_SLEEP);
do {
retry = 0;
mutex_enter(&fngp->fng_autofs_daemon_lock);
dh = fngp->fng_autofs_daemon_dh;
if (dh)
door_ki_hold(dh);
mutex_exit(&fngp->fng_autofs_daemon_lock);
if (dh == NULL) {
if (orp)
kmem_free(orp, orl);
kmem_free(xdr_argsp, xdr_len + sizeof (*xdr_argsp));
return (ENOENT);
}
door_args.data_ptr = (char *)xdr_argsp;
door_args.data_size = sizeof (*xdr_argsp) + xdr_argsp->xdr_len;
door_args.desc_ptr = NULL;
door_args.desc_num = 0;
door_args.rbuf = orp ? (char *)orp : NULL;
door_args.rsize = orl;
sigintr(&smask, 1);
error =
door_ki_upcall_limited(dh, &door_args, NULL, SIZE_MAX, 0);
sigunintr(&smask);
door_ki_rele(dh);
if (!error) {
autofs_door_res_t *adr =
(autofs_door_res_t *)door_args.rbuf;
if (door_args.rbuf != NULL) {
int nl;
switch (error = adr->res_status) {
case 0:
break;
case EOVERFLOW:
if ((nl = adr->xdr_len) > 0 &&
(btopr(nl) < freemem/64)) {
if (orp)
kmem_free(orp, orl);
orp = kmem_zalloc(nl, KM_SLEEP);
orl = nl;
retry = 1;
break;
}
default:
kmem_free(xdr_argsp,
xdr_len + sizeof (*xdr_argsp));
if (orp)
kmem_free(orp, orl);
return (error);
}
}
continue;
}
switch (error) {
case EINTR:
if (lwp && (ISSIG(curthread,
JUSTLOOKING) || MUSTRETURN(curproc, curthread))) {
if (ISSIG(curthread, FORREAL) ||
lwp->lwp_sysabort ||
MUSTRETURN(curproc, curthread)) {
lwp->lwp_sysabort = 0;
return (EINTR);
}
}
if (schedctl_cancel_pending())
break;
case EAGAIN:
delay(hz);
retry = 1;
break;
case EBADF:
case EINVAL:
mutex_enter(&fngp->fng_autofs_daemon_lock);
if (dh == fngp->fng_autofs_daemon_dh) {
door_ki_rele(fngp->fng_autofs_daemon_dh);
fngp->fng_autofs_daemon_dh = NULL;
}
mutex_exit(&fngp->fng_autofs_daemon_lock);
AUTOFS_DPRINT((5, "auto_calldaemon error=%d\n", error));
if (hard) {
if (!fngp->fng_printed_not_running_msg) {
fngp->fng_printed_not_running_msg = 1;
zprintf(zoneid, "automountd not "
"running, retrying\n");
}
delay(hz);
retry = 1;
break;
} else {
error = ECONNREFUSED;
kmem_free(xdr_argsp,
xdr_len + sizeof (*xdr_argsp));
if (orp)
kmem_free(orp, orl);
return (error);
}
default:
error = ENOENT;
kmem_free(xdr_argsp, xdr_len + sizeof (*xdr_argsp));
if (orp)
kmem_free(orp, orl);
return (error);
}
} while (retry);
if (fngp->fng_printed_not_running_msg == 1) {
fngp->fng_printed_not_running_msg = 0;
zprintf(zoneid, "automountd OK\n");
}
if (orp && orl) {
autofs_door_res_t *door_resp;
door_resp = (autofs_door_res_t *)door_args.rbuf;
if ((void *)door_args.rbuf != orp)
kmem_free(orp, orl);
xdrmem_create(&xdrres, (char *)&door_resp->xdr_res,
door_resp->xdr_len, XDR_DECODE);
if (!((*xresp_func)(&xdrres, resp)))
error = EINVAL;
kmem_free(door_args.rbuf, door_args.rsize);
}
kmem_free(xdr_argsp, xdr_len + sizeof (*xdr_argsp));
return (error);
}
static int
auto_null_request(zoneid_t zoneid, bool_t hard)
{
int error;
AUTOFS_DPRINT((4, "\tauto_null_request\n"));
error = auto_calldaemon(zoneid, NULLPROC,
xdr_void, NULL, xdr_void, NULL, 0, hard);
AUTOFS_DPRINT((5, "\tauto_null_request: error=%d\n", error));
return (error);
}
static int
auto_lookup_request(
fninfo_t *fnip,
char *key,
struct linka *lnp,
bool_t hard,
bool_t *mountreq,
cred_t *cred)
{
int error;
struct autofs_globals *fngp;
struct autofs_lookupargs reqst;
autofs_lookupres *resp;
struct linka *p;
AUTOFS_DPRINT((4, "auto_lookup_equest: path=%s name=%s\n",
fnip->fi_path, key));
fngp = vntofn(fnip->fi_rootvp)->fn_globals;
reqst.map = fnip->fi_map;
reqst.path = fnip->fi_path;
if (fnip->fi_flags & MF_DIRECT)
reqst.name = fnip->fi_key;
else
reqst.name = key;
AUTOFS_DPRINT((4, "auto_lookup_request: using key=%s\n", reqst.name));
reqst.subdir = fnip->fi_subdir;
reqst.opts = fnip->fi_opts;
reqst.isdirect = fnip->fi_flags & MF_DIRECT ? TRUE : FALSE;
reqst.uid = crgetuid(cred);
resp = kmem_zalloc(sizeof (*resp), KM_SLEEP);
error = auto_calldaemon(fngp->fng_zoneid, AUTOFS_LOOKUP,
xdr_autofs_lookupargs, &reqst, xdr_autofs_lookupres,
(void *)resp, sizeof (autofs_lookupres), hard);
if (error) {
xdr_free(xdr_autofs_lookupres, (char *)resp);
kmem_free(resp, sizeof (*resp));
return (error);
}
if (!error) {
fngp->fng_verbose = resp->lu_verbose;
switch (resp->lu_res) {
case AUTOFS_OK:
switch (resp->lu_type.action) {
case AUTOFS_MOUNT_RQ:
lnp->link = NULL;
lnp->dir = NULL;
*mountreq = TRUE;
break;
case AUTOFS_LINK_RQ:
p = &resp->lu_type.lookup_result_type_u.lt_linka;
lnp->dir = kmem_alloc(strlen(p->dir) + 1,
KM_SLEEP);
(void) strcpy(lnp->dir, p->dir);
lnp->link = kmem_alloc(strlen(p->link) + 1,
KM_SLEEP);
(void) strcpy(lnp->link, p->link);
break;
case AUTOFS_NONE:
lnp->link = NULL;
lnp->dir = NULL;
break;
default:
auto_log(fngp->fng_verbose, fngp->fng_zoneid,
CE_WARN, "auto_lookup_request: bad action "
"type %d", resp->lu_res);
error = ENOENT;
}
break;
case AUTOFS_NOENT:
error = ENOENT;
break;
default:
error = ENOENT;
auto_log(fngp->fng_verbose, fngp->fng_zoneid, CE_WARN,
"auto_lookup_request: unknown result: %d",
resp->lu_res);
break;
}
}
xdr_free(xdr_autofs_lookupres, (char *)resp);
kmem_free(resp, sizeof (*resp));
AUTOFS_DPRINT((5, "auto_lookup_request: path=%s name=%s error=%d\n",
fnip->fi_path, key, error));
return (error);
}
static int
auto_mount_request(
fninfo_t *fnip,
char *key,
action_list **alpp,
cred_t *cred,
bool_t hard)
{
int error;
struct autofs_globals *fngp;
autofs_lookupargs reqst;
autofs_mountres *xdrres = NULL;
AUTOFS_DPRINT((4, "auto_mount_request: path=%s name=%s\n",
fnip->fi_path, key));
fngp = vntofn(fnip->fi_rootvp)->fn_globals;
reqst.map = fnip->fi_map;
reqst.path = fnip->fi_path;
if (fnip->fi_flags & MF_DIRECT)
reqst.name = fnip->fi_key;
else
reqst.name = key;
AUTOFS_DPRINT((4, "auto_mount_request: using key=%s\n", reqst.name));
reqst.subdir = fnip->fi_subdir;
reqst.opts = fnip->fi_opts;
reqst.isdirect = fnip->fi_flags & MF_DIRECT ? TRUE : FALSE;
reqst.uid = crgetuid(cred);
xdrres = kmem_zalloc(sizeof (*xdrres), KM_SLEEP);
error = auto_calldaemon(fngp->fng_zoneid, AUTOFS_MNTINFO,
xdr_autofs_lookupargs, &reqst, xdr_autofs_mountres,
(void *)xdrres, sizeof (autofs_mountres), hard);
if (!error) {
fngp->fng_verbose = xdrres->mr_verbose;
switch (xdrres->mr_type.status) {
case AUTOFS_ACTION:
error = 0;
*alpp = xdrres->mr_type.mount_result_type_u.list;
xdrres->mr_type.mount_result_type_u.list = NULL;
break;
case AUTOFS_DONE:
error = xdrres->mr_type.mount_result_type_u.error;
break;
default:
error = ENOENT;
auto_log(fngp->fng_verbose, fngp->fng_zoneid, CE_WARN,
"auto_mount_request: unknown status %d",
xdrres->mr_type.status);
break;
}
}
xdr_free(xdr_autofs_mountres, (char *)xdrres);
kmem_free(xdrres, sizeof (*xdrres));
AUTOFS_DPRINT((5, "auto_mount_request: path=%s name=%s error=%d\n",
fnip->fi_path, key, error));
return (error);
}
static int
auto_send_unmount_request(
fninfo_t *fnip,
umntrequest *ul,
bool_t hard)
{
int error;
umntres xdrres;
struct autofs_globals *fngp = vntofn(fnip->fi_rootvp)->fn_globals;
AUTOFS_DPRINT((4, "\tauto_send_unmount_request: fstype=%s "
" mntpnt=%s\n", ul->fstype, ul->mntpnt));
bzero(&xdrres, sizeof (umntres));
error = auto_calldaemon(fngp->fng_zoneid, AUTOFS_UNMOUNT,
xdr_umntrequest, (void *)ul, xdr_umntres, (void *)&xdrres,
sizeof (umntres), hard);
if (!error)
error = xdrres.status;
AUTOFS_DPRINT((5, "\tauto_send_unmount_request: error=%d\n", error));
return (error);
}
static int
auto_perform_link(fnnode_t *fnp, struct linka *linkp, cred_t *cred)
{
vnode_t *vp;
size_t len;
char *tmp;
AUTOFS_DPRINT((3, "auto_perform_link: fnp=%p dir=%s link=%s\n",
(void *)fnp, linkp->dir, linkp->link));
len = strlen(linkp->link) + 1;
tmp = kmem_zalloc(len, KM_SLEEP);
(void) kcopy(linkp->link, tmp, len);
mutex_enter(&fnp->fn_lock);
fnp->fn_symlink = tmp;
fnp->fn_symlinklen = (uint_t)len;
fnp->fn_flags |= MF_THISUID_MATCH_RQD;
crhold(cred);
fnp->fn_cred = cred;
mutex_exit(&fnp->fn_lock);
vp = fntovn(fnp);
vp->v_type = VLNK;
return (0);
}
static void
auto_free_autofs_args(struct mounta *m)
{
autofs_args *aargs = (autofs_args *)m->dataptr;
if (aargs->addr.buf)
kmem_free(aargs->addr.buf, aargs->addr.len);
if (aargs->path)
kmem_free(aargs->path, strlen(aargs->path) + 1);
if (aargs->opts)
kmem_free(aargs->opts, strlen(aargs->opts) + 1);
if (aargs->map)
kmem_free(aargs->map, strlen(aargs->map) + 1);
if (aargs->subdir)
kmem_free(aargs->subdir, strlen(aargs->subdir) + 1);
if (aargs->key)
kmem_free(aargs->key, strlen(aargs->key) + 1);
kmem_free(aargs, sizeof (*aargs));
}
static void
auto_free_action_list(action_list *alp)
{
struct mounta *m;
action_list *lastalp;
char *fstype;
m = &alp->action.action_list_entry_u.mounta;
while (alp != NULL) {
fstype = alp->action.action_list_entry_u.mounta.fstype;
m = &alp->action.action_list_entry_u.mounta;
if (m->dataptr) {
if (strcmp(fstype, "autofs") == 0) {
auto_free_autofs_args(m);
}
}
if (m->spec)
kmem_free(m->spec, strlen(m->spec) + 1);
if (m->dir)
kmem_free(m->dir, strlen(m->dir) + 1);
if (m->fstype)
kmem_free(m->fstype, strlen(m->fstype) + 1);
if (m->optptr)
kmem_free(m->optptr, m->optlen);
lastalp = alp;
alp = alp->next;
kmem_free(lastalp, sizeof (*lastalp));
}
}
static boolean_t
auto_invalid_autofs(fninfo_t *dfnip, fnnode_t *dfnp, action_list *p)
{
struct mounta *m;
struct autofs_args *argsp;
vnode_t *dvp;
char buff[AUTOFS_MAXPATHLEN];
size_t len;
struct autofs_globals *fngp;
fngp = dfnp->fn_globals;
dvp = fntovn(dfnp);
m = &p->action.action_list_entry_u.mounta;
if (m->spec == NULL || m->dir == NULL || m->dir[0] != '.' ||
(m->dir[1] != '/' && m->dir[1] != '\0') ||
m->fstype == NULL || strcmp(m->fstype, "autofs") != 0 ||
m->dataptr == NULL || m->datalen != sizeof (struct autofs_args) ||
m->optptr == NULL)
return (B_TRUE);
if (strstr(m->dir, "/../") != NULL ||
(len = strlen(m->dir)) > sizeof ("/..") - 1 &&
m->dir[len] == '.' && m->dir[len - 1] == '.' &&
m->dir[len - 2] == '/')
return (B_TRUE);
argsp = (struct autofs_args *)m->dataptr;
if (argsp->addr.buf == NULL || argsp->path == NULL ||
argsp->opts == NULL || argsp->map == NULL || argsp->subdir == NULL)
return (B_TRUE);
if (dvp->v_flag & VROOT) {
if (m->dir[1] == '\0' && !(dfnp->fn_flags & MF_TRIGGER))
return (B_TRUE);
(void) snprintf(buff, sizeof (buff), "%s%s",
dfnip->fi_path, m->dir + 1);
} else {
(void) snprintf(buff, sizeof (buff), "%s/%s%s",
dfnip->fi_path, dfnp->fn_name, m->dir + 1);
}
if (strcmp(argsp->path, buff) != 0) {
auto_log(fngp->fng_verbose, fngp->fng_zoneid,
CE_WARN, "autofs: expected path of '%s', "
"got '%s' instead.", buff, argsp->path);
return (B_TRUE);
}
return (B_FALSE);
}
static boolean_t
auto_invalid_action(fninfo_t *dfnip, fnnode_t *dfnp, action_list *alistpp)
{
if (alistpp->action.action != AUTOFS_MOUNT_RQ)
return (B_TRUE);
return (auto_invalid_autofs(dfnip, dfnp, alistpp));
}
static int
auto_perform_actions(
fninfo_t *dfnip,
fnnode_t *dfnp,
action_list *alp,
cred_t *cred)
{
action_list *p;
struct mounta *m, margs;
struct autofs_args *argsp;
int error, success = 0;
vnode_t *mvp, *dvp, *newvp;
fnnode_t *newfnp, *mfnp;
int auto_mount = 0;
int save_triggers = 0;
int update_times = 0;
char *mntpnt;
char buff[AUTOFS_MAXPATHLEN];
timestruc_t now;
struct autofs_globals *fngp;
cred_t *zcred;
AUTOFS_DPRINT((4, "auto_perform_actions: alp=%p\n", (void *)alp));
fngp = dfnp->fn_globals;
dvp = fntovn(dfnp);
for (p = alp; p != NULL; p = p->next) {
if (auto_invalid_action(dfnip, dfnp, p)) {
cmn_err(CE_WARN, "autofs: invalid action list received "
"by automountd in zone %s.",
curproc->p_zone->zone_name);
xdr_free(xdr_action_list, (char *)alp);
return (EINVAL);
}
}
zcred = zone_get_kcred(getzoneid());
ASSERT(zcred != NULL);
if (vn_mountedvfs(dvp) != NULL) {
mutex_enter(&dfnp->fn_lock);
dfnp->fn_flags |= MF_MOUNTPOINT;
ASSERT(dfnp->fn_dirents == NULL);
mutex_exit(&dfnp->fn_lock);
success++;
} else {
mutex_enter(&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;
mutex_exit(&dfnp->fn_lock);
}
for (p = alp; p != NULL; p = p->next) {
vfs_t *vfsp;
vfs_t *mvfsp;
auto_mount = 0;
m = &p->action.action_list_entry_u.mounta;
argsp = (struct autofs_args *)m->dataptr;
ASSERT(strcmp(m->fstype, "autofs") == 0);
argsp->mount_to = dfnip->fi_mount_to;
ASSERT(m->dir[0] == '.');
if (m->dir[0] == '.' && m->dir[1] == '\0') {
mvp = dvp;
VN_HOLD(mvp);
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) sprintf(buff, "%s/%s", dfnip->fi_path, mntpnt);
} else {
AUTOFS_DPRINT((10, "\tINDIRECT\n"));
(void) sprintf(buff, "%s/%s/%s",
dfnip->fi_path, dfnp->fn_name, mntpnt);
}
if (vn_mountedvfs(dvp) == NULL) {
rw_enter(&dfnp->fn_rwlock, RW_WRITER);
error = auto_search(dfnp, mntpnt, &mfnp, cred);
if (error == 0) {
if (vn_mountedvfs(fntovn(mfnp)) != NULL) {
cmn_err(CE_PANIC,
"auto_perform_actions:"
" mfnp=%p covered", (void *)mfnp);
}
} else {
ASSERT((dfnp->fn_flags & MF_MOUNTPOINT) == 0);
error = auto_enter(dfnp, mntpnt, &mfnp, cred);
ASSERT(mfnp->fn_linkcnt == 1);
mfnp->fn_linkcnt++;
}
if (!error)
update_times = 1;
rw_exit(&dfnp->fn_rwlock);
ASSERT(error != EEXIST);
if (!error) {
mvp = fntovn(mfnp);
} else {
auto_log(fngp->fng_verbose, fngp->fng_zoneid,
CE_WARN, "autofs: mount of %s "
"failed - can't create"
" mountpoint.", buff);
continue;
}
} else {
if (error = auto_getmntpnt(dvp, mntpnt, &mvp, kcred)) {
auto_log(fngp->fng_verbose, fngp->fng_zoneid,
CE_WARN, "autofs: mount of %s "
"failed - mountpoint doesn't"
" exist.", buff);
continue;
}
if (mvp->v_type == VLNK) {
auto_log(fngp->fng_verbose, fngp->fng_zoneid,
CE_WARN, "autofs: %s symbolic "
"link: not a valid mountpoint "
"- mount failed", buff);
VN_RELE(mvp);
error = ENOENT;
continue;
}
}
mount:
m->flags |= MS_SYSSPACE | MS_OPTIONSTR;
bcopy(m, &margs, sizeof (*m));
margs.optptr = kmem_alloc(MAX_MNTOPT_STR, KM_SLEEP);
margs.optlen = MAX_MNTOPT_STR;
(void) strcpy(margs.optptr, m->optptr);
margs.dir = argsp->path;
error = domount(NULL, &margs, mvp, zcred, &vfsp);
kmem_free(margs.optptr, MAX_MNTOPT_STR);
if (error != 0) {
auto_log(fngp->fng_verbose, fngp->fng_zoneid,
CE_WARN, "autofs: domount of %s failed "
"error=%d", buff, error);
VN_RELE(mvp);
continue;
}
VFS_RELE(vfsp);
if (vfs_matchops(dvp->v_vfsp, vfs_getops(mvp->v_vfsp))) {
mfnp = vntofn(mvp);
mutex_enter(&mfnp->fn_lock);
mfnp->fn_flags |= MF_IK_MOUNT;
mutex_exit(&mfnp->fn_lock);
}
(void) vn_vfswlock_wait(mvp);
mvfsp = vn_mountedvfs(mvp);
if (mvfsp != NULL) {
vfs_lock_wait(mvfsp);
vn_vfsunlock(mvp);
error = VFS_ROOT(mvfsp, &newvp);
vfs_unlock(mvfsp);
if (error) {
(void) vn_vfswlock_wait(mvp);
mvfsp = vn_mountedvfs(mvp);
if (mvfsp != NULL) {
error = dounmount(mvfsp, 0, CRED());
if (error) {
cmn_err(CE_WARN,
"autofs: could not unmount"
" vfs=%p", (void *)mvfsp);
}
} else
vn_vfsunlock(mvp);
VN_RELE(mvp);
continue;
}
} else {
vn_vfsunlock(mvp);
VN_RELE(mvp);
continue;
}
auto_mount = vfs_matchops(dvp->v_vfsp,
vfs_getops(newvp->v_vfsp));
newfnp = vntofn(newvp);
newfnp->fn_parent = dfnp;
if (auto_mount && (error == 0) && (mvp != dvp)) {
save_triggers++;
newfnp->fn_flags |= MF_TRIGGER;
rw_enter(&newfnp->fn_rwlock, RW_WRITER);
newfnp->fn_next = dfnp->fn_trigger;
rw_exit(&newfnp->fn_rwlock);
rw_enter(&dfnp->fn_rwlock, RW_WRITER);
dfnp->fn_trigger = newfnp;
rw_exit(&dfnp->fn_rwlock);
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"));
} else
VN_RELE(newvp);
if (!error)
success++;
if (update_times) {
gethrestime(&now);
dfnp->fn_atime = dfnp->fn_mtime = now;
}
VN_RELE(mvp);
}
if (save_triggers) {
VN_HOLD(dvp);
}
crfree(zcred);
error = success ? 0 : ENOENT;
if (alp != NULL) {
if ((error == 0) && save_triggers) {
mutex_enter(&dfnp->fn_lock);
ASSERT(dfnp->fn_alp == NULL);
dfnp->fn_alp = alp;
mutex_exit(&dfnp->fn_lock);
} else {
xdr_free(xdr_action_list, (char *)alp);
}
}
AUTOFS_DPRINT((5, "auto_perform_actions: error=%d\n", error));
return (error);
}
fnnode_t *
auto_makefnnode(
vtype_t type,
vfs_t *vfsp,
char *name,
cred_t *cred,
struct autofs_globals *fngp)
{
fnnode_t *fnp;
vnode_t *vp;
char *tmpname;
timestruc_t now;
static ino_t nodeid = 3;
fnp = kmem_zalloc(sizeof (*fnp), KM_SLEEP);
fnp->fn_vnode = vn_alloc(KM_SLEEP);
vp = fntovn(fnp);
tmpname = kmem_alloc(strlen(name) + 1, KM_SLEEP);
(void) strcpy(tmpname, name);
fnp->fn_name = &tmpname[0];
fnp->fn_namelen = (int)strlen(tmpname) + 1;
fnp->fn_uid = crgetuid(cred);
fnp->fn_gid = crgetgid(cred);
fnp->fn_mode = AUTOFS_MODE;
gethrestime(&now);
fnp->fn_atime = fnp->fn_mtime = fnp->fn_ctime = now;
fnp->fn_ref_time = now.tv_sec;
mutex_enter(&autofs_nodeid_lock);
fnp->fn_nodeid = nodeid;
nodeid += 2;
fnp->fn_globals = fngp;
fngp->fng_fnnode_count++;
mutex_exit(&autofs_nodeid_lock);
vn_setops(vp, auto_vnodeops);
vp->v_type = type;
vp->v_data = (void *)fnp;
vp->v_vfsp = vfsp;
mutex_init(&fnp->fn_lock, NULL, MUTEX_DEFAULT, NULL);
rw_init(&fnp->fn_rwlock, NULL, RW_DEFAULT, NULL);
cv_init(&fnp->fn_cv_mount, NULL, CV_DEFAULT, NULL);
vn_exists(vp);
return (fnp);
}
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(vp->v_count == 0);
ASSERT(fnp->fn_dirents == NULL);
ASSERT(fnp->fn_parent == NULL);
vn_invalid(vp);
kmem_free(fnp->fn_name, fnp->fn_namelen);
if (fnp->fn_symlink) {
ASSERT(fnp->fn_flags & MF_THISUID_MATCH_RQD);
kmem_free(fnp->fn_symlink, fnp->fn_symlinklen);
}
if (fnp->fn_cred)
crfree(fnp->fn_cred);
mutex_destroy(&fnp->fn_lock);
rw_destroy(&fnp->fn_rwlock);
cv_destroy(&fnp->fn_cv_mount);
vn_free(vp);
mutex_enter(&autofs_nodeid_lock);
fnp->fn_globals->fng_fnnode_count--;
mutex_exit(&autofs_nodeid_lock);
kmem_free(fnp, sizeof (*fnp));
}
void
auto_disconnect(
fnnode_t *dfnp,
fnnode_t *fnp)
{
fnnode_t *tmp, **fnpp;
vnode_t *vp = fntovn(fnp);
timestruc_t now;
AUTOFS_DPRINT((4,
"auto_disconnect: dfnp=%p fnp=%p linkcnt=%d\n v_count=%d",
(void *)dfnp, (void *)fnp, fnp->fn_linkcnt, vp->v_count));
ASSERT(RW_WRITE_HELD(&dfnp->fn_rwlock));
ASSERT(fnp->fn_linkcnt == 1);
if (vn_mountedvfs(vp) != NULL) {
cmn_err(CE_PANIC, "auto_disconnect: vp %p mounted on",
(void *)vp);
}
fnp->fn_linkcnt--;
fnp->fn_size--;
fnp->fn_parent = NULL;
fnpp = &dfnp->fn_dirents;
for (;;) {
tmp = *fnpp;
if (tmp == NULL) {
cmn_err(CE_PANIC,
"auto_disconnect: %p not in %p dirent list",
(void *)fnp, (void *)dfnp);
}
if (tmp == fnp) {
*fnpp = tmp->fn_next;
ASSERT(vp->v_count == 0);
dfnp->fn_linkcnt--;
dfnp->fn_size--;
break;
}
fnpp = &tmp->fn_next;
}
mutex_enter(&fnp->fn_lock);
gethrestime(&now);
fnp->fn_atime = fnp->fn_mtime = now;
mutex_exit(&fnp->fn_lock);
AUTOFS_DPRINT((5, "auto_disconnect: done\n"));
}
int
auto_enter(fnnode_t *dfnp, char *name, fnnode_t **fnpp, cred_t *cred)
{
struct fnnode *cfnp, **spp;
vnode_t *dvp = fntovn(dfnp);
ushort_t offset = 0;
ushort_t diff;
AUTOFS_DPRINT((4, "auto_enter: dfnp=%p, name=%s ", (void *)dfnp, name));
ASSERT(RW_WRITE_HELD(&dfnp->fn_rwlock));
cfnp = dfnp->fn_dirents;
if (cfnp == NULL) {
spp = &dfnp->fn_dirents;
offset = 2;
}
for (; cfnp; cfnp = cfnp->fn_next) {
if (strcmp(cfnp->fn_name, name) == 0) {
mutex_enter(&cfnp->fn_lock);
if (cfnp->fn_flags & MF_THISUID_MATCH_RQD) {
mutex_exit(&cfnp->fn_lock);
if (crcmp(cfnp->fn_cred, cred) == 0)
return (EEXIST);
} else {
mutex_exit(&cfnp->fn_lock);
return (EEXIST);
}
}
if (cfnp->fn_next != NULL) {
diff = (ushort_t)
(cfnp->fn_next->fn_offset - cfnp->fn_offset);
ASSERT(diff != 0);
if (diff > 1 && offset == 0) {
offset = (ushort_t)cfnp->fn_offset + 1;
spp = &cfnp->fn_next;
}
} else if (offset == 0) {
offset = (ushort_t)cfnp->fn_offset + 1;
spp = &cfnp->fn_next;
}
}
*fnpp = auto_makefnnode(VDIR, dvp->v_vfsp, name, cred,
dfnp->fn_globals);
if (*fnpp == NULL)
return (ENOMEM);
(*fnpp)->fn_offset = offset;
(*fnpp)->fn_next = *spp;
*spp = *fnpp;
(*fnpp)->fn_parent = dfnp;
(*fnpp)->fn_linkcnt++;
(*fnpp)->fn_size++;
dfnp->fn_linkcnt++;
dfnp->fn_size++;
dfnp->fn_ref_time = gethrestime_sec();
AUTOFS_DPRINT((5, "*fnpp=%p\n", (void *)*fnpp));
return (0);
}
int
auto_search(fnnode_t *dfnp, char *name, fnnode_t **fnpp, cred_t *cred)
{
vnode_t *dvp;
fnnode_t *p;
int error = ENOENT, match = 0;
AUTOFS_DPRINT((4, "auto_search: dfnp=%p, name=%s...\n",
(void *)dfnp, name));
dvp = fntovn(dfnp);
if (dvp->v_type != VDIR) {
cmn_err(CE_PANIC, "auto_search: dvp=%p not a directory",
(void *)dvp);
}
ASSERT(RW_LOCK_HELD(&dfnp->fn_rwlock));
for (p = dfnp->fn_dirents; p != NULL; p = p->fn_next) {
if (strcmp(p->fn_name, name) == 0) {
mutex_enter(&p->fn_lock);
if (p->fn_flags & MF_THISUID_MATCH_RQD) {
mutex_exit(&p->fn_lock);
match = crcmp(p->fn_cred, cred) == 0;
} else {
mutex_exit(&p->fn_lock);
match = 1;
}
}
if (match) {
error = 0;
if (fnpp) {
*fnpp = p;
VN_HOLD(fntovn(*fnpp));
}
break;
}
}
AUTOFS_DPRINT((5, "auto_search: error=%d\n", error));
return (error);
}
static int
auto_getmntpnt(
vnode_t *dvp,
char *path,
vnode_t **mvpp,
cred_t *cred)
{
int error = 0;
vnode_t *newvp;
char namebuf[TYPICALMAXPATHLEN];
struct pathname lookpn;
vfs_t *vfsp;
AUTOFS_DPRINT((4, "auto_getmntpnt: path=%s\n", path));
if (error = vn_vfsrlock_wait(dvp))
return (error);
vfsp = vn_mountedvfs(dvp);
if (vfsp == NULL) {
vn_vfsunlock(dvp);
error = EBUSY;
goto done;
}
error = VFS_ROOT(vfsp, &newvp);
vn_vfsunlock(dvp);
if (error)
goto done;
VN_HOLD(newvp);
error = pn_get_buf(path, UIO_SYSSPACE, &lookpn,
namebuf, sizeof (namebuf));
if (error == 0) {
error = lookuppnvp(&lookpn, NULL, NO_FOLLOW, NULLVPP,
mvpp, rootdir, newvp, cred);
} else
VN_RELE(newvp);
if (error == ENAMETOOLONG) {
if ((error = pn_get(path, UIO_SYSSPACE, &lookpn)) == 0) {
error = lookuppnvp(&lookpn, NULL, NO_FOLLOW, NULLVPP,
mvpp, rootdir, newvp, cred);
pn_free(&lookpn);
} else
VN_RELE(newvp);
} else {
VN_RELE(newvp);
}
done:
AUTOFS_DPRINT((5, "auto_getmntpnt: path=%s *mvpp=%p error=%d\n",
path, (void *)*mvpp, error));
return (error);
}
#define DEEPER(x) (((x)->fn_dirents != NULL) || \
(vn_mountedvfs(fntovn((x)))) != NULL)
static int
auto_inkernel_unmount(vfs_t *vfsp)
{
vnode_t *cvp = vfsp->vfs_vnodecovered;
int error;
AUTOFS_DPRINT((4,
"auto_inkernel_unmount: devid=%lx mntpnt(%p) count %u\n",
vfsp->vfs_dev, (void *)cvp, cvp->v_count));
ASSERT(vn_vfswlock_held(cvp));
error = dounmount(vfsp, 0, kcred);
AUTOFS_DPRINT((5, "auto_inkernel_unmount: exit count %u\n",
cvp->v_count));
return (error);
}
static void
unmount_triggers(fnnode_t *fnp, action_list **alp)
{
fnnode_t *tp, *next;
int error = 0;
vfs_t *vfsp;
vnode_t *tvp;
AUTOFS_DPRINT((4, "unmount_triggers: fnp=%p\n", (void *)fnp));
ASSERT(RW_WRITE_HELD(&fnp->fn_rwlock));
*alp = fnp->fn_alp;
next = fnp->fn_trigger;
while ((tp = next) != NULL) {
tvp = fntovn(tp);
ASSERT(tvp->v_count >= 2);
next = tp->fn_next;
rw_exit(&fnp->fn_rwlock);
vfsp = tvp->v_vfsp;
VN_RELE(tvp);
if (error = auto_inkernel_unmount(vfsp)) {
cmn_err(CE_PANIC, "unmount_triggers: "
"unmount of vp=%p failed error=%d",
(void *)tvp, error);
}
rw_enter(&fnp->fn_rwlock, RW_WRITER);
}
VN_RELE(fntovn(fnp));
fnp->fn_trigger = NULL;
fnp->fn_alp = NULL;
AUTOFS_DPRINT((5, "unmount_triggers: finished\n"));
}
static boolean_t
triggers_busy(fnnode_t *fnp)
{
int done;
int lck_error = 0;
fnnode_t *tp, *t1p;
vfs_t *vfsp;
ASSERT(RW_WRITE_HELD(&fnp->fn_rwlock));
for (tp = fnp->fn_trigger; tp != NULL; tp = tp->fn_next) {
AUTOFS_DPRINT((10, "\ttrigger: %s\n", tp->fn_name));
ASSERT((tp->fn_flags & MF_LOOKUP) == 0);
vfsp = fntovn(tp)->v_vfsp;
lck_error = vn_vfswlock(vfsp->vfs_vnodecovered);
if (lck_error != 0 || (tp->fn_flags & MF_INPROG) ||
DEEPER(tp) || ((fntovn(tp))->v_count) > 2) {
AUTOFS_DPRINT((10, "\ttrigger busy\n"));
for (done = 0, t1p = fnp->fn_trigger; !done;
t1p = t1p->fn_next) {
if (t1p != tp || !lck_error) {
vfsp = fntovn(t1p)->v_vfsp;
vn_vfsunlock(vfsp->vfs_vnodecovered);
}
done = (t1p == tp);
}
return (B_TRUE);
}
}
return (B_FALSE);
}
static int
unmount_node(vnode_t *cvp, int force)
{
int error = 0;
fnnode_t *cfnp;
vfs_t *vfsp;
umntrequest ul;
fninfo_t *fnip;
AUTOFS_DPRINT((4, "\tunmount_node cvp=%p\n", (void *)cvp));
ASSERT(vn_vfswlock_held(cvp));
cfnp = vntofn(cvp);
vfsp = vn_mountedvfs(cvp);
if (force || cfnp->fn_flags & MF_IK_MOUNT) {
error = auto_inkernel_unmount(vfsp);
} else {
zone_t *zone = NULL;
refstr_t *mntpt, *resource;
size_t mntoptslen;
bzero(&ul, sizeof (ul));
mntfs_getmntopts(vfsp, &ul.mntopts, &mntoptslen);
if (ul.mntopts == NULL) {
auto_log(cfnp->fn_globals->fng_verbose,
cfnp->fn_globals->fng_zoneid, CE_WARN,
"unmount_node: no memory");
vn_vfsunlock(cvp);
error = ENOMEM;
goto done;
}
if (mntoptslen > AUTOFS_MAXOPTSLEN)
ul.mntopts[AUTOFS_MAXOPTSLEN - 1] = '\0';
mntpt = vfs_getmntpoint(vfsp);
ul.mntpnt = (char *)refstr_value(mntpt);
resource = vfs_getresource(vfsp);
ul.mntresource = (char *)refstr_value(resource);
fnip = vfstofni(cvp->v_vfsp);
ul.isdirect = fnip->fi_flags & MF_DIRECT ? TRUE : FALSE;
ASSERT(fnip->fi_zoneid == getzoneid());
zone = curproc->p_zone;
if (fnip->fi_zoneid != GLOBAL_ZONEID) {
if (ZONE_PATH_VISIBLE(ul.mntpnt, zone)) {
ul.mntpnt =
ZONE_PATH_TRANSLATE(ul.mntpnt, zone);
}
if (ZONE_PATH_VISIBLE(ul.mntresource, zone)) {
ul.mntresource =
ZONE_PATH_TRANSLATE(ul.mntresource, zone);
}
}
ul.fstype = vfssw[vfsp->vfs_fstype].vsw_name;
vn_vfsunlock(cvp);
error = auto_send_unmount_request(fnip, &ul, FALSE);
kmem_free(ul.mntopts, mntoptslen);
refstr_rele(mntpt);
refstr_rele(resource);
}
done:
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;
int error = 0;
uint_t count;
AUTOFS_DPRINT((4, "\tcheck_auto_node vp=%p ", (void *)vp));
fnp = vntofn(vp);
count = 1;
if (fnp->fn_flags & MF_TRIGGER) {
count++;
}
if (fnp->fn_trigger != NULL) {
count++;
}
if (vn_ismntpt(vp)) {
count++;
}
mutex_enter(&vp->v_lock);
ASSERT(vp->v_count > 0);
if (vp->v_flag & VROOT)
count++;
AUTOFS_DPRINT((10, "\tcount=%u ", vp->v_count));
if (vp->v_count > count)
error = EBUSY;
mutex_exit(&vp->v_lock);
AUTOFS_DPRINT((5, "\tcheck_auto_node error=%d ", error));
return (error);
}
static void
unmount_autofs(vnode_t *rootvp)
{
fnnode_t *fnp, *rootfnp, *nfnp;
AUTOFS_DPRINT((4, "\tunmount_autofs rootvp=%p ", (void *)rootvp));
rootfnp = vntofn(rootvp);
rw_enter(&rootfnp->fn_rwlock, RW_WRITER);
for (fnp = rootfnp->fn_dirents; fnp != NULL; fnp = nfnp) {
ASSERT(fntovn(fnp)->v_count == 0);
ASSERT(fnp->fn_dirents == NULL);
ASSERT(fnp->fn_linkcnt == 2);
fnp->fn_linkcnt--;
auto_disconnect(rootfnp, fnp);
nfnp = fnp->fn_next;
auto_freefnnode(fnp);
}
rw_exit(&rootfnp->fn_rwlock);
}
static int
try_unmount_node(fnnode_t *fnp, boolean_t force)
{
boolean_t trigger_unmount = B_FALSE;
action_list *alp = NULL;
vnode_t *vp;
int error = 0;
fninfo_t *fnip;
vfs_t *vfsp;
struct autofs_globals *fngp;
AUTOFS_DPRINT((10, "\ttry_unmount_node: processing node %p\n",
(void *)fnp));
ASSERT(MUTEX_HELD(&fnp->fn_lock));
fngp = fnp->fn_globals;
vp = fntovn(fnp);
fnip = vfstofni(vp->v_vfsp);
if (fnp->fn_flags & (MF_INPROG | MF_LOOKUP)) {
mutex_exit(&fnp->fn_lock);
return (EBUSY);
}
if (check_auto_node(vp) == EBUSY) {
mutex_exit(&fnp->fn_lock);
return (EBUSY);
}
if (!force &&
fnp->fn_ref_time + fnip->fi_mount_to > gethrestime_sec()) {
mutex_exit(&fnp->fn_lock);
return (EBUSY);
}
AUTOFS_BLOCK_OTHERS(fnp, MF_INPROG);
fnp->fn_error = 0;
mutex_exit(&fnp->fn_lock);
rw_enter(&fnp->fn_rwlock, RW_WRITER);
if (fnp->fn_trigger != NULL) {
trigger_unmount = B_TRUE;
if (triggers_busy(fnp)) {
rw_exit(&fnp->fn_rwlock);
mutex_enter(&fnp->fn_lock);
AUTOFS_UNBLOCK_OTHERS(fnp, MF_INPROG);
mutex_exit(&fnp->fn_lock);
return (EBUSY);
}
unmount_triggers(fnp, &alp);
}
rw_exit(&fnp->fn_rwlock);
(void) vn_vfswlock_wait(vp);
vfsp = vn_mountedvfs(vp);
if (vfsp != NULL) {
error = unmount_node(vp, force);
if (error == ECONNRESET) {
if (vn_mountedvfs(vp) == NULL) {
error = 0;
auto_log(fngp->fng_verbose, fngp->fng_zoneid,
CE_WARN, "autofs: automountd "
"connection dropped when unmounting %s/%s",
fnip->fi_path, (fnip->fi_flags & MF_DIRECT)
? "" : fnp->fn_name);
}
}
} else {
vn_vfsunlock(vp);
if (trigger_unmount)
unmount_autofs(vp);
}
if (error != 0) {
if (trigger_unmount) {
int ret;
ASSERT((fnp->fn_flags & MF_THISUID_MATCH_RQD) == 0);
ret = auto_perform_actions(fnip, fnp, alp, CRED());
if (ret != 0) {
auto_log(fngp->fng_verbose, fngp->fng_zoneid,
CE_WARN, "autofs: can't remount triggers "
"fnp=%p error=%d", (void *)fnp, ret);
}
}
mutex_enter(&fnp->fn_lock);
AUTOFS_UNBLOCK_OTHERS(fnp, MF_INPROG);
mutex_exit(&fnp->fn_lock);
} else {
if (trigger_unmount)
xdr_free(xdr_action_list, (char *)alp);
mutex_enter(&fnp->fn_lock);
fnp->fn_flags &= ~MF_IK_MOUNT;
if (fnp->fn_flags & MF_WAITING)
fnp->fn_error = EAGAIN;
AUTOFS_UNBLOCK_OTHERS(fnp, MF_INPROG);
mutex_exit(&fnp->fn_lock);
}
return (error);
}
int
unmount_subtree(fnnode_t *rootfnp, boolean_t force)
{
fnnode_t *currfnp;
fnnode_t *lastfnp;
fnnode_t *nextfnp;
vnode_t *curvp;
vnode_t *newvp;
vfs_t *vfsp;
time_t timestamp;
ASSERT(fntovn(rootfnp)->v_type != VLNK);
AUTOFS_DPRINT((10, "unmount_subtree: root=%p (%s)\n", (void *)rootfnp,
rootfnp->fn_name));
timestamp = gethrestime_sec();
currfnp = lastfnp = rootfnp;
mutex_enter(&currfnp->fn_lock);
while (currfnp != rootfnp || rootfnp->fn_unmount_ref_time < timestamp) {
curvp = fntovn(currfnp);
AUTOFS_DPRINT((10, "\tunmount_subtree: entering node %p (%s)\n",
(void *)currfnp, currfnp->fn_name));
if (currfnp->fn_unmount_ref_time == timestamp &&
currfnp != lastfnp) {
(void) try_unmount_node(currfnp, force);
lastfnp = currfnp;
mutex_enter(&currfnp->fn_lock);
}
if (currfnp->fn_unmount_ref_time >= timestamp ||
curvp->v_type == VLNK) {
mutex_exit(&currfnp->fn_lock);
rw_enter(&currfnp->fn_parent->fn_rwlock, RW_READER);
if ((nextfnp = currfnp->fn_next) != NULL) {
VN_HOLD(fntovn(nextfnp));
rw_exit(&currfnp->fn_parent->fn_rwlock);
VN_RELE(curvp);
currfnp = nextfnp;
mutex_enter(&currfnp->fn_lock);
continue;
}
rw_exit(&currfnp->fn_parent->fn_rwlock);
nextfnp = currfnp->fn_parent;
VN_HOLD(fntovn(nextfnp));
VN_RELE(curvp);
currfnp = nextfnp;
mutex_enter(&currfnp->fn_lock);
continue;
}
if (currfnp->fn_unmount_ref_time < timestamp)
currfnp->fn_unmount_ref_time = timestamp;
if (!rw_tryenter(&currfnp->fn_rwlock, RW_READER)) {
mutex_exit(&currfnp->fn_lock);
rw_enter(&currfnp->fn_rwlock, RW_READER);
mutex_enter(&currfnp->fn_lock);
}
if (currfnp->fn_flags & MF_INPROG) {
rw_exit(&currfnp->fn_rwlock);
continue;
}
mutex_exit(&currfnp->fn_lock);
if ((nextfnp = currfnp->fn_trigger) != NULL) {
VN_HOLD(fntovn(nextfnp));
rw_exit(&currfnp->fn_rwlock);
VN_RELE(curvp);
currfnp = nextfnp;
mutex_enter(&currfnp->fn_lock);
continue;
}
if ((nextfnp = currfnp->fn_dirents) != NULL) {
VN_HOLD(fntovn(nextfnp));
rw_exit(&currfnp->fn_rwlock);
VN_RELE(curvp);
currfnp = nextfnp;
mutex_enter(&currfnp->fn_lock);
continue;
}
rw_exit(&currfnp->fn_rwlock);
(void) vn_vfswlock_wait(curvp);
vfsp = vn_mountedvfs(curvp);
if (vfsp != NULL &&
vfs_matchops(vfsp, vfs_getops(curvp->v_vfsp))) {
if (VFS_ROOT(vfsp, &newvp)) {
cmn_err(CE_PANIC,
"autofs: VFS_ROOT(vfs=%p) failed",
(void *)vfsp);
}
vn_vfsunlock(curvp);
VN_RELE(curvp);
currfnp = vntofn(newvp);
mutex_enter(&currfnp->fn_lock);
continue;
}
vn_vfsunlock(curvp);
mutex_enter(&currfnp->fn_lock);
}
return (try_unmount_node(currfnp, force));
}
void
unmount_tree(struct autofs_globals *fngp, boolean_t force)
{
callb_cpr_t cprinfo;
kmutex_t unmount_tree_cpr_lock;
fnnode_t *root, *fnp, *next;
mutex_init(&unmount_tree_cpr_lock, NULL, MUTEX_DEFAULT, NULL);
CALLB_CPR_INIT(&cprinfo, &unmount_tree_cpr_lock, callb_generic_cpr,
"unmount_tree");
ASSERT(force || fngp->fng_zoneid == getzoneid());
if (force || auto_null_request(fngp->fng_zoneid, FALSE) == 0) {
root = fngp->fng_rootfnnodep;
rw_enter(&root->fn_rwlock, RW_READER);
for (fnp = root->fn_dirents; fnp != NULL; fnp = next) {
VN_HOLD(fntovn(fnp));
rw_exit(&root->fn_rwlock);
(void) unmount_subtree(fnp, force);
rw_enter(&root->fn_rwlock, RW_READER);
next = fnp->fn_next;
VN_RELE(fntovn(fnp));
}
rw_exit(&root->fn_rwlock);
}
mutex_enter(&unmount_tree_cpr_lock);
CALLB_CPR_EXIT(&cprinfo);
mutex_destroy(&unmount_tree_cpr_lock);
}
static void
unmount_zone_tree(struct autofs_globals *fngp)
{
AUTOFS_DPRINT((5, "unmount_zone_tree started. Thread created.\n"));
unmount_tree(fngp, B_FALSE);
mutex_enter(&fngp->fng_unmount_threads_lock);
fngp->fng_unmount_threads--;
mutex_exit(&fngp->fng_unmount_threads_lock);
AUTOFS_DPRINT((5, "unmount_zone_tree done. Thread exiting.\n"));
zthread_exit();
}
void
auto_do_unmount(struct autofs_globals *fngp)
{
callb_cpr_t cprinfo;
clock_t timeleft;
zone_t *zone = curproc->p_zone;
CALLB_CPR_INIT(&cprinfo, &fngp->fng_unmount_threads_lock,
callb_generic_cpr, "auto_do_unmount");
for (;;) {
mutex_enter(&fngp->fng_unmount_threads_lock);
CALLB_CPR_SAFE_BEGIN(&cprinfo);
newthread:
mutex_exit(&fngp->fng_unmount_threads_lock);
timeleft = zone_status_timedwait(zone, ddi_get_lbolt() +
autofs_unmount_thread_timer * hz, ZONE_IS_SHUTTING_DOWN);
mutex_enter(&fngp->fng_unmount_threads_lock);
if (timeleft != -1) {
ASSERT(zone_status_get(zone) >= ZONE_IS_SHUTTING_DOWN);
CALLB_CPR_SAFE_END(&cprinfo,
&fngp->fng_unmount_threads_lock);
CALLB_CPR_EXIT(&cprinfo);
zthread_exit();
}
if (fngp->fng_unmount_threads < autofs_unmount_threads) {
fngp->fng_unmount_threads++;
CALLB_CPR_SAFE_END(&cprinfo,
&fngp->fng_unmount_threads_lock);
mutex_exit(&fngp->fng_unmount_threads_lock);
(void) zthread_create(NULL, 0, unmount_zone_tree, fngp,
0, minclsyspri);
} else
goto newthread;
}
}
int
auto_nobrowse_option(char *opts)
{
char *buf;
char *p;
char *t;
int nobrowse = 0;
int last_opt = 0;
size_t len;
len = strlen(opts) + 1;
p = buf = kmem_alloc(len, KM_SLEEP);
(void) strcpy(buf, opts);
do {
if (t = strchr(p, ','))
*t++ = '\0';
else
last_opt++;
if (strcmp(p, MNTOPT_NOBROWSE) == 0)
nobrowse = 1;
else if (strcmp(p, MNTOPT_BROWSE) == 0)
nobrowse = 0;
p = t;
} while (!last_opt);
kmem_free(buf, len);
return (nobrowse);
}
void
auto_log(int verbose, zoneid_t zoneid, int level, const char *fmt, ...)
{
va_list args;
if (verbose) {
va_start(args, fmt);
vzcmn_err(zoneid, level, fmt, args);
va_end(args);
}
}
#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);
(void) vprintf(fmt, args);
va_end(args);
}
}
#endif