/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
*/
#include <sys/pathname.h>
#include <sys/sysmacros.h>
#include <rpcsvc/autofs_prot.h>
#include <sys/schedctl.h>
/*
* Autofs and Zones:
*
* Zones are delegated the responsibility of managing their own autofs mounts
* and maps. Each zone runs its own copy of automountd, with its own timeouts,
* and other logically "global" parameters. kRPC and virtualization in the
* loopback transport (tl) will prevent a zone from communicating with another
* zone's automountd.
*
* Each zone has its own "rootfnnode" and associated tree of auto nodes.
*
* Each zone also has its own set of "unmounter" kernel threads; these are
* created and run within the zone's context (ie, they are created via
* zthread_create()).
*
* Cross-zone mount triggers are disallowed. There is a check in
* auto_trigger_mount() to this effect; EPERM is returned to indicate that the
* mount is not owned by the caller.
*
* autofssys() enables a caller in the global zone to clean up in-kernel (as
* well as regular) autofs mounts via the unmount_tree() mechanism. This is
* routinely done when all mounts are removed as part of zone shutdown.
*/
/* max number of unmount threads running */
action_list *, cred_t *);
bool_t);
/*
* Clears the MF_INPROG flag, and wakes up those threads sleeping on
* fn_cv_mount if MF_WAITING is set.
*/
void
{
}
}
int
{
int error;
/*
* There is a mount or a lookup in progress.
*/
/*
* Decided not to wait for operation to
* finish after all.
*/
return (EINTR);
}
}
/*
* The thread doing the mount got interrupted, we need to
* try again, by returning EAGAIN.
*/
}
error));
return (error);
}
int
{
int error = 0;
if (!error) {
/*
* This node should be a symlink
*/
} else if (mountreq) {
/*
* The automount daemon is requesting a mount,
* implying this entry must be a wildcard match and
* therefore in need of verification that the entry
* exists on the server.
*/
/*
* Unblock other lookup requests on this node,
* this is needed to let the lookup generated by
* the mount call to complete. The caveat is
* other lookups on this node can also get by,
* i.e., another lookup on this node that occurs
* while this lookup is attempting the mount
* would return a positive result no matter what.
* Therefore two lookups on the this node could
* potentially get disparate results.
*/
/*
* auto_new_mount_thread fires up a new thread which
* calls automountd finishing up the work
*/
/*
* At this point, we are simply another thread
* waiting for the mount to complete
*/
if (error == AUTOFS_SHUTDOWN)
}
}
/*
* it's done.
*/
if (mountreq) {
} else {
}
return (error);
}
/*
* Starting point for thread to handle mount requests with automountd.
* XXX auto_mount_thread() is not suspend-safe within the scope of
* the present model defined for cpr to suspend the system. Calls
* made by the auto_mount_thread() that have been identified to be unsafe
* are (1) RPC client handle setup and client calls to automountd which
* can block deep down in the RPC library, (2) kmem_alloc() calls with the
* KM_SLEEP flag which can block if memory is low, and (3) VFS_*(), and
* lookuppnvp() calls which can result in over the wire calls to servers.
* The thread should be completely reevaluated to make it suspend-safe in
* case of future updates to the cpr model.
*/
static void
{
char *name;
int error;
callb_generic_cpr, "auto_mount_thread");
if (!error)
/*
* Notify threads waiting for mount that
* it's done.
*/
zthread_exit();
/* NOTREACHED */
}
static int autofs_thr_success = 0;
/*
* Creates new thread which calls auto_mount_thread which does
* the bulk of the work calling automountd, via 'auto_perform_actions'.
*/
void
{
}
/*ARGSUSED*/
int
int which,
void *argsp,
void *resp,
int reslen,
{
int retry;
int error = 0;
int orl;
int xdr_len = 0;
int printed_not_running_msg = 0;
/*
* We know that the current thread is doing work on
* behalf of its own zone, so it's ok to use
* curproc->p_zone.
*/
/*
* There's no point in trying to talk to
* automountd. Plus, zone_shutdown() is
* waiting for us.
*/
return (ECONNREFUSED);
}
do {
retry = 0;
if (hard) {
AUTOFS_DPRINT((5,
"auto_calldaemon: "\
"failed to get door handle\n"));
if (!printed_not_running_msg) {
"running, retrying\n");
}
retry = 1;
} else {
/*
* There is no global data so no door.
* There's no point in attempting to talk
* to automountd if we can't get the door
* handle.
*/
return (ECONNREFUSED);
}
}
} while (retry);
if (printed_not_running_msg) {
}
return (EINVAL);
if (argsp) {
return (EINVAL);
}
}
/*
* We're saving off the original pointer and length due to the
* possibility that the results buffer returned by the door
* upcall can be different then what we passed in. This is because
* the door will allocate new memory if the results buffer passed
* in isn't large enough to hold what we need to send back.
* In this case we need to free the memory originally allocated
* for that buffer.
*/
if (resp)
do {
retry = 0;
if (dh)
if (orp)
return (ENOENT);
}
error =
/*
* Handle daemon errors
*/
if (!error) {
/*
* Upcall successful. Let's check for soft errors
* from the daemon. We only recover from overflow
* type scenarios. Any other errors, we return to
* the caller.
*/
int nl;
case 0: /* no error; continue */
break;
case EOVERFLOW:
/*
* orig landing buf not big enough.
* xdr_len in XDR_BYTES_PER_UNIT
*/
if (orp)
retry = 1;
break;
}
/*FALLTHROUGH*/
default:
if (orp)
return (error);
}
}
continue;
}
/*
*/
switch (error) {
case EINTR:
/*
* interrupts should be handled properly by the
* door upcall. If the door doesn't handle the
* interupt completely then we need to bail out.
*/
lwp->lwp_sysabort ||
lwp->lwp_sysabort = 0;
return (EINTR);
}
}
/*
* We may have gotten EINTR for other reasons
* like the door being revoked on us. Instead
* of trying to extract this out of the door
* handle, sleep and try again, if still
* revoked we will get EBADF next time
* through.
*
* If we have a pending cancellation and we don't
* have cancellation disabled, we will get EINTR
* forever, no matter how many times we retry,
* so just get out now if this is the case.
*/
if (schedctl_cancel_pending())
break;
/* FALLTHROUGH */
case EAGAIN: /* process may be forking */
/*
* Back off for a bit
*/
retry = 1;
break;
case EBADF: /* Invalid door */
case EINVAL: /* Not a door, wrong target */
/*
* A fatal door error, if our failing door
* handle is the current door handle, clean
* up our state.
*/
}
if (hard) {
if (!fngp->fng_printed_not_running_msg) {
"running, retrying\n");
}
retry = 1;
break;
} else {
if (orp)
return (error);
}
default: /* Unknown must be fatal */
if (orp)
return (error);
}
} while (retry);
}
}
return (error);
}
static int
{
int error;
return (error);
}
static int
char *key,
{
int error;
struct linka *p;
else
if (error) {
return (error);
}
if (!error) {
case AUTOFS_OK:
case AUTOFS_MOUNT_RQ:
break;
case AUTOFS_LINK_RQ:
KM_SLEEP);
KM_SLEEP);
break;
case AUTOFS_NONE:
break;
default:
CE_WARN, "auto_lookup_request: bad action "
}
break;
case AUTOFS_NOENT:
break;
default:
"auto_lookup_request: unknown result: %d",
break;
}
}
done:
return (error);
}
static int
char *key,
action_list **alpp,
{
int error;
else
if (!error) {
case AUTOFS_ACTION:
error = 0;
/*
* Save the action list since it is used by
* the caller. We NULL the action list pointer
* in 'result' so that xdr_free() will not free
* the list.
*/
break;
case AUTOFS_DONE:
break;
default:
"auto_mount_request: unknown status %d",
break;
}
}
return (error);
}
static int
{
int error;
if (!error)
return (error);
}
static int
{
char *tmp;
return (0);
}
static void
{
}
static void
{
struct mounta *m;
char *fstype;
if (m->dataptr) {
}
}
if (m->spec)
if (m->dir)
if (m->fstype)
if (m->optptr)
}
}
static boolean_t
{
struct mounta *m;
/*
* Make sure we aren't geting passed NULL values or a "dir" that
* isn't "." and doesn't begin with "./".
*
* We also only want to perform autofs mounts, so make sure
* no-one is trying to trick us into doing anything else.
*/
return (B_TRUE);
/*
* We also don't like ".."s in the pathname. Symlinks are
* handled by the fact that we'll use NOFOLLOW when we do
* lookup()s.
*/
return (B_TRUE);
/*
* We don't want NULL values here either.
*/
return (B_TRUE);
/*
* We know what the claimed pathname *should* look like:
*
* If the parent (dfnp) is a mount point (VROOT), then
* the path should be (dfnip->fi_path + m->dir).
*
* Else, we know we're only two levels deep, so we use
* (dfnip->fi_path + dfnp->fn_name + m->dir).
*
* Furthermore, "." only makes sense if dfnp is a
* trigger node.
*
* At this point it seems like the passed-in path is
* redundant.
*/
return (B_TRUE);
} else {
}
CE_WARN, "autofs: expected path of '%s', "
return (B_TRUE);
}
return (B_FALSE); /* looks OK */
}
/*
* auto_invalid_action will validate the action_list received. If all is good
* this function returns FALSE, if there is a problem it returns TRUE.
*/
static boolean_t
{
/*
* Before we go any further, this better be a mount request.
*/
return (B_TRUE);
}
static int
{
action_list *p;
int auto_mount = 0;
int save_triggers = 0;
int update_times = 0;
char *mntpnt;
/*
* As automountd running in a zone may be compromised, and this may be
* an attack, we can't trust everything passed in by automountd, and we
* need to do argument verification. We'll issue a warning and drop
* the request if it doesn't seem right.
*/
/*
* This warning should be sent to the global zone,
* since presumably the zone administrator is the same
* as the attacker.
*/
"by automountd in zone %s.",
/*
* This conversation is over.
*/
return (EINVAL);
}
}
/*
* The daemon successfully mounted a filesystem
* on the AUTOFS root node.
*/
success++;
} else {
/*
* Clear MF_MOUNTPOINT.
*/
}
}
auto_mount = 0;
/*
* use the parent directory's timeout since it's the
*/
/*
* The mountpoint is relative, and it is guaranteed to
* begin with "."
*
*/
/*
* mounting on the trigger node
*/
goto mount;
}
/*
* ignore "./" in front of mountpoint
*/
} else {
}
/*
* Daemon didn't mount anything on the root
* We have to create the mountpoint if it
* doesn't exist already
*
* We use the caller's credentials in case a
* UID-match is required
* (MF_THISUID_MATCH_RQD).
*/
if (error == 0) {
/*
* AUTOFS mountpoint exists
*/
"auto_perform_actions:"
" mfnp=%p covered", (void *)mfnp);
}
} else {
/*
* Create AUTOFS mountpoint
*/
mfnp->fn_linkcnt++;
}
if (!error)
update_times = 1;
if (!error) {
/*
* mfnp is already held.
*/
} else {
CE_WARN, "autofs: mount of %s "
"failed - can't create"
" mountpoint.", buff);
continue;
}
} else {
/*
* Find mountpoint in VFS mounted here. If not
* found, fail the submount, though the overall
* mount has succeeded since the root is
* mounted.
*/
CE_WARN, "autofs: mount of %s "
"failed - mountpoint doesn't"
" exist.", buff);
continue;
}
CE_WARN, "autofs: %s symbolic "
"link: not a valid mountpoint "
"- mount failed", buff);
continue;
}
}
/*
* Copy mounta struct here so we can substitute a
* buffer that is large enough to hold the returned
* option string, if that string is longer than the
* input option string.
* This can happen if there are default options enabled
* that were not in the input option string.
*/
/*
* We use the zone's kcred because we don't want the
* zone to be able to thus do something it wouldn't
* normally be able to.
*/
if (error != 0) {
CE_WARN, "autofs: domount of %s failed "
continue;
}
/*
* If mountpoint is an AUTOFS node, then I'm going to
* flag it that the Filesystem mounted on top was
* mounted in the kernel so that the unmount can be
* done inside the kernel as well.
* I don't care to flag non-AUTOFS mountpoints when an
* AUTOFS in-kernel mount was done on top, because the
* unmount routine already knows that such case was
* done in the kernel.
*/
}
(void) vn_vfswlock_wait(mvp);
if (error) {
/*
* We've dropped the locks, so let's
* get the mounted vfs again in case
* it changed.
*/
(void) vn_vfswlock_wait(mvp);
if (error) {
"autofs: could not unmount"
" vfs=%p", (void *)mvfsp);
}
} else
continue;
}
} else {
continue;
}
/*
* At this time we want to save the AUTOFS filesystem
* as a trigger node. (We only do this if the mount
* occurred on a node different from the root.
* We look at the trigger nodes during
* the automatic unmounting to make sure we remove them
* as a unit and remount them as a unit if the
* filesystem mounted at the root could not be
* unmounted.
*/
/*
* Add AUTOFS mount to hierarchy
*/
/*
* Don't VN_RELE(newvp) here since dfnp now
* holds reference to it as its trigger node.
*/
else
} else
if (!error)
success++;
if (update_times) {
gethrestime(&now);
}
}
if (save_triggers) {
/*
* Make sure the parent can't be freed while it has triggers.
*/
}
done:
/*
* Return failure if daemon didn't mount anything, and all
* kernel mounts attempted failed.
*/
if ((error == 0) && save_triggers) {
/*
* Save action_list information, so that we can use it
* when it comes time to remount the trigger nodes
* The action list is freed when the directory node
* containing the reference to it is unmounted in
* unmount_tree().
*/
} else {
/*
* free the action list now,
*/
}
}
return (error);
}
fnnode_t *
char *name,
struct autofs_globals *fngp)
{
char *tmpname;
/*
* autofs uses odd inode numbers
* automountd uses even inode numbers
*
* To preserve the age-old semantics that inum+devid is unique across
* the system, this variable must be global across zones.
*/
/*
* ".." is added in auto_enter and auto_mount.
* "." is added in auto_mkdir and auto_mount.
*/
/*
* Note that fn_size and fn_linkcnt are already 0 since
* we used kmem_zalloc to allocated fnp
*/
gethrestime(&now);
nodeid += 2;
fngp->fng_fnnode_count++;
return (fnp);
}
void
{
vn_invalid(vp);
if (fnp->fn_symlink) {
}
}
void
{
AUTOFS_DPRINT((4,
"auto_disconnect: dfnp=%p fnp=%p linkcnt=%d\n v_count=%d",
(void *)vp);
}
/*
* Decrement by 1 because we're removing the entry in dfnp.
*/
fnp->fn_linkcnt--;
/*
* only changed while holding parent's (dfnp) rw_lock
*/
for (;;) {
"auto_disconnect: %p not in %p dirent list",
}
/* child had a pointer to parent ".." */
dfnp->fn_linkcnt--;
break;
}
}
gethrestime(&now);
}
int
{
/*
* offset = 0 for '.' and offset = 1 for '..'
*/
offset = 2;
}
/*
* "thisuser" kind of node, need to
* match CREDs as well
*/
return (EEXIST);
} else {
return (EEXIST);
}
}
}
} else if (offset == 0) {
}
}
dfnp->fn_globals);
return (ENOMEM);
/*
* I don't hold the mutex on fnpp because I created it, and
* I'm already holding the writers lock for it's parent
* directory, therefore nobody can reference it without me first
* releasing the writers lock.
*/
/*
* dfnp->fn_linkcnt and dfnp->fn_size protected by dfnp->rw_lock
*/
return (0);
}
int
{
fnnode_t *p;
(void *)dvp);
}
mutex_enter(&p->fn_lock);
if (p->fn_flags & MF_THISUID_MATCH_RQD) {
/*
* "thisuser" kind of node
* Need to match CREDs as well
*/
mutex_exit(&p->fn_lock);
} else {
/*
* No need to check CRED
*/
mutex_exit(&p->fn_lock);
match = 1;
}
}
if (match) {
error = 0;
if (fnpp) {
*fnpp = p;
}
break;
}
}
return (error);
}
/*
* If dvp is mounted on, get path's vnode in the mounted on
* filesystem. Path is relative to dvp, ie "./path".
* If successful, *mvp points to a the held mountpoint vnode.
*/
/* ARGSUSED */
static int
char *path,
{
int error = 0;
return (error);
/*
* Now that we have the vfswlock, check to see if dvp
* is still mounted on. If not, then just bail out as
* there is no need to remount the triggers since the
* higher level mount point has gotten unmounted.
*/
goto done;
}
/*
* Since mounted on, lookup "path" in the new filesystem,
* it is important that we do the filesystem jump here to
* avoid lookuppn() calling auto_lookup on dvp and deadlock.
*/
if (error)
goto done;
/*
* We do a VN_HOLD on newvp just in case the first call to
* lookuppnvp() fails with ENAMETOOLONG. We should still have a
* reference to this vnode for the second call to lookuppnvp().
*/
/*
* Now create the pathname struct so we can make use of lookuppnvp,
* and pn_getcomponent.
*/
if (error == 0) {
} else
if (error == ENAMETOOLONG) {
/*
* This thread used a pathname > TYPICALMAXPATHLEN bytes long.
* newvp is VN_RELE'd by this call to lookuppnvp.
*
* Using 'rootdir' in a zone's context is OK here: we already
* ascertained that there are no '..'s in the path, and we're
* not following symlinks.
*/
} else
} else {
/*
* Need to release newvp here since we held it.
*/
}
done:
return (error);
}
/*
* The caller, should have already VN_RELE'd its reference to the
* root vnode of this filesystem.
*/
static int
{
int error;
AUTOFS_DPRINT((4,
"auto_inkernel_unmount: devid=%lx mntpnt(%p) count %u\n",
/*
* Perform the unmount
* The mountpoint has already been locked by the caller.
*/
return (error);
}
/*
* unmounts trigger nodes in the kernel.
*/
static void
{
int error = 0;
/*
* drop writer's lock since the unmount will end up
* disconnecting this node from fnp and needs to acquire
* the writer's lock again.
* next has at least a reference count >= 2 since it's
* a trigger node, therefore can not be accidentally freed
* by a VN_RELE
*/
/*
* Its parent was holding a reference to it, since this
* is a trigger vnode.
*/
"unmount of vp=%p failed error=%d",
}
/*
* reacquire writer's lock
*/
}
/*
* We were holding a reference to our parent. Drop that.
*/
}
/*
* This routine locks the mountpoint of every trigger node if they're
* not busy, or returns EBUSY if any node is busy.
*/
static boolean_t
{
int done;
int lck_error = 0;
/* MF_LOOKUP should never be set on trigger nodes */
/*
* The vn_vfsunlock will be done in auto_inkernel_unmount.
*/
/*
* couldn't lock it because it's busy,
* It is mounted on or has dirents?
* If reference count is greater than two, then
* somebody else is holding a reference to this vnode.
* One reference is for the mountpoint, and the second
* is for the trigger node.
*/
/*
* Unlock previously locked mountpoints
*/
/*
* Unlock all nodes previously
* locked. All nodes up to 'tp'
* were successfully locked. If 'lck_err' is
* set, then 'tp' was not locked, and thus
* should not be unlocked. If
* 'lck_err' is not set, then 'tp' was
* successfully locked, and it should
* be unlocked.
*/
}
}
return (B_TRUE);
}
}
return (B_FALSE);
}
/*
* It is the caller's responsibility to grab the VVFSLOCK.
* Releases the VVFSLOCK upon return.
*/
static int
{
int error = 0;
/*
* Mount was performed in the kernel, so
* do an in-kernel unmount. auto_inkernel_unmount()
* will vn_vfsunlock(cvp).
*/
} else {
/*
* Get the mnttab information of the node
* and ask the daemon to unmount it.
*/
"unmount_node: no memory");
goto done;
}
if (mntoptslen > AUTOFS_MAXOPTSLEN)
/*
* Since a zone'd automountd's view of the autofs mount points
* differs from those in the kernel, we need to make sure we
* give it consistent mount points.
*/
}
}
}
}
done:
error));
return (error);
}
/*
* return EBUSY if any thread is holding a reference to this vnode
* other than us. Result of this function cannot be relied on, since
* it doesn't follow proper locking rules (i.e. vp->v_vfsmountedhere
* and fnp->fn_trigger can change throughout this function). However
* it's good enough for rough estimation.
*/
static int
{
int error = 0;
/*
* number of references to expect for
* a non-busy vnode.
*/
/*
* parent holds a pointer to us (trigger)
*/
count++;
}
/*
* The trigger nodes have a hold on us.
*/
count++;
}
if (vn_ismntpt(vp)) {
/*
* File system is mounted on us.
*/
count++;
}
count++;
return (error);
}
/*
* rootvp is the root of the AUTOFS filesystem.
* If rootvp is busy (v_count > 1) returns EBUSY.
* else removes every vnode under this tree.
* ASSUMPTION: Assumes that the only node which can be busy is
* the root vnode. This filesystem better be two levels deep only,
* the root and its immediate subdirs.
* The daemon will "AUTOFS direct-mount" only one level below the root.
*/
static void
{
/*
* Remove all its immediate subdirectories.
*/
fnp->fn_linkcnt--;
}
}
/*
* If a node matches all unmount criteria, do:
* destroy subordinate trigger node(s) if there is any
* unmount filesystem mounted on top of the node if there is any
*
* Function should be called with locked fnp's mutex. The mutex is
* unlocked before return from function.
*/
static int
{
int error = 0;
(void *)fnp));
/*
* If either a mount, lookup or another unmount of this subtree is in
* progress, don't attempt to unmount at this time.
*/
return (EBUSY);
}
/*
* Bail out if someone else is holding reference to this vnode.
* This check isn't just an optimization (someone is probably
* just about to trigger mount). It is necessary to prevent a deadlock
* in domount() called from auto_perform_actions() if unmount of
* trigger parent fails. domount() calls lookupname() to resolve
* special in mount arguments. Special is set to a map name in case
* of autofs triggers (i.e. auto_ws.sun.com). Thus if current
* working directory is set to currently processed node, lookupname()
* calls into autofs vnops in order to resolve special, which deadlocks
* the process.
*
* Note: This should be fixed. Autofs shouldn't pass the map name
* in special and avoid useless lookup with potentially disasterous
* consequence.
*/
return (EBUSY);
}
/*
* If not forced operation, back out if node has been referenced
* recently.
*/
if (!force &&
return (EBUSY);
}
/* unmount next level triggers if there are any */
if (triggers_busy(fnp)) {
return (EBUSY);
}
/*
* At this point, we know all trigger nodes are locked,
* and they're not busy or mounted on.
*
* Attempt to unmount all trigger nodes, save the
* action_list in case we need to remount them later.
* The action_list will be freed later if there was no
* need to remount the trigger nodes.
*/
}
(void) vn_vfswlock_wait(vp);
/* vn_vfsunlock(vp) is done inside unmount_node() */
if (error == ECONNRESET) {
/*
* The filesystem was unmounted before the
* daemon died. Unfortunately we can not
* determine whether all the cleanup work was
* successfully finished (i.e. update mnttab,
* or notify NFS server of the unmount).
* We should not retry the operation since the
* filesystem has already been unmounted, and
* may have already been removed from mnttab,
* the daemon will not be matched. So we have
* to be content with the partial unmount.
* Since the mountpoint is no longer covered, we
* clear the error condition.
*/
error = 0;
CE_WARN, "autofs: automountd "
"connection dropped when unmounting %s/%s",
}
}
} else {
/* Destroy all dirents of fnp if we unmounted its triggers */
if (trigger_unmount)
}
/* If unmount failed, we got to remount triggers */
if (error != 0) {
if (trigger_unmount) {
int ret;
/*
* The action list was free'd by auto_perform_actions
*/
if (ret != 0) {
CE_WARN, "autofs: can't remount triggers "
}
}
} else {
/* Free the action list here */
if (trigger_unmount)
/*
* Other threads may be waiting for this unmount to
* finish. We must let it know that in order to
* proceed, it must trigger the mount itself.
*/
}
return (error);
}
/*
* This is an implementation of depth-first search in a tree rooted by
* start_fnp and composed from fnnodes. Links between tree levels are
* fn_dirents, fn_trigger in fnnode_t and v_mountedvfs in vnode_t (if
* mounted vfs is autofs). The algorithm keeps track of visited nodes
* by means of a timestamp (fn_unmount_ref_time).
*
* Upon top-down traversal of the tree we apply following locking scheme:
* lock fn_rwlock of current node
* grab reference to child's vnode (VN_HOLD)
* unlock fn_rwlock
* free reference to current vnode (VN_RELE)
* Similar locking scheme is used for down-top and left-right traversal.
*
* Algorithm examines the most down-left node in tree, which hasn't been
* visited yet. From this follows that nodes are processed in bottom-up
* fashion.
*
* Function returns either zero if unmount of root node was successful
* or error code (mostly EBUSY).
*/
int
{
/*
* Timestamp, which visited nodes are marked with, to distinguish them
* from unvisited nodes.
*/
timestamp = gethrestime_sec();
/* Loop until we examine all nodes in the tree */
/*
* New candidate for processing must have been already visited,
* by us because we want to process tree nodes in bottom-up
* order.
*/
/*
* Fall through to next if-branch to pick
* sibling or parent of this node.
*/
}
/*
* If this node has been already visited, it means that it's
* dead end and we need to pick sibling or parent as next node.
*/
/*
* Obtain parent's readers lock before grabbing
* reference to sibling.
*/
continue;
}
/*
* All descendants and siblings were visited. Perform
* bottom-up move.
*/
continue;
}
/*
* Mark node as visited. Note that the timestamp could have
* been updated by somebody else in the meantime.
*/
/*
*
* We need to hold both locks at once: fn_lock because we need
* to read MF_INPROG and fn_rwlock to prevent anybody from
* modifying fn_trigger until its used to traverse triggers
* below.
*
* Acquire fn_rwlock in non-blocking mode to avoid deadlock.
* If it can't be acquired, then acquire locks in correct
* order.
*/
}
continue;
}
/*
* Examine descendants in this order: triggers, dirents, autofs
* mounts.
*/
continue;
}
continue;
}
(void) vn_vfswlock_wait(curvp);
/*
*
* We know this call to VFS_ROOT is safe to call while
* holding VVFSLOCK, since it resolves to a call to
* auto_root().
*/
"autofs: VFS_ROOT(vfs=%p) failed",
(void *)vfsp);
}
continue;
}
}
/*
* Now we deal with the root node (currfnp's mutex is unlocked
* in try_unmount_node()).
*/
}
/*
* XXX unmount_tree() is not suspend-safe within the scope of
* the present model defined for cpr to suspend the system. Calls made
* by the unmount_tree() that have been identified to be unsafe are
* (1) RPC client handle setup and client calls to automountd which can
* block deep down in the RPC library, (2) kmem_alloc() calls with the
* KM_SLEEP flag which can block if memory is low, and (3) VFS_*() and
* VOP_*() calls which can result in over the wire calls to servers.
* The thread should be completely reevaluated to make it suspend-safe in
* case of future updates to the cpr model.
*/
void
{
"unmount_tree");
/*
* autofssys() will be calling in from the global zone and doing
* work on the behalf of the given zone, hence we can't always
* assert that we have the right credentials, nor that the
* caller is always in the correct zone.
*
* We do, however, know that if this is a "forced unmount"
* operation (which autofssys() does), then we won't go down to
* the krpc layers, so we don't need to fudge with the
* credentials.
*/
/*
* If automountd is not running in this zone,
* don't attempt unmounting this round.
*/
/*
* Iterate over top level autofs filesystems and call
* unmount_subtree() for each of them.
*/
}
}
}
static void
{
zthread_exit();
/* NOTREACHED */
}
void
{
callb_generic_cpr, "auto_do_unmount");
for (;;) { /* forever */
/*
* zone is exiting... don't create any new threads.
* fng_unmount_threads_lock is released implicitly by
* the below.
*/
zthread_exit();
/* NOTREACHED */
}
0, minclsyspri);
} else
goto newthread;
}
/* NOTREACHED */
}
/*
* Is nobrowse specified in option string?
* opts should be a null ('\0') terminated string.
* Returns non-zero if nobrowse has been specified.
*/
int
{
char *buf;
char *p;
char *t;
int nobrowse = 0;
int last_opt = 0;
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);
return (nobrowse);
}
/*
* used to log warnings only if automountd is running
* with verbose mode set
*/
void
{
if (verbose) {
}
}
#ifdef DEBUG
static int autofs_debug = 0;
/*
* Utilities used by both client and server
* Standard levels:
* 0) no debugging
* 1) hard failures
* 2) soft failures
* 3) current test software
* 4) main procedure entry points
* 5) main procedure exit points
* 6) utility procedure entry points
* 7) utility procedure exit points
* 8) obscure procedure entry points
* 9) obscure procedure exit points
* 10) random stuff
* 11) all <= 1
* 12) all <= 2
* 13) all <= 3
* ...
*/
/* PRINTFLIKE2 */
void
{
if (autofs_debug == level ||
}
}
#endif /* DEBUG */