#include "am.h"
#include <sys/stat.h>
#ifdef HAS_NFS
#define NFS
#define NFSCLIENT
#include "mount.h"
#define unx_error(e) ((int)(e))
typedef struct fh_cache fh_cache;
struct fh_cache {
qelem fh_q;
void *fh_wchan;
int fh_error;
int fh_id;
int fh_cid;
fhstatus fh_handle;
struct sockaddr_in fh_sin;
fserver *fh_fs;
char *fh_path;
};
#define FH_TTL (5 * 60)
#define FH_TTL_ERROR (30)
static int fh_id = 0;
#define FHID_ALLOC() (++fh_id)
extern qelem fh_head;
qelem fh_head = { &fh_head, &fh_head };
static int call_mountd(fh_cache*, unsigned long, fwd_fun, void *);
AUTH *nfs_auth;
static fh_cache *
find_nfs_fhandle_cache(void *idv, int done)
{
fh_cache *fp, *fp2 = 0;
int id = (int) ((long)idv);
ITER(fp, fh_cache, &fh_head) {
if (fp->fh_id == id) {
fp2 = fp;
break;
}
}
#ifdef DEBUG
if (fp2) {
dlog("fh cache gives fp %#x, fs %s", fp2, fp2->fh_path);
} else {
dlog("fh cache search failed");
}
#endif
if (fp2 && !done) {
fp2->fh_error = ETIMEDOUT;
return 0;
}
return fp2;
}
static void
got_nfs_fh(void *pkt, int len, struct sockaddr_in *sa,
struct sockaddr_in *ia, void *idv, int done)
{
fh_cache *fp = find_nfs_fhandle_cache(idv, done);
if (fp) {
fp->fh_handle.fhs_vers = MOUNTVERS;
fp->fh_error = pickup_rpc_reply(pkt, len, &fp->fh_handle,
xdr_fhstatus);
if (!fp->fh_error) {
#ifdef DEBUG
dlog("got filehandle for %s:%s", fp->fh_fs->fs_host, fp->fh_path);
#endif
if (fp->fh_wchan) {
#ifdef DEBUG
dlog("Calling wakeup on %#x", fp->fh_wchan);
#endif
wakeup(fp->fh_wchan);
}
}
}
}
void
flush_nfs_fhandle_cache(fserver *fs)
{
fh_cache *fp;
ITER(fp, fh_cache, &fh_head) {
if (fp->fh_fs == fs || fs == 0) {
fp->fh_sin.sin_port = (u_short) 0;
fp->fh_error = -1;
}
}
}
static void
discard_fh(void *arg)
{
fh_cache *fp = arg;
rem_que(&fp->fh_q);
#ifdef DEBUG
dlog("Discarding filehandle for %s:%s", fp->fh_fs->fs_host, fp->fh_path);
#endif
free_srvr(fp->fh_fs);
free(fp->fh_path);
free(fp);
}
static int
prime_nfs_fhandle_cache(char *path, fserver *fs, fhstatus *fhbuf, void *wchan)
{
fh_cache *fp, *fp_save = 0;
int error;
int reuse_id = FALSE;
#ifdef DEBUG
dlog("Searching cache for %s:%s", fs->fs_host, path);
#endif
ITER(fp, fh_cache, &fh_head) {
if (fs == fp->fh_fs && strcmp(path, fp->fh_path) == 0) {
switch (fp->fh_error) {
case 0:
error = fp->fh_error = unx_error(fp->fh_handle.fhs_stat);
if (error == 0) {
if (fhbuf)
bcopy(&fp->fh_handle, fhbuf,
sizeof(fp->fh_handle));
if (fp->fh_cid)
untimeout(fp->fh_cid);
fp->fh_cid = timeout(FH_TTL,
discard_fh, fp);
} else if (error == EACCES) {
plog(XLOG_INFO, "Filehandle denied for \"%s:%s\"",
fs->fs_host, path);
} else {
errno = error;
plog(XLOG_INFO, "Filehandle error for \"%s:%s\": %m",
fs->fs_host, path);
}
return error;
case -1:
fp_save = fp;
reuse_id = TRUE;
break;
default:
error = fp->fh_error;
fp->fh_error = -1;
return error;
}
break;
}
}
if (fp_save) {
fp = fp_save;
untimeout(fp->fh_cid);
free_srvr(fp->fh_fs);
free(fp->fh_path);
} else {
fp = ALLOC(fh_cache);
bzero(fp, sizeof(*fp));
ins_que(&fp->fh_q, &fh_head);
}
if (!reuse_id)
fp->fh_id = FHID_ALLOC();
fp->fh_wchan = wchan;
fp->fh_error = -1;
fp->fh_cid = timeout(FH_TTL, discard_fh, fp);
if (fp->fh_sin.sin_addr.s_addr != fs->fs_ip->sin_addr.s_addr) {
fp->fh_sin = *fs->fs_ip;
fp->fh_sin.sin_port = 0;
}
fp->fh_fs = dup_srvr(fs);
fp->fh_path = strdup(path);
error = call_mountd(fp, MOUNTPROC_MNT, got_nfs_fh, wchan);
if (error) {
untimeout(fp->fh_cid);
fp->fh_cid = timeout(error < 0 ? 2 * ALLOWED_MOUNT_TIME : FH_TTL_ERROR,
discard_fh, fp);
fp->fh_error = error;
} else {
error = fp->fh_error;
}
return error;
}
int
make_nfs_auth(void)
{
static int group_wheel = 0;
nfs_auth = authunix_create(hostd, 0, 0, 1, &group_wheel);
if (!nfs_auth)
return ENOBUFS;
return 0;
}
static int
call_mountd(fh_cache *fp, u_long proc, fwd_fun f, void *wchan)
{
struct rpc_msg mnt_msg;
int len;
char iobuf[8192];
int error;
if (!nfs_auth) {
error = make_nfs_auth();
if (error)
return error;
}
if (fp->fh_sin.sin_port == 0) {
u_short port;
error = nfs_srvr_port(fp->fh_fs, &port, wchan);
if (error)
return error;
fp->fh_sin.sin_port = port;
}
rpc_msg_init(&mnt_msg, MOUNTPROG, MOUNTVERS, (unsigned long) 0);
len = make_rpc_packet(iobuf, sizeof(iobuf), proc,
&mnt_msg, &fp->fh_path, xdr_nfspath, nfs_auth);
if (len > 0) {
error = fwd_packet(MK_RPC_XID(RPC_XID_MOUNTD, fp->fh_id),
iobuf, len, &fp->fh_sin, &fp->fh_sin,
(void *)((long)fp->fh_id), f);
} else {
error = -len;
}
fp->fh_sin.sin_port = 0;
return error;
}
static char *
nfs_match(am_opts *fo)
{
char *xmtab;
if (fo->opt_fs && !fo->opt_rfs)
fo->opt_rfs = fo->opt_fs;
if (!fo->opt_rfs) {
plog(XLOG_USER, "nfs: no remote filesystem specified");
return FALSE;
}
if (!fo->opt_rhost) {
plog(XLOG_USER, "nfs: no remote host specified");
return FALSE;
}
xmtab = xmalloc(strlen(fo->opt_rhost) + strlen(fo->opt_rfs) + 2);
snprintf(xmtab, strlen(fo->opt_rhost) + strlen(fo->opt_rfs) + 2,
"%s:%s", fo->opt_rhost, fo->opt_rfs);
#ifdef DEBUG
dlog("NFS: mounting remote server \"%s\", remote fs \"%s\" on \"%s\"",
fo->opt_rhost, fo->opt_rfs, fo->opt_fs);
#endif
return xmtab;
}
static int
nfs_init(mntfs *mf)
{
if (!mf->mf_private) {
int error;
fhstatus fhs;
char *colon = strchr(mf->mf_info, ':');
if (colon == 0)
return ENOENT;
error = prime_nfs_fhandle_cache(colon+1, mf->mf_server,
&fhs, mf);
if (!error) {
mf->mf_private = ALLOC(fhstatus);
mf->mf_prfree = free;
bcopy(&fhs, mf->mf_private, sizeof(fhs));
}
return error;
}
return 0;
}
int
mount_nfs_fh(fhstatus *fhp, char *dir, char *fs_name, char *opts,
mntfs *mf)
{
struct nfs_args nfs_args;
struct mntent mnt;
int retry;
char *colon;
char host[HOST_NAME_MAX+1 + PATH_MAX+2];
fserver *fs = mf->mf_server;
int flags;
char *xopts;
int error;
#ifdef notdef
unsigned short port;
#endif
const char *type = MOUNT_NFS;
bzero(&nfs_args, sizeof(nfs_args));
if (!(colon = strchr(fs_name, ':')))
return ENOENT;
strlcpy(host, fs_name, sizeof(host));
if (mf->mf_remopts && *mf->mf_remopts && !islocalnet(fs->fs_ip->sin_addr.s_addr))
xopts = strdup(mf->mf_remopts);
else
xopts = strdup(opts);
bzero(&nfs_args, sizeof(nfs_args));
mnt.mnt_dir = dir;
mnt.mnt_fsname = fs_name;
mnt.mnt_type = "nfs";
mnt.mnt_opts = xopts;
mnt.mnt_freq = 0;
mnt.mnt_passno = 0;
retry = hasmntval(&mnt, "retry");
if (retry <= 0)
retry = 1;
nfs_args.fh = (void *)fhp->fhs_fhandle;
nfs_args.fhsize = fhp->fhs_size;
nfs_args.version = NFS_ARGSVERSION;
nfs_args.hostname = host;
#ifdef HOSTNAMESZ
if (strlen(host) >= HOSTNAMESZ)
strlcpy(host + HOSTNAMESZ - 3, "..", sizeof host - HOSTNAMESZ + 3);
#endif
if ((nfs_args.rsize = hasmntval(&mnt, "rsize")))
nfs_args.flags |= NFSMNT_RSIZE;
#ifdef NFSMNT_READDIRSIZE
if ((nfs_args.readdirsize = hasmntval(&mnt, "readdirsize"))) {
nfs_args.flags |= NFSMNT_READDIRSIZE;
} else if (nfs_args.rsize) {
nfs_args.readdirsize = nfs_args.rsize;
nfs_args.flags |= NFSMNT_READDIRSIZE;
}
#endif
if ((nfs_args.wsize = hasmntval(&mnt, "wsize")))
nfs_args.flags |= NFSMNT_WSIZE;
if ((nfs_args.timeo = hasmntval(&mnt, "timeo")))
nfs_args.flags |= NFSMNT_TIMEO;
if ((nfs_args.retrans = hasmntval(&mnt, "retrans")))
nfs_args.flags |= NFSMNT_RETRANS;
#ifdef NFSMNT_BIODS
if ((nfs_args.biods = hasmntval(&mnt, "biods")))
nfs_args.flags |= NFSMNT_BIODS;
#endif
#ifdef NFSMNT_MAXGRPS
if ((nfs_args.maxgrouplist = hasmntval(&mnt, "maxgroups")))
nfs_args.flags |= NFSMNT_MAXGRPS;
#endif
#ifdef NFSMNT_READAHEAD
if ((nfs_args.readahead = hasmntval(&mnt, "readahead")))
nfs_args.flags |= NFSMNT_READAHEAD;
#endif
#ifdef notdef
if ((port = hasmntval(&mnt, "port")))
sin.sin_port = htons(port);
else
sin.sin_port = htons(NFS_PORT);
#endif
if (hasmntopt(&mnt, "soft") != NULL)
nfs_args.flags |= NFSMNT_SOFT;
#ifdef NFSMNT_SPONGY
if (hasmntopt(&mnt, "spongy") != NULL) {
nfs_args.flags |= NFSMNT_SPONGY;
if (nfs_args.flags & NFSMNT_SOFT) {
plog(XLOG_USER, "Mount opts soft and spongy are incompatible - soft ignored");
nfs_args.flags &= ~NFSMNT_SOFT;
}
}
#endif
if (hasmntopt(&mnt, "intr") != NULL)
nfs_args.flags |= NFSMNT_INT;
#ifdef MNTOPT_NODEVS
if (hasmntopt(&mnt, MNTOPT_NODEVS) != NULL)
nfs_args.flags |= NFSMNT_NODEVS;
#endif
if (hasmntopt(&mnt, "noconn") != NULL)
nfs_args.flags |= NFSMNT_NOCONN;
if (hasmntopt(&mnt, "resvport") != NULL)
nfs_args.flags |= NFSMNT_RESVPORT;
#ifdef NFSMNT_PGTHRESH
if ((nfs_args.pg_thresh = hasmntval(&mnt, "pgthresh")))
nfs_args.flags |= NFSMNT_PGTHRESH;
#endif
nfs_args.addr = (struct sockaddr *)fs->fs_ip;
nfs_args.addrlen = sizeof(*fs->fs_ip);
nfs_args.sotype = SOCK_DGRAM;
nfs_args.proto = 0;
flags = compute_mount_flags(&mnt);
#ifdef NFSMNT_NOCTO
if (hasmntopt(&mnt, "nocto") != NULL)
nfs_args.flags |= NFSMNT_NOCTO;
#endif
if (hasmntopt(&mnt, "tcp") != NULL)
nfs_args.sotype = SOCK_STREAM;
error = mount_fs(&mnt, flags, (caddr_t) &nfs_args, retry, type);
free(xopts);
return error;
}
static int
mount_nfs(char *dir, char *fs_name, char *opts, mntfs *mf)
{
#ifdef notdef
int error;
fhstatus fhs;
char *colon;
if (!(colon = strchr(fs_name, ':')))
return ENOENT;
#ifdef DEBUG
dlog("locating fhandle for %s", fs_name);
#endif
error = prime_nfs_fhandle_cache(colon+1, mf->mf_server, &fhs, NULL);
if (error)
return error;
return mount_nfs_fh(&fhs, dir, fs_name, opts, mf);
#endif
if (!mf->mf_private) {
plog(XLOG_ERROR, "Missing filehandle for %s", fs_name);
return EINVAL;
}
return mount_nfs_fh((fhstatus *) mf->mf_private, dir, fs_name, opts, mf);
}
static int
nfs_fmount(mntfs *mf)
{
int error;
error = mount_nfs(mf->mf_mount, mf->mf_info, mf->mf_mopts, mf);
#ifdef DEBUG
if (error) {
errno = error;
dlog("mount_nfs: %m");
}
#endif
return error;
}
static int
nfs_fumount(mntfs *mf)
{
return (umount_fs(mf->mf_mount));
}
static void
nfs_umounted(am_node *mp)
{
#ifdef KICK_KERNEL
if (mp->am_parent && mp->am_parent->am_path &&
STREQ(mp->am_parent->am_mnt->mf_ops->fs_type, "direct")) {
struct stat stb;
pid_t pid;
if ((pid = background()) == 0) {
if (lstat(mp->am_parent->am_path, &stb) < 0) {
plog(XLOG_ERROR, "lstat(%s) after unmount: %m", mp->am_parent->am_path);
#ifdef DEBUG
} else {
dlog("hack lstat(%s): ok", mp->am_parent->am_path);
#endif
}
_exit(0);
}
}
#endif
}
am_ops nfs_ops = {
"nfs",
nfs_match,
nfs_init,
auto_fmount,
nfs_fmount,
auto_fumount,
nfs_fumount,
efs_lookuppn,
efs_readdir,
0,
0,
nfs_umounted,
find_nfs_srvr,
FS_MKMNT|FS_BACKGROUND|FS_AMQINFO
};
#endif