#include "am.h"
#include <netdb.h>
#include <rpc/pmap_prot.h>
#include "mount.h"
extern qelem nfs_srvr_list;
qelem nfs_srvr_list = { &nfs_srvr_list, &nfs_srvr_list };
typedef struct nfs_private {
u_short np_mountd;
char np_mountd_inval;
int np_ping;
time_t np_ttl;
int np_xid;
int np_error;
} nfs_private;
static int np_xid;
#define NPXID_ALLOC() (++np_xid)
#define MAX_ALLOWED_PINGS (3 + 1)
#define FAST_NFS_PING 3
#if (FAST_NFS_PING * MAX_ALLOWED_PINGS) >= ALLOWED_MOUNT_TIME
#error: sanity check failed
#endif
static int ping_len;
static char ping_buf[sizeof(struct rpc_msg) + 32];
void
flush_srvr_nfs_cache(void)
{
fserver *fs = 0;
ITER(fs, fserver, &nfs_srvr_list) {
nfs_private *np = (nfs_private *) fs->fs_private;
if (np) {
np->np_mountd_inval = TRUE;
np->np_error = -1;
}
}
}
static void
start_ping(void)
{
XDR ping_xdr;
struct rpc_msg ping_msg;
rpc_msg_init(&ping_msg, NFS_PROGRAM, NFS_VERSION, NFSPROC_NULL);
xdrmem_create(&ping_xdr, ping_buf, sizeof(ping_buf), XDR_ENCODE);
if (!xdr_callmsg(&ping_xdr, &ping_msg)) {
plog(XLOG_ERROR, "Couldn't create ping RPC message");
going_down(3);
}
ping_len = xdr_getpos(&ping_xdr);
xdr_destroy(&ping_xdr);
}
static void
got_portmap(void *pkt, int len, struct sockaddr_in *sa,
struct sockaddr_in *ia, void *idv, int done)
{
fserver *fs2 = (fserver *) idv;
fserver *fs = 0;
ITER(fs, fserver, &nfs_srvr_list)
if (fs == fs2)
break;
if (fs == fs2) {
u_long port = 0;
int error = done ? pickup_rpc_reply(pkt, len, &port, xdr_u_long) : -1;
nfs_private *np = (nfs_private *) fs->fs_private;
if (!error && port) {
#ifdef DEBUG
dlog("got port (%d) for mountd on %s", port, fs->fs_host);
#endif
np->np_mountd = htons((u_short) port);
np->np_mountd_inval = FALSE;
np->np_error = 0;
} else {
#ifdef DEBUG
dlog("Error fetching port for mountd on %s", fs->fs_host);
#endif
np->np_error = error ? error : ETIMEDOUT;
}
if (fs->fs_flags & FSF_WANT)
wakeup_srvr(fs);
} else if (done) {
#ifdef DEBUG
dlog("Got portmap for old port request");
#endif
} else {
#ifdef DEBUG
dlog("portmap request timed out");
#endif
}
}
static int
call_portmap(fserver *fs, AUTH *auth, unsigned long prog,
unsigned long vers, unsigned long prot)
{
struct rpc_msg pmap_msg;
int len;
char iobuf[UDPMSGSIZE];
int error;
struct pmap pmap;
rpc_msg_init(&pmap_msg, PMAPPROG, PMAPVERS, (unsigned long) 0);
pmap.pm_prog = prog;
pmap.pm_vers = vers;
pmap.pm_prot = prot;
pmap.pm_port = 0;
len = make_rpc_packet(iobuf, sizeof(iobuf), PMAPPROC_GETPORT,
&pmap_msg, &pmap, xdr_pmap, auth);
if (len > 0) {
struct sockaddr_in sin;
bzero(&sin, sizeof(sin));
sin = *fs->fs_ip;
sin.sin_port = htons(PMAPPORT);
error = fwd_packet(RPC_XID_PORTMAP, iobuf, len,
&sin, &sin, fs, got_portmap);
} else {
error = -len;
}
return error;
}
static void nfs_keepalive(void *);
static void
recompute_portmap(fserver *fs)
{
int error;
if (nfs_auth)
error = 0;
else
error = make_nfs_auth();
if (error) {
nfs_private *np = (nfs_private *) fs->fs_private;
np->np_error = error;
} else {
call_portmap(fs, nfs_auth, MOUNTPROG,
MOUNTVERS, (unsigned long) IPPROTO_UDP);
}
}
static void
nfs_pinged(void *pkt, int len, struct sockaddr_in *sp,
struct sockaddr_in *tsp, void *idv, int done)
{
int xid = (int) ((long)idv);
fserver *fs;
#ifdef DEBUG
int found_map = 0;
#endif
if (!done)
return;
ITER(fs, fserver, &nfs_srvr_list) {
nfs_private *np = (nfs_private *) fs->fs_private;
if (np->np_xid == xid) {
if (fs->fs_flags & FSF_DOWN) {
fs->fs_flags &= ~FSF_DOWN;
if (fs->fs_flags & FSF_VALID) {
srvrlog(fs, "is up");
} else {
if (np->np_ping > 1)
srvrlog(fs, "ok");
#ifdef DEBUG
else
srvrlog(fs, "starts up");
#endif
fs->fs_flags |= FSF_VALID;
}
#ifdef notdef
if (fs->fs_flags & FSF_WANT)
wakeup_srvr(fs);
#endif
map_flush_srvr(fs);
} else {
if (fs->fs_flags & FSF_VALID) {
#ifdef DEBUG
dlog("file server %s type nfs is still up", fs->fs_host);
#endif
} else {
if (np->np_ping > 1)
srvrlog(fs, "ok");
fs->fs_flags |= FSF_VALID;
}
}
untimeout(fs->fs_cid);
fs->fs_cid = timeout(fs->fs_pinger, nfs_keepalive, fs);
np->np_ttl = clocktime() +
(MAX_ALLOWED_PINGS - 1) * FAST_NFS_PING + fs->fs_pinger - 1;
np->np_xid = NPXID_ALLOC();
np->np_ping = 0;
if (np->np_mountd_inval)
recompute_portmap(fs);
#ifdef DEBUG
found_map++;
#endif
break;
}
}
#ifdef DEBUG
if (found_map == 0)
dlog("Spurious ping packet");
#endif
}
static void
nfs_timed_out(void *arg)
{
fserver *fs = arg;
nfs_private *np = (nfs_private *) fs->fs_private;
np->np_ping++;
if (FSRV_ISUP(fs)) {
fs->fs_flags &= ~FSF_VALID;
if (np->np_ping > 1)
srvrlog(fs, "not responding");
}
if (np->np_ttl < clocktime()) {
int oflags = fs->fs_flags;
if ((fs->fs_flags & FSF_DOWN) == 0) {
srvrlog(fs, "is down");
fs->fs_flags |= FSF_DOWN|FSF_VALID;
flush_nfs_fhandle_cache(fs);
np->np_error = -1;
#ifdef notdef
np->np_ping = 1;
#endif
} else {
#ifdef DEBUG
if ((fs->fs_flags & FSF_VALID) == 0)
srvrlog(fs, "starts down");
#endif
fs->fs_flags |= FSF_VALID;
}
if (oflags != fs->fs_flags && (fs->fs_flags & FSF_WANT))
wakeup_srvr(fs);
} else {
#ifdef DEBUG
if (np->np_ping > 1)
dlog("%d pings to %s failed - at most %d allowed", np->np_ping, fs->fs_host, MAX_ALLOWED_PINGS);
#endif
}
nfs_keepalive(fs);
}
static void
nfs_keepalive(void *arg)
{
fserver *fs = arg;
int error;
nfs_private *np = (nfs_private *) fs->fs_private;
int fstimeo = -1;
if (ping_len == 0)
start_ping();
error = fwd_packet(MK_RPC_XID(RPC_XID_NFSPING, np->np_xid), ping_buf,
ping_len, fs->fs_ip, NULL, (void *)((long)np->np_xid),
nfs_pinged);
switch (error) {
case ENETDOWN:
case ENETUNREACH:
case EHOSTDOWN:
case EHOSTUNREACH:
np->np_ping = MAX_ALLOWED_PINGS;
np->np_ttl = (time_t) 0;
fstimeo = 0;
break;
case 0:
#ifdef DEBUG
dlog("Sent NFS ping to %s", fs->fs_host);
#endif
break;
}
#ifdef DEBUG
#endif
switch (fs->fs_flags & (FSF_DOWN|FSF_VALID)) {
case FSF_VALID:
if (fstimeo < 0)
fstimeo = FAST_NFS_PING;
break;
case FSF_VALID|FSF_DOWN:
fstimeo = fs->fs_pinger;
break;
default:
fstimeo = FAST_NFS_PING;
break;
}
#ifdef DEBUG
dlog("NFS timeout in %d seconds", fstimeo);
#endif
fs->fs_cid = timeout(fstimeo, nfs_timed_out, fs);
}
int
nfs_srvr_port(fserver *fs, u_short *port, void *wchan)
{
int error = -1;
if ((fs->fs_flags & FSF_VALID) == FSF_VALID) {
if ((fs->fs_flags & FSF_DOWN) == 0) {
nfs_private *np = (nfs_private *) fs->fs_private;
if (np->np_error == 0) {
*port = np->np_mountd;
error = 0;
} else {
error = np->np_error;
}
if (np->np_mountd_inval)
recompute_portmap(fs);
else
np->np_mountd_inval = TRUE;
} else {
error = EWOULDBLOCK;
}
}
if (error < 0 && wchan && !(fs->fs_flags & FSF_WANT)) {
fs->fs_flags |= FSF_WANT;
sched_task(wakeup_task, wchan, fs);
}
return error;
}
static void
start_nfs_pings(fserver *fs, int pingval)
{
if (!(fs->fs_flags & FSF_PINGING)) {
fs->fs_flags |= FSF_PINGING;
if (fs->fs_cid)
untimeout(fs->fs_cid);
if (pingval < 0) {
srvrlog(fs, "wired up");
fs->fs_flags |= FSF_VALID;
fs->fs_flags &= ~FSF_DOWN;
} else {
nfs_keepalive(fs);
}
} else {
#ifdef DEBUG
dlog("Already running pings to %s", fs->fs_host);
#endif
}
}
fserver *
find_nfs_srvr(mntfs *mf)
{
fserver *fs;
struct hostent *hp = 0;
char *host = mf->mf_fo->opt_rhost;
struct sockaddr_in *ip;
nfs_private *np;
int pingval;
{ struct mntent mnt;
mnt.mnt_opts = mf->mf_mopts;
pingval = hasmntval(&mnt, "ping");
if (pingval == 0 && hasmntopt(&mnt, "tcp"))
pingval = -1;
}
hp = gethostbyname(host);
if (hp && !normalize_hosts) host = hp->h_name;
ITER(fs, fserver, &nfs_srvr_list) {
if (STREQ(host, fs->fs_host)) {
start_nfs_pings(fs, pingval);
fs->fs_refc++;
return fs;
}
}
if (hp) {
switch (hp->h_addrtype) {
case AF_INET:
ip = ALLOC(sockaddr_in);
bzero(ip, sizeof(*ip));
ip->sin_family = AF_INET;
bcopy(hp->h_addr, &ip->sin_addr, sizeof(ip->sin_addr));
ip->sin_port = htons(NFS_PORT);
break;
default:
ip = 0;
break;
}
} else {
plog(XLOG_USER, "Unknown host: %s", host);
ip = 0;
}
fs = ALLOC(fserver);
fs->fs_refc = 1;
fs->fs_host = strdup(hp ? hp->h_name : "unknown_hostname");
if (normalize_hosts) host_normalize(&fs->fs_host);
fs->fs_ip = ip;
fs->fs_cid = 0;
if (ip) {
fs->fs_flags = FSF_DOWN;
} else {
fs->fs_flags = FSF_ERROR|FSF_VALID;
mf->mf_flags |= MFF_ERROR;
mf->mf_error = ENOENT;
}
fs->fs_type = "nfs";
fs->fs_pinger = AM_PINGER;
np = ALLOC(nfs_private);
bzero(np, sizeof(*np));
np->np_mountd_inval = TRUE;
np->np_xid = NPXID_ALLOC();
np->np_error = -1;
np->np_ttl = clocktime() + MAX_ALLOWED_PINGS * FAST_NFS_PING - 1;
fs->fs_private = np;
fs->fs_prfree = free;
if (!(fs->fs_flags & FSF_ERROR)) {
start_nfs_pings(fs, pingval);
}
ins_que(&fs->fs_q, &nfs_srvr_list);
return fs;
}