[comp.protocols.nfs] Avoiding resource waits on unavailable hard mounted fs

G.Eustace@massey.ac.nz (Glen Eustace) (02/14/91)

I am trying to write some code that will determine whether an NFS
mounted partition is currently accessible.  The filesystem is hard
mounted and most file system will hang on a resource wait until such
time as the server is back up again.  As this is part of a larger
program I don't want this to occur.

I want to determine whether an access will hang prior to attempting
to access the file system in order to take some evasive action.

A short time out would be acceptable.  Can anyone suggest a system
call that will allow this.

-- 
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
Glen Eustace, Software Manager, Computer Centre, Massey University,
Palmerston North, New Zealand.        EMail: G.Eustace@massey.ac.nz
Phone: +64 63 69099 x7440, Fax: +64 63 505 607,    Timezone: GMT-12

thurlow@convex.com (Robert Thurlow) (02/14/91)

In <1991Feb14.035507.28805@massey.ac.nz> G.Eustace@massey.ac.nz (Glen Eustace) writes:

>I am trying to write some code that will determine whether an NFS
>mounted partition is currently accessible.  The filesystem is hard
>mounted and most file system will hang on a resource wait until such
>time as the server is back up again.  As this is part of a larger
>program I don't want this to occur.

Our df(1) does this now.  I can't recall where the idea came from, but
Tom Christiansen (tchrist@convex.com) put it into our code.  The idea
is to throw a packet at the null NFS procedure before trying an op
which may hang.

        signal(SIGALRM, timedout);
        alarm(TIMEOUT);

        if(setjmp(env) == 0) {
            stat = callrpc(host, NFS_PROGRAM, NFS_VERSION, RFS_NULL,
                           xdr_void, 0, xdr_void, 0);
            alarm(0);   /* unimportant race condition here */
            if (stat == 0)
                return 0;
        }

After some experience with the automounter, I want to make a change
to the kernel to permit me to set/clear a process flag to say, "I
don't care if this is a hard mount or not, I want NFS operations
to timeout; trust me, I can handle it."  The automounter usually is
a net benefit with dead servers, but if the server dies in the 5
minute window, the automounter can lock and take lots of processes
with it.  It's the most serious bug we have left with it right now,
and probably needs the more aggressive fix.

Rob T
--
Rob Thurlow, thurlow@convex.com
An employee and not a spokesman for Convex Computer Corp., Dallas, TX

geoff@hinode.East.Sun.COM (Geoff Arnold @ Sun BOS - R.H. coast near the top) (02/15/91)

Quoth G.Eustace@massey.ac.nz (Glen Eustace) (in <1991Feb14.035507.28805@massey.ac.nz>):
#I am trying to write some code that will determine whether an NFS
#mounted partition is currently accessible.  The filesystem is hard
#mounted and most file system will hang on a resource wait until such
#time as the server is back up again.  As this is part of a larger
#program I don't want this to occur.
#
#I want to determine whether an access will hang prior to attempting
#to access the file system in order to take some evasive action.

Well, I assume that you know the identity of the server. In that case,
you could code up a simple RPC call to procedure 0 of the NFS program
on the server, setting the timeouts appropriately. If you're going
to do this very often, you probably want to use some of the lower-level
API calls to bypass the portmap call.

Geoff

-- Geoff Arnold, PC-NFS architect, Sun Microsystems. (geoff@East.Sun.COM)   --
------------------------------------------------------------------------------
--                   No cute comments. War isn't cute.                      --
------------------------------------------------------------------------------

ado@elsie.nci.nih.gov (Arthur David Olson) (02/15/91)

> I want to determine whether an access will hang prior to attempting
> to access the file system in order to take some evasive action.
> 
> A short time out would be acceptable.  Can anyone suggest a system
> call that will allow this.

In this neck of the woods, we nfsping the server first and check the
exit status.
-- 
	The Multiprocessor Turing Instruction Set Computer:  TISC-TISC
		Arthur David Olson	ado@elsie.nci.nih.gov
		ADO and Elsie are Ampex and Borden trademarks

richard@aiai.ed.ac.uk (Richard Tobin) (02/15/91)

In article <thurlow.666546557@convex.convex.com> thurlow@convex.com (Robert Thurlow) writes:
>Our df(1) does this now.  I can't recall where the idea came from, 

>        signal(SIGALRM, timedout);
>        alarm(TIMEOUT);
>
>        if(setjmp(env) == 0) {
>            stat = callrpc(host, NFS_PROGRAM, NFS_VERSION, RFS_NULL,
>                           xdr_void, 0, xdr_void, 0);
>            alarm(0);   /* unimportant race condition here */
>            if (stat == 0)
>                return 0;
>        }

This code appears to be from a version of df I posted to Sun-spots
some time ago.  A better version uses explicit timeouts in the
RPC call.  The new code is below.  Note that it assumes the NFS
server is on port 2049 to avoid having to deal with hanging while
waiting for the portmapper.

-- Richard

/* df.c
 * A replacement for df that trys not to hang if a fileserver doesn't respond.
 * Copyright Richard Tobin (R.Tobin@uk.ac.ed) 1989.
 * May be freely redistributed if this comment remains intact.
 */

#include <stdio.h>
#include <sys/types.h>
#include <mntent.h>
#include <sys/vfs.h>
#include <strings.h>
#include <signal.h>
#include <sys/time.h>
#include <rpc/rpc.h>
#include <rpc/clnt.h>
#include <errno.h>
#include <nfs/nfs.h>
#include <setjmp.h>
#include <netdb.h>
#include <sys/socket.h>

extern int errno;

#define MTAB "/etc/mtab"
#define TIMEOUT 5

struct mntent *findmntent();
void printline(), usage();
int nfscheck();

main(argc, argv)
int argc;
char **argv;
{
    int c, aflag=0, iflag=0, tflag=0, errs = 0;
    char *type, *file;
    extern char *optarg;
    extern int optind, opterr;

    while((c = getopt(argc, argv, "ait:")) != EOF)
	switch(c)
	{
	  case 'a':		/* report even boring filesystems */
	    aflag++;
	    break;

	  case 'i':		/* report inode use */
	    iflag++;
	    break;

	  case 't':		/* only report on one type of filesystem */
	    tflag++;
	    type = optarg;
	    break;

	  case '?':
	    usage();
	}

    if(tflag && optind != argc)
	usage();

    if(iflag)
	printf("Filesystem             iused   ifree  %%iused  Mounted on\n");
    else
	printf("Filesystem            kbytes    used   avail capacity  Mounted on\n");

    if(optind < argc)
    {
	/* report on listed filesystems */

	for(file = argv[optind]; optind < argc; file = argv[++optind])
	{
	    struct statfs filebuf;
	    struct mntent *mntbuf;

	    if(statfs(file, &filebuf) != 0)
	    {
		perror(file);
		errs++;
		continue;
	    }

	    if(!aflag && filebuf.f_blocks == 0)
		continue;

	    mntbuf = findmntent(filebuf.f_fsid);
	    if(!mntbuf)
	    {
		fprintf(stderr, "can't find filesystem for %s\n", file);
		errs++;
		continue;
	    }

	    printline(&filebuf, mntbuf, iflag);	    
	}

    }
    else
    {
	/* report on all mounted filesystems */

	struct statfs filebuf;
	struct mntent *mntbuf;
	FILE *f;

	errno = 0;
	f = setmntent(MTAB, "r");
	if(!f)
	{
	    fprintf(stderr, "can't open %s\n", MTAB);
	    if(errno)
		perror("setmntent");
	    exit(1);
	}

	while(mntbuf = getmntent(f))
	{
	    if(tflag && strcmp(mntbuf->mnt_type, type) != 0)
		continue;

	    if(nfscheck(mntbuf) != 0)
	    {
		errs++;
		continue;
	    }

	    if(statfs(mntbuf->mnt_dir, &filebuf) != 0)
	    {
		perror(mntbuf->mnt_dir);
		errs++;
		continue;
	    }

	    if(!aflag && filebuf.f_blocks == 0)
                continue;

	    printline(&filebuf, mntbuf, iflag);
	}

	endmntent(f);
    }
    
    exit(errs > 0 ? 1 : 0);
}

/* findmntent finds the filesystem in mtab with the specified id. */

struct mntent *findmntent(id)
fsid_t id;
{
    FILE *f;

    struct mntent *fs;

    errno = 0;
    f = setmntent(MTAB, "r");
    if(!f)
    {
	fprintf(stderr, "can't open %s\n", MTAB);
	if(errno)
	    perror("setmntent");
	exit(1);
    }

    while(fs = getmntent(f))
    {
	struct statfs buf;

	if(nfscheck(fs) != 0)
	    continue;

	if(statfs(fs->mnt_dir, &buf) != 0)
	    continue;
	if(bcmp(&buf.f_fsid, &id, sizeof(fsid_t)) == 0)
	{
	    endmntent(f);
	    return fs;
	}
    }

    endmntent(f);
    return (struct mntent *)0;
}

void printline(buf, mntbuf, iflag)
struct statfs *buf;
struct mntent *mntbuf;
int iflag;
{
    printf("%-20s", mntbuf->mnt_fsname);
    if(strlen(mntbuf->mnt_fsname) > 20)
	printf("\n%20s", "");

    if(iflag)
	printf(" %7d %7d   %3d%%   %s\n",
	       buf->f_files - buf->f_ffree,
	       buf->f_ffree,
	       ((buf->f_files - buf->f_ffree) * 100 + buf->f_files / 2) / buf->f_files,
	       mntbuf->mnt_dir);
    else
	printf(" %7d %7d %7d   %3d%%    %s\n",
	       (buf->f_blocks * buf->f_bsize + 512) / 1024,
	       ((buf->f_blocks - buf->f_bfree) * buf->f_bsize + 512) / 1024,
	       (buf->f_bavail * buf->f_bsize + 512) / 1024,
	       ((buf->f_blocks - buf->f_bfree) * 100 + buf->f_blocks / 2) / ((buf->f_blocks * 9)/10),
	       mntbuf->mnt_dir);
}

void usage()
{
    fprintf(stderr, "usage: df [ -i ] [ -a ] [ -t type | file... ]\n");
    exit(2);
}

static jmp_buf env;

/*
 * nfscheck checks that the host supplying the filesystem described
 * by mntbuf is responding, by calling the null nfs procedure.
 */

int nfscheck(mntbuf)
struct mntent *mntbuf;
{
    int stat, sock= RPC_ANYSOCK;
    char host[50], *p;
    struct hostent *server;
    struct sockaddr_in sin;
    CLIENT *client;
    struct timeval try, total;

    if(strcmp(mntbuf->mnt_type, "nfs") != 0)
	return 0;

    p = index(mntbuf->mnt_fsname, ':');
    if(!p || p - mntbuf->mnt_fsname >= sizeof(host))
	return 0;

    strncpy(host, mntbuf->mnt_fsname, p - mntbuf->mnt_fsname);
    host[p - mntbuf->mnt_fsname] = '\0';

    server = gethostbyname(host);
    if(!server)
    {
	fprintf(stderr, "%s: unknown host %s\n", mntbuf->mnt_dir, host);
	return -1;
    }

    bcopy(server->h_addr, &sin.sin_addr, server->h_length);
    sin.sin_family = AF_INET;
    sin.sin_port = 2049;	/* avoid calling portmapper */

    try.tv_sec = 1;
    try.tv_usec = 0;

    client = clntudp_create(&sin, NFS_PROGRAM, NFS_VERSION,
			    try, &sock);
    if(!client)
    {
	fprintf(stderr, "%s: can't create connection to %s\n",
	       mntbuf->mnt_dir, host);
	return -1;
    }

    total.tv_sec = TIMEOUT;
    total.tv_usec = 0;

    stat = clnt_call(client, RFS_NULL, xdr_void, 0, xdr_void, 0, total);

    clnt_destroy(client);

    if(stat != RPC_SUCCESS)
    {
	fprintf(stderr, "%s: nfs server %s not responding\n",
		mntbuf->mnt_dir, host);
	return -1;
    }
    return 0;
}



-- 
Richard Tobin,                       JANET: R.Tobin@uk.ac.ed             
AI Applications Institute,           ARPA:  R.Tobin%uk.ac.ed@nsfnet-relay.ac.uk
Edinburgh University.                UUCP:  ...!ukc!ed.ac.uk!R.Tobin

G.Eustace@massey.ac.nz (Glen Eustace) (02/18/91)

My thanks to those responding to this query.  The most sensible
solution seems to be to attempt an RPC operation against the NULL
procedure in one of the nfs daemons on the server in question.

I liked this approach and it ought to work in theory, but I am having
a bit of difficulty in practice.  The program in question, is itself
an rpc daemon.  It is checking another server as part of performing a
service for a PC.  Hence the rpc code in the daemon is being executed
within a routine which is itself an rpc service.

The problem I am experiencing is as follows.  If the target server is
up and running everything works fine.  I query the NULL proc, which
succeeds almost instantly and carry on constructing the reply to the
original request.  However, if the server is not there, the original
request times out and then the target query completes, even though
the timeouts are set so that this should not occur.  It would appear
that the failure of the nested call somehow interfers with the other.

Can anyone shed any light on this behaviour ?

-- 
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
Glen Eustace, Software Manager, Computer Centre, Massey University,
Palmerston North, New Zealand.        EMail: G.Eustace@massey.ac.nz
Phone: +64 63 69099 x7440, Fax: +64 63 505 607,    Timezone: GMT-12