statbug.c: Spot a BUG with NFS stat() - please try this. Esp 386/ix!

Guy Harris guy at auspex.auspex.com
Sun Mar 11 08:51:42 AEST 1990


>	On my system the st_dev number returned is negative; clearly a load of
>	garbage.

There is no reason why a negative "st_dev" is necessarily "a load of
garbage".  For an extreme example, consider a system with the
(currently-standard) 8 bits of major and 8 bits of minor device number,
and with 129 different block device drivers....

Many UNIX NFS implementations choose negative "st_dev" values for
remotely-mounted file systems (NFS or RFS), simply to avoid colliding
with values for local file systems.

The problem with "ed" is probably that "ustat()", while working
*perfectly correctly* with those allegedly-"garbage" "st_dev" values,
may not be returning proper data for an NFS-mounted file system. 
Another problem may be that it can't cope with "st_dev" values for file
systems other than local ones, or for NFS file systems in particular.

It certainly *is* possible to have "st_dev" return negative values for
NFS-mounted file systems *and* to have "ustat()" work just fine with
NFS-mounted file systems - SunOS has done so since SunOS 3.2.  The
following test program can be used to see how well "ustat()" works.  It
takes one argument, which should be the path name of a file (any file or
directory) on the file system on which you want to test this, and prints
out the "ustat" information for that file system.

To check its answers, do a "df" on the file system in question and
compare the results.  NOTE: beware of the units that "ustat" may be
using.  UNIX software tends to assume that the "f_tfree" figure is in
units of 512-byte blocks.  As such, if the program reports a figure that
does *not* appear to be in those units, the UNIX implementation on which
it's running arguably has a bug or misfeature.  The SVID - even the S5R4
Third Edition - says only that it's the number of "total free blocks",
and doesn't indicate whether this means:

	sectors;
	512-byte "blocks", even if your system has 1024-byte sectors
	    (yes, there really *are* systems that have 1K sectors rather
	    than 512-byte sectors);
	the native block size of the file system - or, in file systems
	    with two "block" sizes, which one this is; for instance, is
	    is the "block" size or the "fragment" size in a BSD file
	    system?

and it is quite possible that ISC, or somebody, chose some meaning other
than the one that will, as indicated, make "ed" happy, namely "512-byte
'blocks'".  (The S5R3.1 "ed" - and, I think, most "ed"s prior to that,
and probably the S5R3.2 one as well - shift the file size right by 9
bits before comparing it with the "f_tfree", so that "f_tfree" must be
in units of 512-byte chunks for this to work.)

It is also possible that they misimplemented the way "ustat" on an NFS
file system deals with the results of the STATFS call required for
"ustat".  What it should do is multiply "bavail" (since that's the
number of bytes that non-privileged users can use - "bfree" is the
number of blocks "free", but in e.g. the BSD file system, only the
super-user can use all of them; other users can use only "bavail" of
them) by "bsize" and divide the result by 512 (or, alternatively,
multiply it by "bsize/512").

(Yes, I know, NFSSRC4.0 doesn't quite do that.  *Mea culpa.* 512 should
be used instead of DEV_BSIZE; the comment in the code notes that AT&T
was, shall we say, less-than-careful in specifying what the units
actually were.  Doing the multiplication before the division may be
risky, too, if it can overflow; if you can have a file system with > 2
gigabyte on it, this is a potential problem.)

The test program (which works just fine in SunOS 4.0.3) follows.  It may
have to be compiled in the "System V environment" on systems that have
multiple environments (e.g., compile it with "/usr/5bin/cc" on SunOS). 
I've only compiled it under SunOS, so I don't know whether it'll work on
a "normal" S5 system.

NOTE: the current NFS protocol has no way of telling a client how many
"file slots" - e.g., inodes - are free on a file system.  As such, the
"f_tinode" number will be bogus on an NFS file system; "ed" doesn't use
that, though.

----------------------------------Cut Here-------------------------------------
#include <stdio.h>
#include <sys/types.h>
#include <sys/sysmacros.h>
#include <sys/stat.h>
#include <ustat.h>
#include <errno.h>

/*
 * If you have the ANSI C "strerror" routine, toss this declaration and
 * the later definition out.
 */
static char *strerror(/*int errnum*/);

int
main(argc, argv)
	int argc;
	char **argv;
{
	struct stat statb;
	struct ustat ustatb;

	if (argc != 2) {
		(void) fprintf(stderr, "Usage: ustattst <file>\n");
		return 1;
	}

	if (stat(argv[1], &statb) < 0) {
		(void) fprintf(stderr,
		    "ustattst: Can't stat %s: %s\n", argv[1], strerror(errno));
		return 2;
	}

	if (ustat(statb.st_dev, &ustatb) < 0) {
		(void) fprintf(stderr,
		    "ustattst: Can't do ustat on (%d, %d): %s\n",
		    major(statb.st_dev), minor(statb.st_dev), strerror(errno));
		return 2;
	}

	(void) printf("tfree %ld tinode %ld fname %.6s fpack %.6s\n",
	    ustatb.f_tfree, ustatb.f_tinode, ustatb.f_fname,
	    ustatb.f_fpack);

	return 0;
}

extern int sys_nerr;
extern char *sys_errlist[];

static char *
strerror(errnum)
	int errnum;
{
	static char errbuf[5+1+10+1];	/* "Error %d\0" */
	
	if (errnum > 0 && errnum < sys_nerr)
		return sys_errlist[errnum];
	else {
		(void) sprintf(errbuf, "Error %d", errnum);
		return errbuf;
	}
}



More information about the Comp.unix.i386 mailing list