* Copyright (c) 1990 Jan-Simon Pendry
* Copyright (c) 1990 Imperial College of Science, Technology & Medicine
* Copyright (c) 1990, 1993
* 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
* 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. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. 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
* @(#)srvr_nfs.c 8.1 (Berkeley) 6/6/93
* $Id: srvr_nfs.c,v 5.2.2.1 1992/02/09 15:09:06 jsp beta $
#include <rpc/pmap_prot.h>
extern qelem nfs_srvr_list
;
qelem nfs_srvr_list
= { &nfs_srvr_list
, &nfs_srvr_list
};
typedef struct nfs_private
{
u_short np_mountd
; /* Mount daemon port number */
char np_mountd_inval
; /* Port *may* be invalid */
int np_ping
; /* Number of failed ping attempts */
time_t np_ttl
; /* Time when server is thought dead */
int np_xid
; /* RPC transaction id for pings */
int np_error
; /* Error during portmap request */
static int np_xid
; /* For NFS pings */
#define NPXID_ALLOC() (++np_xid)
/*#define NPXID_ALLOC() ((++np_xid&0x0fffffff) == 0 ? npxid_gc() : np_xid)*/
* Number of pings allowed to fail before host is declared down
* - three-fifths of the allowed mount time...
#define MAX_ALLOWED_PINGS ((((ALLOWED_MOUNT_TIME + 5 * AM_PINGER - 1) * 3) / 5) / AM_PINGER)
#define MAX_ALLOWED_PINGS (3 + /* for luck ... */ 1)
* How often to ping when starting a new server
#if (FAST_NFS_PING * MAX_ALLOWED_PINGS) >= ALLOWED_MOUNT_TIME
#error: sanity check failed
you cannot do things this way...
sufficient fast pings must be given the chance to fail
within the allowed mount time
#endif /* (FAST_NFS_PING * MAX_ALLOWED_PINGS) >= ALLOWED_MOUNT_TIME */
static char ping_buf
[sizeof(struct rpc_msg
) + 32];
void flush_srvr_nfs_cache
P((void));
void flush_srvr_nfs_cache()
ITER(fs
, fserver
, &nfs_srvr_list
) {
nfs_private
*np
= (nfs_private
*) fs
->fs_private
;
np
->np_mountd_inval
= TRUE
;
static void start_ping(P_void
);
rpc_msg_init(&ping_msg
, NFS_PROGRAM
, NFS_VERSION
, NFSPROC_NULL
);
xdrmem_create(&ping_xdr
, ping_buf
, sizeof(ping_buf
), XDR_ENCODE
);
* Create the NFS ping message
if (!xdr_callmsg(&ping_xdr
, &ping_msg
)) {
plog(XLOG_ERROR
, "Couldn't create ping RPC message");
* Find out how long it is
ping_len
= xdr_getpos(&ping_xdr
);
* Destroy the XDR endpoint - we don't need it anymore
* Called when a portmap reply arrives
static void got_portmap
P((voidp pkt
, int len
, struct sockaddr_in
*sa
, struct sockaddr_in
*ia
, voidp idv
, int done
));
static void got_portmap(pkt
, len
, sa
, ia
, idv
, done
)
fserver
*fs2
= (fserver
*) idv
;
* Find which fileserver we are talking about
ITER(fs
, fserver
, &nfs_srvr_list
)
u_long port
= 0; /* XXX - should be short but protocol is naff */
int error
= done
? pickup_rpc_reply(pkt
, len
, (voidp
) &port
, xdr_u_long
) : -1;
nfs_private
*np
= (nfs_private
*) fs
->fs_private
;
dlog("got port (%d) for mountd on %s", port
, fs
->fs_host
);
* Grab the port number. Portmap sends back
* an unsigned long in native ordering, so it
* needs converting to a unsigned short in
np
->np_mountd
= htons((u_short
) port
);
np
->np_mountd_inval
= FALSE
;
dlog("Error fetching port for mountd on %s", fs
->fs_host
);
* Almost certainly no mountd running on remote host
np
->np_error
= error
? error
: ETIMEDOUT
;
if (fs
->fs_flags
& FSF_WANT
)
dlog("Got portmap for old port request");
dlog("portmap request timed out");
* Obtain portmap information
static int call_portmap
P((fserver
*fs
, AUTH
*auth
, unsigned long prog
, unsigned long vers
, unsigned long prot
));
static int call_portmap(fs
, auth
, prog
, vers
, prot
)
unsigned long prog
, vers
, prot
;
rpc_msg_init(&pmap_msg
, PMAPPROG
, PMAPVERS
, (unsigned long) 0);
len
= make_rpc_packet(iobuf
, sizeof(iobuf
), PMAPPROC_GETPORT
,
&pmap_msg
, (voidp
) &pmap
, xdr_pmap
, auth
);
bzero((voidp
) &sin
, sizeof(sin
));
sin
.sin_port
= htons(PMAPPORT
);
error
= fwd_packet(RPC_XID_PORTMAP
, (voidp
) iobuf
, len
,
&sin
, &sin
, (voidp
) fs
, got_portmap
);
static void nfs_keepalive
P((fserver
*));
static void recompute_portmap
P((fserver
*fs
));
static void recompute_portmap(fs
)
nfs_private
*np
= (nfs_private
*) fs
->fs_private
;
call_portmap(fs
, nfs_auth
, MOUNTPROG
,
MOUNTVERS
, (unsigned long) IPPROTO_UDP
);
* This is called when we get a reply to an RPC ping.
* The value of id was taken from the nfs_private
* structure when the ping was transmitted.
static void nfs_pinged
P((voidp pkt
, int len
, struct sockaddr_in
*sp
, struct sockaddr_in
*tsp
, voidp idv
, int done
));
static void nfs_pinged(pkt
, len
, sp
, tsp
, idv
, done
)
ITER(fs
, fserver
, &nfs_srvr_list
) {
nfs_private
*np
= (nfs_private
*) fs
->fs_private
;
* Reset the ping counter.
* Update the keepalive timer.
if (fs
->fs_flags
& FSF_DOWN
) {
fs
->fs_flags
&= ~FSF_DOWN
;
if (fs
->fs_flags
& FSF_VALID
) {
srvrlog(fs
, "starts up");
fs
->fs_flags
|= FSF_VALID
;
if (fs
->fs_flags
& FSF_WANT
)
if (fs
->fs_flags
& FSF_VALID
) {
dlog("file server %s type nfs is still up", fs
->fs_host
);
fs
->fs_flags
|= FSF_VALID
;
fs
->fs_cid
= timeout(fs
->fs_pinger
, nfs_keepalive
, (voidp
) fs
);
* Update ttl for this server
np
->np_ttl
= clocktime() +
(MAX_ALLOWED_PINGS
- 1) * FAST_NFS_PING
+ fs
->fs_pinger
- 1;
np
->np_xid
= NPXID_ALLOC();
* Failed pings is zero...
* Recompute portmap information if not known
dlog("Spurious ping packet");
* Called when no ping-reply received
static void nfs_timed_out
P((fserver
*fs
));
static void nfs_timed_out(fs
)
nfs_private
*np
= (nfs_private
*) fs
->fs_private
;
* Another ping has failed
* Not known to be up any longer
fs
->fs_flags
&= ~FSF_VALID
;
srvrlog(fs
, "not responding");
* If ttl has expired then guess that it is dead
if (np
->np_ttl
< clocktime()) {
int oflags
= fs
->fs_flags
;
if ((fs
->fs_flags
& FSF_DOWN
) == 0) {
* Server was up, but is now down.
fs
->fs_flags
|= FSF_DOWN
|FSF_VALID
;
* Since the server is down, the portmap
* information may now be wrong, so it
* must be flushed from the local cache
flush_nfs_fhandle_cache(fs
);
* Pretend just one ping has failed now
if ((fs
->fs_flags
& FSF_VALID
) == 0)
srvrlog(fs
, "starts down");
fs
->fs_flags
|= FSF_VALID
;
if (oflags
!= fs
->fs_flags
&& (fs
->fs_flags
& FSF_WANT
))
dlog("%d pings to %s failed - at most %d allowed", np
->np_ping
, fs
->fs_host
, MAX_ALLOWED_PINGS
);
* Keep track of whether a server is alive
static void nfs_keepalive
P((fserver
*fs
));
static void nfs_keepalive(fs
)
nfs_private
*np
= (nfs_private
*) fs
->fs_private
;
* Send an NFS ping to this node
error
= fwd_packet(MK_RPC_XID(RPC_XID_NFSPING
, np
->np_xid
), (voidp
) ping_buf
,
ping_len
, fs
->fs_ip
, (struct sockaddr_in
*) 0, (voidp
) np
->np_xid
, nfs_pinged
);
* See if a hard error occured
np
->np_ping
= MAX_ALLOWED_PINGS
; /* immediately down */
* This causes an immediate call to nfs_timed_out
* whenever the server was thought to be up.
dlog("Sent NFS ping to %s", fs
->fs_host
);
/*dlog("keepalive, ping = %d", np->np_ping);*/
* Back off the ping interval if we are not getting replies and
* the remote system is know to be down.
switch (fs
->fs_flags
& (FSF_DOWN
|FSF_VALID
)) {
if (fstimeo
< 0) /* +++ see above */
case FSF_VALID
|FSF_DOWN
: /* Down */
dlog("NFS timeout in %d seconds", fstimeo
);
fs
->fs_cid
= timeout(fstimeo
, nfs_timed_out
, (voidp
) fs
);
int nfs_srvr_port
P((fserver
*fs
, u_short
*port
, voidp wchan
));
int nfs_srvr_port(fs
, port
, wchan
)
if ((fs
->fs_flags
& FSF_VALID
) == FSF_VALID
) {
if ((fs
->fs_flags
& FSF_DOWN
) == 0) {
nfs_private
*np
= (nfs_private
*) fs
->fs_private
;
* Now go get the port mapping again in case it changed.
* Note that it is used even if (np_mountd_inval)
* is True. The flag is used simply as an
* indication that the mountd may be invalid, not
* that it is known to be invalid.
np
->np_mountd_inval
= TRUE
;
if (error
< 0 && wchan
&& !(fs
->fs_flags
& FSF_WANT
)) {
* If a wait channel is supplied, and no
* error has yet occured, then arrange
* that a wakeup is done on the wait channel,
* whenever a wakeup is done on this fs node.
* Wakeup's are done on the fs node whenever
* it changes state - thus causing control to
* come back here and new, better things to happen.
fs
->fs_flags
|= FSF_WANT
;
sched_task(wakeup_task
, wchan
, (voidp
) fs
);
static void start_nfs_pings
P((fserver
*fs
, int pingval
));
static void start_nfs_pings(fs
, pingval
)
if (!(fs
->fs_flags
& FSF_PINGING
)) {
fs
->fs_flags
|= FSF_PINGING
;
fs
->fs_flags
|= FSF_VALID
;
fs
->fs_flags
&= ~FSF_DOWN
;
dlog("Already running pings to %s", fs
->fs_host
);
* Find an nfs server for a host.
fserver
*find_nfs_srvr
P((mntfs
*mf
));
fserver
*find_nfs_srvr(mf
)
char *host
= mf
->mf_fo
->opt_rhost
;
* Get ping interval from mount options.
* Current only used to decide whether pings
* are required or not. < 0 = no pings.
mnt
.mnt_opts
= mf
->mf_mopts
;
pingval
= hasmntval(&mnt
, "ping");
* Over TCP mount, don't bother to do pings.
* This is experimental - maybe you want to
if (pingval
== 0 && hasmntopt(&mnt
, "tcp"))
* lookup host address and canonical name
hp
= gethostbyname(host
);
* New code from Bob Harris <harris@basil-rathbone.mit.edu>
* Use canonical name to keep track of file server
* information. This way aliases do not generate
* multiple NFS pingers. (Except when we're normalizing
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
);
* Get here if we can't find an entry
switch (hp
->h_addrtype
) {
bzero((voidp
) ip
, sizeof(*ip
));
ip
->sin_family
= AF_INET
;
bcopy((voidp
) hp
->h_addr
, (voidp
) &ip
->sin_addr
, sizeof(ip
->sin_addr
));
ip
->sin_port
= htons(NFS_PORT
);
plog(XLOG_USER
, "Unknown host: %s", host
);
fs
->fs_host
= strdup(hp
? hp
->h_name
: "unknown_hostname");
if (normalize_hosts
) host_normalize(&fs
->fs_host
);
fs
->fs_flags
= FSF_DOWN
; /* Starts off down */
fs
->fs_flags
= FSF_ERROR
|FSF_VALID
;
mf
->mf_flags
|= MFF_ERROR
;
fs
->fs_pinger
= AM_PINGER
;
bzero((voidp
) np
, sizeof(*np
));
np
->np_mountd_inval
= TRUE
;
np
->np_xid
= NPXID_ALLOC();
* Initially the server will be deemed dead after
* MAX_ALLOWED_PINGS of the fast variety have failed.
np
->np_ttl
= clocktime() + MAX_ALLOWED_PINGS
* FAST_NFS_PING
- 1;
fs
->fs_private
= (voidp
) np
;
fs
->fs_prfree
= (void (*)()) free
;
if (!(fs
->fs_flags
& FSF_ERROR
)) {
* Start of keepalive timer
start_nfs_pings(fs
, pingval
);
ins_que(&fs
->fs_q
, &nfs_srvr_list
);