/* $NetBSD: amfs_host.c,v 1.1.1.3 2015/01/17 16:34:15 christos Exp $ */
/*
* Copyright (c) 1997-2014 Erez Zadok
* Copyright (c) 1990 Jan-Simon Pendry
* Copyright (c) 1990 Imperial College of Science, Technology & Medicine
* Copyright (c) 1990 The Regents of the University of California.
* All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Jan-Simon Pendry at Imperial College, London.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*
* File: am-utils/amd/amfs_host.c
*
*/
/*
* NFS host file system.
* Mounts all exported filesystems from a given host.
* This has now degenerated into a mess but will not
* be rewritten. Amd 6 will support the abstractions
* needed to make this work correctly.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* HAVE_CONFIG_H */
#include <am_defs.h>
#include <amd.h>
static char *amfs_host_match(am_opts *fo);
static int amfs_host_init(mntfs *mf);
static int amfs_host_mount(am_node *am, mntfs *mf);
static int amfs_host_umount(am_node *am, mntfs *mf);
static void amfs_host_umounted(mntfs *mf);
/*
* Ops structure
*/
am_ops amfs_host_ops =
{
"host",
amfs_host_match,
amfs_host_init,
amfs_host_mount,
amfs_host_umount,
amfs_error_lookup_child,
amfs_error_mount_child,
amfs_error_readdir,
0, /* amfs_host_readlink */
0, /* amfs_host_mounted */
amfs_host_umounted,
find_nfs_srvr,
0, /* amfs_host_get_wchan */
FS_MKMNT | FS_BACKGROUND | FS_AMQINFO,
#ifdef HAVE_FS_AUTOFS
AUTOFS_HOST_FS_FLAGS,
#endif /* HAVE_FS_AUTOFS */
};
/*
* Determine the mount point:
*
* The next change we put in to better handle PCs. This is a bit
* disgusting, so you'd better sit down. We change the make_mntpt function
* to look for exported file systems without a leading '/'. If they don't
* have a leading '/', we add one. If the export is 'a:' through 'z:'
* (without a leading slash), we change it to 'a%' (or b% or z%). This
* allows the entire PC disk to be mounted.
*/
static void
make_mntpt(char *mntpt, size_t l, const exports ex, const char *mf_mount)
{
if (ex->ex_dir[0] == '/') {
if (ex->ex_dir[1] == 0)
xstrlcpy(mntpt, mf_mount, l);
else
xsnprintf(mntpt, l, "%s%s", mf_mount, ex->ex_dir);
} else if (ex->ex_dir[0] >= 'a' &&
ex->ex_dir[0] <= 'z' &&
ex->ex_dir[1] == ':' &&
ex->ex_dir[2] == '/' &&
ex->ex_dir[3] == 0)
xsnprintf(mntpt, l, "%s/%c%%", mf_mount, ex->ex_dir[0]);
else
xsnprintf(mntpt, l, "%s/%s", mf_mount, ex->ex_dir);
}
/*
* Execute needs the same as NFS plus a helper command
*/
static char *
amfs_host_match(am_opts *fo)
{
extern am_ops nfs_ops;
/*
* Make sure rfs is specified to keep nfs_match happy...
*/
if (!fo->opt_rfs)
fo->opt_rfs = "/";
return (*nfs_ops.fs_match) (fo);
}
static int
amfs_host_init(mntfs *mf)
{
u_short mountd_port;
if (strchr(mf->mf_info, ':') == 0)
return ENOENT;
/*
* This is primarily to schedule a wakeup so that as soon
* as our fileserver is ready, we can continue setting up
* the host filesystem. If we don't do this, the standard
* amfs_auto code will set up a fileserver structure, but it will
* have to wait for another nfs request from the client to come
* in before finishing. Our way is faster since we don't have
* to wait for the client to resend its request (which could
* take a second or two).
*/
/*
* First, we find the fileserver for this mntfs and then call
* get_mountd_port with our mntfs passed as the wait channel.
* get_mountd_port will check some things and then schedule
* it so that when the fileserver is ready, a wakeup is done
* on this mntfs. amfs_cont() is already sleeping on this mntfs
* so as soon as that wakeup happens amfs_cont() is called and
* this mount is retried.
*/
if (mf->mf_server)
/*
* We don't really care if there's an error returned.
* Since this is just to help speed things along, the
* error will get handled properly elsewhere.
*/
get_mountd_port(mf->mf_server, &mountd_port, get_mntfs_wchan(mf));
return 0;
}
static int
do_mount(am_nfs_handle_t *fhp, char *mntdir, char *fs_name, mntfs *mf)
{
struct stat stb;
dlog("amfs_host: mounting fs %s on %s\n", fs_name, mntdir);
(void) mkdirs(mntdir, 0555);
if (stat(mntdir, &stb) < 0 || (stb.st_mode & S_IFMT) != S_IFDIR) {
plog(XLOG_ERROR, "No mount point for %s - skipping", mntdir);
return ENOENT;
}
return mount_nfs_fh(fhp, mntdir, fs_name, mf);
}
static int
sortfun(const voidp x, const voidp y)
{
exports *a = (exports *) x;
exports *b = (exports *) y;
return strcmp((*a)->ex_dir, (*b)->ex_dir);
}
/*
* Get filehandle
*/
static int
fetch_fhandle(CLIENT *client, char *dir, am_nfs_handle_t *fhp, u_long nfs_version)
{
struct timeval tv;
enum clnt_stat clnt_stat;
struct fhstatus res;
#ifdef HAVE_FS_NFS3
struct am_mountres3 res3;
#endif /* HAVE_FS_NFS3 */
/*
* Pick a number, any number...
*/
tv.tv_sec = 20;
tv.tv_usec = 0;
dlog("Fetching fhandle for %s", dir);
/*
* Call the mount daemon on the remote host to
* get the filehandle. Use NFS version specific call.
*/
plog(XLOG_INFO, "fetch_fhandle: NFS version %d", (int) nfs_version);
#ifdef HAVE_FS_NFS3
if (nfs_version == NFS_VERSION3
#ifdef HAVE_FS_NFS4
#ifndef NO_FALLBACK
|| nfs_version == NFS_VERSION4
#endif /* NO_FALLBACK */
#endif /* HAVE_FS_NFS4 */
) {
memset((char *) &res3, 0, sizeof(res3));
clnt_stat = clnt_call(client,
MOUNTPROC_MNT,
(XDRPROC_T_TYPE) xdr_dirpath,
(SVC_IN_ARG_TYPE) &dir,
(XDRPROC_T_TYPE) xdr_am_mountres3,
(SVC_IN_ARG_TYPE) &res3,
tv);
if (clnt_stat != RPC_SUCCESS) {
plog(XLOG_ERROR, "mountd rpc failed: %s", clnt_sperrno(clnt_stat));
return EIO;
}
/* Check the status of the filehandle */
if ((errno = res3.fhs_status)) {
dlog("fhandle fetch for mount version 3 failed: %m");
return errno;
}
memset((voidp) &fhp->v3, 0, sizeof(am_nfs_fh3));
fhp->v3.am_fh3_length = res3.mountres3_u.mountinfo.fhandle.fhandle3_len;
memmove(fhp->v3.am_fh3_data,
res3.mountres3_u.mountinfo.fhandle.fhandle3_val,
fhp->v3.am_fh3_length);
} else { /* not NFS_VERSION3 mount */
#endif /* HAVE_FS_NFS3 */
clnt_stat = clnt_call(client,
MOUNTPROC_MNT,
(XDRPROC_T_TYPE) xdr_dirpath,
(SVC_IN_ARG_TYPE) &dir,
(XDRPROC_T_TYPE) xdr_fhstatus,
(SVC_IN_ARG_TYPE) &res,
tv);
if (clnt_stat != RPC_SUCCESS) {
plog(XLOG_ERROR, "mountd rpc failed: %s", clnt_sperrno(clnt_stat));
return EIO;
}
/* Check status of filehandle */
if (res.fhs_status) {
errno = res.fhs_status;
dlog("fhandle fetch for mount version 1 failed: %m");
return errno;
}
memmove(&fhp->v2, &res.fhs_fh, NFS_FHSIZE);
#ifdef HAVE_FS_NFS3
} /* end of "if (nfs_version == NFS_VERSION3)" statement */
#endif /* HAVE_FS_NFS3 */
/* all is well */
return 0;
}
/*
* Scan mount table to see if something already mounted
*/
static int
already_mounted(mntlist *mlist, char *dir)
{
mntlist *ml;
for (ml = mlist; ml; ml = ml->mnext)
if (STREQ(ml->mnt->mnt_dir, dir))
return 1;
return 0;
}
static int
amfs_host_mount(am_node *am, mntfs *mf)
{
struct timeval tv2;
CLIENT *client;
enum clnt_stat clnt_stat;
int n_export;
int j, k;
exports exlist = 0, ex;
exports *ep = NULL;
am_nfs_handle_t *fp = NULL;
char *host;
int error = 0;
struct sockaddr_in sin;
int sock = RPC_ANYSOCK;
int ok = FALSE;
mntlist *mlist;
char fs_name[MAXPATHLEN], *rfs_dir;
char mntpt[MAXPATHLEN];
struct timeval tv;
u_long mnt_version;
/*
* WebNFS servers don't necessarily run mountd.
*/
if (mf->mf_flags & MFF_WEBNFS) {
plog(XLOG_ERROR, "amfs_host_mount: cannot support WebNFS");
return EIO;
}
/*
* Read the mount list
*/
mlist = read_mtab(mf->mf_mount, mnttab_file_name);
#ifdef MOUNT_TABLE_ON_FILE
/*
* Unlock the mount list
*/
unlock_mntlist();
#endif /* MOUNT_TABLE_ON_FILE */
/*
* Take a copy of the server hostname, address, and nfs version
* to mount version conversion.
*/
host = mf->mf_server->fs_host;
sin = *mf->mf_server->fs_ip;
plog(XLOG_INFO, "amfs_host_mount: NFS version %d", (int) mf->mf_server->fs_version);
#ifdef HAVE_FS_NFS3
if (mf->mf_server->fs_version == NFS_VERSION3)
mnt_version = AM_MOUNTVERS3;
else
#endif /* HAVE_FS_NFS3 */
mnt_version = MOUNTVERS;
/*
* The original 10 second per try timeout is WAY too large, especially
* if we're only waiting 10 or 20 seconds max for the response.
* That would mean we'd try only once in 10 seconds, and we could
* lose the transmit or receive packet, and never try again.
* A 2-second per try timeout here is much more reasonable.
* 09/28/92 Mike Mitchell, mcm@unx.sas.com
*/
tv.tv_sec = 2;
tv.tv_usec = 0;
/*
* Create a client attached to mountd
*/
client = get_mount_client(host, &sin, &tv, &sock, mnt_version);
if (client == NULL) {
#ifdef HAVE_CLNT_SPCREATEERROR
plog(XLOG_ERROR, "get_mount_client failed for %s: %s",
host, clnt_spcreateerror(""));
#else /* not HAVE_CLNT_SPCREATEERROR */
plog(XLOG_ERROR, "get_mount_client failed for %s", host);
#endif /* not HAVE_CLNT_SPCREATEERROR */
error = EIO;
goto out;
}
if (!nfs_auth) {
error = make_nfs_auth();
if (error)
goto out;
}
client->cl_auth = nfs_auth;
dlog("Fetching export list from %s", host);
/*
* Fetch the export list
*/
tv2.tv_sec = 10;
tv2.tv_usec = 0;
clnt_stat = clnt_call(client,
MOUNTPROC_EXPORT,
(XDRPROC_T_TYPE) xdr_void,
0,
(XDRPROC_T_TYPE) xdr_exports,
(SVC_IN_ARG_TYPE) & exlist,
tv2);
if (clnt_stat != RPC_SUCCESS) {
const char *msg = clnt_sperrno(clnt_stat);
plog(XLOG_ERROR, "host_mount rpc failed: %s", msg);
/* clnt_perror(client, "rpc"); */
error = EIO;
goto out;
}
/*
* Figure out how many exports were returned
*/
for (n_export = 0, ex = exlist; ex; ex = ex->ex_next) {
n_export++;
}
/*
* Allocate an array of pointers into the list
* so that they can be sorted. If the filesystem
* is already mounted then ignore it.
*/
ep = (exports *) xmalloc(n_export * sizeof(exports));
for (j = 0, ex = exlist; ex; ex = ex->ex_next) {
make_mntpt(mntpt, sizeof(mntpt), ex, mf->mf_mount);
if (already_mounted(mlist, mntpt))
/* we have at least one mounted f/s, so don't fail the mount */
ok = TRUE;
else
ep[j++] = ex;
}
n_export = j;
/*
* Sort into order.
* This way the mounts are done in order down the tree,
* instead of any random order returned by the mount
* daemon (the protocol doesn't specify...).
*/
qsort(ep, n_export, sizeof(exports), sortfun);
/*
* Allocate an array of filehandles
*/
fp = (am_nfs_handle_t *) xmalloc(n_export * sizeof(am_nfs_handle_t));
/*
* Try to obtain filehandles for each directory.
* If a fetch fails then just zero out the array
* reference but discard the error.
*/
for (j = k = 0; j < n_export; j++) {
/* Check and avoid a duplicated export entry */
if (j > k && ep[k] && STREQ(ep[j]->ex_dir, ep[k]->ex_dir)) {
dlog("avoiding dup fhandle requested for %s", ep[j]->ex_dir);
ep[j] = NULL;
} else {
k = j;
error = fetch_fhandle(client, ep[j]->ex_dir, &fp[j],
mf->mf_server->fs_version);
if (error)
ep[j] = NULL;
}
}
/*
* Mount each filesystem for which we have a filehandle.
* If any of the mounts succeed then mark "ok" and return
* error code 0 at the end. If they all fail then return
* the last error code.
*/
xstrlcpy(fs_name, mf->mf_info, sizeof(fs_name));
if ((rfs_dir = strchr(fs_name, ':')) == (char *) NULL) {
plog(XLOG_FATAL, "amfs_host_mount: mf_info has no colon");
error = EINVAL;
goto out;
}
++rfs_dir;
for (j = 0; j < n_export; j++) {
ex = ep[j];
if (ex) {
/*
* Note: the sizeof space left in rfs_dir is what's left in fs_name
* after strchr() above returned a pointer _inside_ fs_name. The
* calculation below also takes into account that rfs_dir was
* incremented by the ++ above.
*/
xstrlcpy(rfs_dir, ex->ex_dir, sizeof(fs_name) - (rfs_dir - fs_name));
make_mntpt(mntpt, sizeof(mntpt), ex, mf->mf_mount);
if (do_mount(&fp[j], mntpt, fs_name, mf) == 0)
ok = TRUE;
}
}
/*
* Clean up and exit
*/
out:
discard_mntlist(mlist);
XFREE(ep);
XFREE(fp);
if (sock != RPC_ANYSOCK)
(void) amu_close(sock);
if (client)
clnt_destroy(client);
if (exlist)
xdr_pri_free((XDRPROC_T_TYPE) xdr_exports, (caddr_t) &exlist);
if (ok)
return 0;
return error;
}
/*
* Return true if pref is a directory prefix of dir.
*
* XXX TODO:
* Does not work if pref is "/".
*/
static int
directory_prefix(char *pref, char *dir)
{
int len = strlen(pref);
if (!NSTREQ(pref, dir, len))
return FALSE;
if (dir[len] == '/' || dir[len] == '\0')
return TRUE;
return FALSE;
}
/*
* Unmount a mount tree
*/
static int
amfs_host_umount(am_node *am, mntfs *mf)
{
mntlist *ml, *mprev;
int unmount_flags = (mf->mf_flags & MFF_ON_AUTOFS) ? AMU_UMOUNT_AUTOFS : 0;
int xerror = 0;
/*
* Read the mount list
*/
mntlist *mlist = read_mtab(mf->mf_mount, mnttab_file_name);
#ifdef MOUNT_TABLE_ON_FILE
/*
* Unlock the mount list
*/
unlock_mntlist();
#endif /* MOUNT_TABLE_ON_FILE */
/*
* Reverse list...
*/
ml = mlist;
mprev = NULL;
while (ml) {
mntlist *ml2 = ml->mnext;
ml->mnext = mprev;
mprev = ml;
ml = ml2;
}
mlist = mprev;
/*
* Unmount all filesystems...
*/
for (ml = mlist; ml && !xerror; ml = ml->mnext) {
char *dir = ml->mnt->mnt_dir;
if (directory_prefix(mf->mf_mount, dir)) {
int error;
dlog("amfs_host: unmounts %s", dir);
/*
* Unmount "dir"
*/
error = UMOUNT_FS(dir, mnttab_file_name, unmount_flags);
/*
* Keep track of errors
*/
if (error) {
/*
* If we have not already set xerror and error is not ENOENT,
* then set xerror equal to error and log it.
* 'xerror' is the return value for this function.
*
* We do not want to pass ENOENT as an error because if the
* directory does not exists our work is done anyway.
*/
if (!xerror && error != ENOENT)
xerror = error;
if (error != EBUSY) {
errno = error;
plog(XLOG_ERROR, "Tree unmount of %s failed: %m", ml->mnt->mnt_dir);
}
} else {
(void) rmdirs(dir);
}
}
}
/*
* Throw away mount list
*/
discard_mntlist(mlist);
/*
* Try to remount, except when we are shutting down.
*/
if (xerror && amd_state != Finishing) {
xerror = amfs_host_mount(am, mf);
if (!xerror) {
/*
* Don't log this - it's usually too verbose
plog(XLOG_INFO, "Remounted host %s", mf->mf_info);
*/
xerror = EBUSY;
}
}
return xerror;
}
/*
* Tell mountd we're done.
* This is not quite right, because we may still
* have other filesystems mounted, but the existing
* mountd protocol is badly broken anyway.
*/
static void
amfs_host_umounted(mntfs *mf)
{
char *host;
CLIENT *client;
enum clnt_stat clnt_stat;
struct sockaddr_in sin;
int sock = RPC_ANYSOCK;
struct timeval tv;
u_long mnt_version;
if (mf->mf_error || mf->mf_refc > 1 || !mf->mf_server)
return;
/*
* WebNFS servers shouldn't ever get here.
*/
if (mf->mf_flags & MFF_WEBNFS) {
plog(XLOG_ERROR, "amfs_host_umounted: cannot support WebNFS");
return;
}
/*
* Take a copy of the server hostname, address, and NFS version
* to mount version conversion.
*/
host = mf->mf_server->fs_host;
sin = *mf->mf_server->fs_ip;
plog(XLOG_INFO, "amfs_host_umounted: NFS version %d", (int) mf->mf_server->fs_version);
#ifdef HAVE_FS_NFS3
if (mf->mf_server->fs_version == NFS_VERSION3)
mnt_version = AM_MOUNTVERS3;
else
#endif /* HAVE_FS_NFS3 */
mnt_version = MOUNTVERS;
/*
* Create a client attached to mountd
*/
tv.tv_sec = 10;
tv.tv_usec = 0;
client = get_mount_client(host, &sin, &tv, &sock, mnt_version);
if (client == NULL) {
#ifdef HAVE_CLNT_SPCREATEERROR
plog(XLOG_ERROR, "get_mount_client failed for %s: %s",
host, clnt_spcreateerror(""));
#else /* not HAVE_CLNT_SPCREATEERROR */
plog(XLOG_ERROR, "get_mount_client failed for %s", host);
#endif /* not HAVE_CLNT_SPCREATEERROR */
goto out;
}
if (!nfs_auth) {
if (make_nfs_auth())
goto out;
}
client->cl_auth = nfs_auth;
dlog("Unmounting all from %s", host);
clnt_stat = clnt_call(client,
MOUNTPROC_UMNTALL,
(XDRPROC_T_TYPE) xdr_void,
0,
(XDRPROC_T_TYPE) xdr_void,
0,
tv);
if (clnt_stat != RPC_SUCCESS && clnt_stat != RPC_SYSTEMERROR) {
/* RPC_SYSTEMERROR seems to be returned for no good reason ... */
const char *msg = clnt_sperrno(clnt_stat);
plog(XLOG_ERROR, "unmount all from %s rpc failed: %s", host, msg);
goto out;
}
out:
if (sock != RPC_ANYSOCK)
(void) amu_close(sock);
if (client)
clnt_destroy(client);
}