#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	README.nokidding
#	makefile
#	nfslex.l
#	nfsstuff.h
#	nfstrace.8
#	nfstrace.c
#	nfsyacc.y
#	nit.c
#	pfilt.c
#	rpcspy.8
#	rpcspy.c
#	rpcspy.h
#	xdr_nfs.c
# This archive created: Fri Feb  7 23:20:09 1992
export PATH; PATH=/bin:/usr/bin:$PATH
if test -f 'README.nokidding'
then
	echo shar: "will not over-write existing file 'README.nokidding'"
else
cat << \SHAR_EOF > 'README.nokidding'
Hello.

This is the rpcspy/nfstrace package.

It is described in detail in the paper "NFS Tracing by Passive Network
Monitoring", which appeared in the January, 1992 USENIX conference.

You'll need either a DEC machine running ULTRIX (with the
packetfilter installed in the kernel) or a Sun running SunOS
4.x (with NIT).  Or you'll need to do a bit of hacking.

The package differs slightly from the version in the paper:

- The handle->name translation facility has been removed.  It's just too
  fragile to include in the general release.  If you need it, contact me
  directly and I'll be happy to mail you the code.

- The output format is a wee-bit different.

- The IBM-RT Enet filter version is also not included, since I seem to be
  the only person in the world running it.  RTs are really too slow 
  for this anyway.


To configure the package, edit the makefile in the obvious (to me at least)
way.

Note that the not all versions of SunOS NIT have working versions of the
packet timestamp mechanism.  Try to set the -DSTAMPS option in the makefile,
and if that doesn't work, take it out.

If you are actually going to use this to gather traces, I'd like to hear
from you! Please send email, and share your results/traces if your organization
will allow it.


I maintain a mailing list of users for updates, etc.  Send me mail to be
added to it.

Happy tracing.

Matt Blaze
Department of Computer Science
Princeton University
35 Olden Street
Princeton, NJ 08544

mab@cs.princeton.edu

609-258-3946
SHAR_EOF
fi
if test -f 'makefile'
then
	echo shar: "will not over-write existing file 'makefile'"
else
cat << \SHAR_EOF > 'makefile'
# makefile for rpcspy & nfstrace

CC=cc

#delete this target ...
all:
	@echo HEY! Edit the makefile to configure for your environment.

#...and add this one:
#all: rpcspy nfstrace

#ULTRIX:
#If you're running ULTRIX (with packetfilter), uncomment this:
#rpcspy: rpcspy.o  xdr_nfs.o pfilt.o
#	cc rpcspy.o pfilt.o xdr_nfs.o -o rpcspy

#SunOS
#if you're running SunOS (with NIT), uncomment this:
#CFLAGS=-DNIT
#except that if NIT timestamps are known to work right
# uncomment this line instead
#CFLAGS=-DNIT -DSTAMPS
#and uncomment these lines
#rpcspy: rpcspy.o  xdr_nfs.o nit.o
#	cc rpcspy.o nit.o xdr_nfs.o -o rpcspy


rpcspy.o: rpcspy.h rpcspy.c

pfilt.o: rpcspy.h pfilt.c

nit.o: rpcspy.h nit.c

nfstrace: y.tab.c lex.yy.c nfstrace.o
	cc nfstrace.o y.tab.c -ly -ll -o nfstrace

nfstrace.o: nfstrace.c nfsstuff.h

y.tab.c: nfsyacc.y nfsstuff.h
	yacc nfsyacc.y

lex.yy.c: nfslex.l nfsstuff.h
	lex nfslex.l
SHAR_EOF
fi
if test -f 'nfslex.l'
then
	echo shar: "will not over-write existing file 'nfslex.l'"
else
cat << \SHAR_EOF > 'nfslex.l'
%{
char *unstring();
%}
%%
[ \t]		;

\n		{ return NL; }

\|		{ return FS; }

"{"		{ return OPEN; }
"}"		{ return CLOSE; }

","		{ return COMMA; }

\#([^\n])*	{ yylval=addstring(yytext);
		  return COMMENT; }

"ok"		{ return OK; }
"failed"	{ return FAILED; }
"null"		{ return PROC_NULL; }
"getattr"	{ return PROC_GETATTR; }
"setattr"	{ return PROC_SETATTR; }
"root"		{ return PROC_ROOT; }
"lookup"	{ return PROC_LOOKUP; }
"readlink"	{ return PROC_READLINK; }
"read"		{ return PROC_READ; }
"writecache"	{ return PROC_WRITECACHE; }
"write"		{ return PROC_WRITE; }
"creat"		{ return PROC_CREAT; }
"remove"	{ return PROC_REMOVE; }
"rename"	{ return PROC_RENAME; }
"link"		{ return PROC_LINK; }
"symlink"	{ return PROC_SYMLINK; }
"mkdir"		{ return PROC_MKDIR; }
"rmdir"		{ return PROC_RMDIR; }
"readdir"	{ return PROC_READDIR; }
"statfs"	{ return PROC_STATFS; }

\"(([^\"\n])|(\\[.\n]))*\"	{ yylval=addstring(unstring(yytext));
			  return STRING; }
([^ \t\n,#\{\}\|])*	{ yylval=addstring(yytext);
			  return STRING; }
%%

/*
 * good enough
 */
char *unstring(s)
     char *s;
{
	static char buf[2048];
	int i;

	i=0;
	for (i=0; *s; s++) {
		if (*s == '"')
			continue;
		if (*s == '\\')
			s++;
		buf[i++] = *s;
	}
	buf[i]='\0';
	return buf;
}
SHAR_EOF
fi
if test -f 'nfsstuff.h'
then
	echo shar: "will not over-write existing file 'nfsstuff.h'"
else
cat << \SHAR_EOF > 'nfsstuff.h'
#include <sys/types.h>
#include <sys/file.h>

#define RFS_NULL        0
#define RFS_GETATTR     1
#define RFS_SETATTR     2
#define RFS_ROOT        3
#define RFS_LOOKUP      4
#define RFS_READLINK    5
#define RFS_READ        6
#define RFS_WRITECACHE  7
#define RFS_WRITE       8
#define RFS_CREATE      9
#define RFS_REMOVE      10
#define RFS_RENAME      11
#define RFS_LINK        12
#define RFS_SYMLINK     13
#define RFS_MKDIR       14
#define RFS_RMDIR       15
#define RFS_READDIR     16
#define RFS_STATFS      17
#define RFS_NPROC       18

typedef struct line {
	int op;		/* RFS_* */
	int stat;	/* OK or FAILED */
	char *time;	/* timestamp */
	char *svr;	/* server */
	char *cln; 	/* client, (including uid) */
	struct {
		char *handle1;
		char *name1;
		char *handle2;
		char *name2;
		char *uid;
		char *mode;
		char *size;
		char *offset;
		char *count;
	} call;
	struct {
		char *handle;
		char *name;
		char *count;
		char *uid;
		char *mode;
		char *size;
	} reply;
} line;

extern line l;
SHAR_EOF
fi
if test -f 'nfstrace.8'
then
	echo shar: "will not over-write existing file 'nfstrace.8'"
else
cat << \SHAR_EOF > 'nfstrace.8'
.TH NFSTRACE 8 ""
.SH NAME
nfstrace
.SH SYNOPSIS
.B nfstrace
[ \-\- ]
[ \-\fBc\fP ]
[ \-\fBt\fP \fItemplate\fP ]
[ \-\fBs\fP \fIsize\fP ]
[ \-\fBn\fP \fIsequence-num\fP ]
[ \-\fBw\fP \fItimeout\fP ]
[ \-\fBz\fP \fIcompress-command\fP ]
.SH DESCRIPTION
.I nfstrace
takes the output of
.I rpcspy(8)
(from stdin) and produces an approximate log of user-level file opens.
.LP
The output format consists of 6 vertical-bar-separated fields:
.br
.I
timestamp | direction | file-id | client | transferred | size
.br
where
.I timestamp
is the time the open occurred,
.I "direction"
is either \fCread\fP or \fCwrite\fP,
.I "file-id"
identifies the server and the file handle,
.I client
is the client and user that performed the open,
.I transferred
is the number of bytes of the file actually read or written (cache
hits have a 0 in this field), and
.I size
is the size of the file (in bytes).
.LP
The file handle to name translation package is not included in the general
release version.
It is only a rough approximation, but if your application requires it,
it is available directly from the author.
.LP
Output is normally to the file "nfs.out.nnnnn", where "nnnnn" is a sequence number
that is incremented when the file is checkpointed.   The file is checkpointed (and
any pending reads emitted) every 10000 input records; this parameter may be
altered with the \-\fBs\fP flag.  The name of the file and the beginning sequence
number may be specified with the \-\fBt\fP and \-\fBn\fP flags, respectively.
If the \-\- flag is given, the output is just written to stdout.
.LP
The output may be compressed with the \-\fBc\fP flag; this causes the
output to be piped through "/usr/ucb/compress -c".  The name of the
compress filter can be specified with \-\fBz\fP.
.LP
A heuristic is used that assumes that each open for read or write
lasts a maximum of two minutes; this time may be altered with the \-\fBw\fP
flag.
.LP
Note that the output is produced out of order.  It can be sorted by a post-processor,
and each output file can be sorted independently and then concatenated together
to produce a chronological trace.
.SH FILES
nfs.out.nnnnn
.SH SEE ALSO
rpcspy(8), compress(1)
.SH BUGS
The output is only an approximation and could be grossly different from
what really occurred.  In particular, reads from the cache tend to be
over-estimated.
.LP
Unusual workloads (especially database applications) will fool the
program particularly badly.
.LP
There should be more control over the level of detail in the output.
.LP
The checkpointing frequency is rather approximate.
.SH AUTHOR
Matt Blaze, mab@cs.princeton.edu
SHAR_EOF
fi
if test -f 'nfstrace.c'
then
	echo shar: "will not over-write existing file 'nfstrace.c'"
else
cat << \SHAR_EOF > 'nfstrace.c'
/*
 * nfstrace main module
 * release 0.9
 *
 * Copyright 1991 Matt Blaze.
 * May be freely reproduced for non-commerical use.
 * All other rights, including use for direct commerical advantage or
 * use in a commerical product, are reserved by the author.
 *
 * bug reports, etc -> mab@cs.princeton.edu
 *
 * This version does NOT include the handle->name translation.
 * That version works, but is rough enough that it is not included in
 * the general distribution.  If you have a use for it, contact the author.
 */
 
#include<sys/types.h>
#include<sys/stat.h>
#include <stdio.h>
#include <strings.h>
#include "nfsstuff.h"

#define HSIZE 65599
#define MAXNF 20000
static int nf=0;
char comp[64] = "/usr/ucb/compress -c";

typedef struct clirec {
	char name[64];	/* of client */
	int direction;	/* 0==read, else write */
	int zeroaccessed; /* zeroth byte accessed yet? */
	int readcount;	/* total bytes accessed */
	int writecount; /* bytes written */
	unsigned long startsecs;	/* time operation started */
	unsigned long startusecs;
	unsigned long lastsecs;		/* last access */
	unsigned long lastusecs;
	struct clirec *nextcli;		/* in hash table */
	struct clirec *nextpend, *prevpend; /* list of pending r/w ops */
	struct file *file;
} clirec;

typedef struct file {
	char key[64];	/* server:handle */
	int mode;
	int uid;
	int size;
	struct clirec *clients;
	struct file *next;
} file;


clirec *pending;	/* list of clients w/ pending read or write ops */

file *table[HSIZE];	/* the file handle hash table */

#define NAME "nfs.out"

int curfile=0;
int cursecs=0;
int curusecs=0;
int lasttimedone=0;
int oplen=135;

FILE *fd;

char *firstpart();
char *lastpart();
clirec *getcli();
file *getfile();
unsigned long hashval();
char *mkkey();
char *Malloc();
static int n=1;




int	nfs_null(),
	nfs_getattr(),
	nfs_setattr(),
	nfs_root(),
	nfs_lookup(),
	nfs_readlink(),
	nfs_read(),
	nfs_writecache(),
	nfs_write(),
	nfs_creat(),
	nfs_remove(),
	nfs_rename(),
	nfs_link(),
	nfs_symlink(),
	nfs_mkdir(),
	nfs_rmdir(),
	nfs_readdir(),
	nfs_statfs();

int (*nfs_proc[])() = {
        nfs_null,          /*  0 */
	nfs_getattr,       /*  1 */
        nfs_setattr,       /*  2 */
        nfs_root,          /*  3 */
        nfs_lookup,        /*  4 */
        nfs_readlink,      /*  5 */
        nfs_read,          /*  6 */
        nfs_writecache,    /*  7 */
        nfs_write,         /*  8 */
        nfs_creat,         /*  9 */
        nfs_remove,        /* 10 */
        nfs_rename,        /* 11 */
        nfs_link,          /* 12 */
        nfs_symlink,       /* 13 */
        nfs_mkdir,         /* 14 */
        nfs_rmdir,         /* 15 */
        nfs_readdir,       /* 16 */
        nfs_statfs         /* 17 */
};


char template[64];
int compress=0;
int outflag=0;
int filesize=5000;

main(argc,argv)
     int argc;
     char **argv;
{
	int i;
	char *flag;
	char *c;
	char *pn;

	strcpy(template,NAME);
	pn=argv[0];
	while (--argc) {
		if (**++argv == '-') {	/* flag */
			for (flag = ++*argv; *flag; flag++) {
				switch (*flag) {
				    case '-':	/* just write to stdout */
					outflag++;
					fd=stdout;
					break;
				    case 'c':	/* compress output */
					compress++;
					break;
				    case 's':	/* size of output files */
				    case 't':	/* name template*/
				    case 'n':	/* file number */
				    case 'w':	/* time to wait for close */
				    case 'z':	/* name of compress cmd */
					enq(*flag);
					break;
				    default:
					usage(pn);
					exit(-1);
				}
			}
		} else {
			switch (deq()) {
			    case -1:
				usage();
				exit(-1);
			    case 's':
				filesize=atoi(*argv);
				break;
			    case 't':
				strcpy(template,*argv);
				break;
			    case 'n':
				curfile=atoi(*argv);
				break;
			    case 'w':
				oplen=atoi(*argv);
				break;
			    case 'z':
				strcpy(comp,*argv);
				break;
			    default:	/* should never happen */
				fprintf(stderr,"Internal error\n");
				exit(-2);
			}
		}

	}
	if ((deq() != -1)) {
		usage(pn);
		exit(-1);
	}
	for (i=0; i<HSIZE; i++)
		table[i]=NULL;
	if (!outflag)
		outopen();
	yyparse();
	cursecs+=500;
	checkall();
}


usage(s)
     char *s;
{
	fprintf(stderr,
		"usage: %s [--] [-c] [-t template] [-s size] [-n seqnum] [-w timeout] [-z filt]\n",s);
}


#define QS 10
struct {
	int data[QS];
	int head;
	int tail;
} argq = {{0},0,0};

enq(f)
     char f;
{
	argq.tail++;
	argq.tail %= QS;
	if (argq.head==argq.tail) {
		fprintf(stderr,"Can't deal with this\n");
		exit(-2);
	}
	argq.data[argq.tail]=f;
}

deq()
{
	if (argq.head==argq.tail)
		return -1;
	argq.head++;
	argq.head %= QS;
	return(argq.data[argq.head]);
}

#define CHECKFREQ 65

outopen()
{
	char buf[64];
	if (compress) {
		sprintf(buf,"%s>%s.%05d.Z",comp,template,curfile);
		if ((fd=popen(buf,"w")) == NULL) {
			perror(buf);
			exit(1);
		}
	} else {
		sprintf(buf,"%s.%05d",template,curfile);
		if((fd=fopen(buf,"w"))==NULL) {
			perror(buf);
			exit(1);
		}
	}
}
	
do_operation()
{
	if (l.stat == 0)
		return;	/* we dont care about failures */
	if (l.op>=RFS_NPROC)
		return; /* should never happen */
	if (!validtime(l.time))
		return;	/* should never happen */
	cursecs=atoi(firstpart(l.time));
	curusecs=atoi(lastpart(l.time));
	if ((cursecs-lasttimedone)>CHECKFREQ) {
		checkall();
		lasttimedone=cursecs;
	}
	if (!outflag &&(n>filesize)) {
		switchfile();
		n=1;
	}
	(*nfs_proc[l.op])();
}

switchfile()
{
	char buf[64];
	
	cursecs += 500;
	checkall();
	cursecs -= 500;
	closefile(fd);
	curfile++;
	outopen();
}

closefile()
{
	if (compress)
		pclose(fd);
	else
		fclose(fd);
}

validtime(s)
     char *s;
{
	return (index(s,'.') != 0);
}

char *firstpart(s)
     char *s;
{
	static char buf[64];
	strcpy(buf,s);
	*(index(buf,'.'))='\0';
	return buf;
}

char *lastpart(s)
     char *s;
{
	return(index(s,'.')+1);
}


unsigned long hashval(s)
     char *s;
{
	unsigned long v;
	int j;

	v=0;
	j=0;
	while (*s) {
		if (j>20)
			j=0;
		v += (*s++)<<(j++);
	}
	return v%HSIZE;
}


checkall()
{
	clirec *c, *prev, *temp;
	
	c=pending;
	prev=NULL;
	while (c!=NULL) {
		if (tooold(c)) {
			emit(c);
			temp=c->nextpend;
			if (prev!=NULL) {
				prev->nextpend=c->nextpend;
			} else
				pending = c->nextpend;
			if (c->nextpend !=NULL)
				c->nextpend->prevpend = prev;
			remove(c);	/* remove from parent list */
			free(c);
			c=temp;
		} else {
			prev=c;
			c=c->nextpend;
		}
	}
}

remove(c)
     clirec *c;
{
	clirec *cl;

	cl=c->file->clients;
	if (cl==c) {
		c->file->clients = c->nextcli;
		return;
	}
	while (cl) {
		if (cl->nextcli == c) {
			cl->nextcli = c->nextcli;
			return;
		}
		cl=cl->nextcli;
	}
}


tooold(c)
     clirec *c;
{
	return ((cursecs - c->lastsecs)>oplen);
}

readold(c)
     clirec *c;
{
	if (c->writecount)
		return 0;
	return ((cursecs - c->lastsecs)>2);
}


file *getfile(s,h)
     char *s;
     char *h;
{
	char buf[64];
	file *f;
	clirec *c;
	clirec *prev;
	clirec *temp;
	unsigned long hash;

	strcpy(buf,mkkey(s,h));
	hash=hashval(buf);
	f=table[hash];
	while (f!=NULL) {
		if (strcmp(f->key,buf)==0) {
			/* first we check for old clients */
			c=f->clients;
			prev=NULL;
			while (c) {
				if (tooold(c)) {
					emit(c);
					temp=c->nextcli;
					if (prev !=NULL)
						prev->nextcli = c->nextcli;
					else
						f->clients = c->nextcli;
					if (c->nextpend != NULL)
						c->nextpend->prevpend=
							c->prevpend;
					if (c->prevpend != NULL)
						c->prevpend->nextpend=
							c->nextpend;
					else
						pending=c->nextpend;
					free (c);
					c=temp;
				} else {
					prev=c;
					c=c->nextcli;
				}
			}
			return f;
		}
		f=f->next;
	}
	if ((f=(file *)Malloc(sizeof(file))) == NULL) {
		perror("malloc");
		exit(1);
	}
	nf++;
	if (nf>MAXNF)
		gc();
	strcpy(f->key,buf);
	f->mode=0;
	f->uid=0;
	f->size=0;
	f->clients=NULL;
	f->next=table[hash];
	table[hash]=f;
	return f;
}

clirec *getcli(f,n)
     file *f;
     char *n;
{
	clirec *c;

	c=f->clients;
	while (c!=NULL) {
		if (strcmp(c->name,n)==0)
			return c;
		c=c->nextcli;
	}
	if ((c=(clirec *)Malloc(sizeof(clirec))) == NULL) {
		perror("malloc");
		exit(1);
	}
	strcpy(c->name,n);
	c->lastusecs=c->startusecs=curusecs;
	c->lastsecs=c->startsecs=cursecs;
	c->readcount=0;
	c->writecount=0;
	c->zeroaccessed=0;
	c->nextcli=f->clients;
	f->clients=c;
	c->nextpend=pending;
	if (pending !=NULL)
		pending->prevpend = c;
	c->prevpend = NULL;
	pending = c;
	c->file=f;
	return c;
}



char *mkkey(c,h)
     char *c;
     char *h;
{
	static char buf[64];

	sprintf(buf,"%s:%s",c,h);
	return buf;
}


emit(c)
     clirec *c;
{
	fprintf(fd,"%d.%06d | %s | %s | %s | %d | %d\n",
	       c->startsecs,
	       c->startusecs,
	       (c->writecount?"write":"read"),
	       c->file->key,
	       c->name,
	       (c->writecount?c->writecount:c->readcount),
	       c->file->size?c->file->size:c->readcount);
	n++;
}

nfs_null()
{
	/* do nothing */
}


nfs_getattr()
{
	file *f;
	clirec *c;
	unsigned int mode;

	/* first, make sure this is a file */
	sscanf(l.reply.mode,"%o",&mode);
	if ((mode&S_IFMT) != S_IFREG)
		return;
	/* find or create a file record */
	if ((f=getfile(l.svr,l.call.handle1)) == NULL)
		exit(1); /* should never happen */
	/* set up the file values */
	f->uid = atoi(l.reply.uid);
	f->mode = mode;
	f->size = atoi(l.reply.size);
	if ((c=getcli(f,l.cln)) == NULL)
		exit(1);  /* should never happen */
	c->lastsecs = cursecs;
	c->lastusecs = curusecs;
}

nfs_setattr()
{
	/* do a little; this might really be a write */
	file *f;
	clirec *c;
	int size;
	int mode;

	size=atoi(l.reply.size);
	if (size<0)
		return;
	sscanf(l.reply.mode,"%o",&mode);
	if ((mode&S_IFMT) != S_IFREG)
		return;
	/* the size changed, so now we treat as a write */
	if ((f=getfile(l.svr,l.call.handle1)) == NULL)
		exit(1); /* should never happen */
	if ((c=getcli(f,l.cln)) == NULL)
		exit(1);  /* should never happen */
	if ((c->readcount>0) ||
	    readold(c))  {	/* there was a read pending on this file */
		emit(c);
		c->startsecs=cursecs;
		c->startusecs=curusecs;
		c->readcount=0;
		c->zeroaccessed=0;
	}
	if (c->writecount && (size==0)) {
		emit(c);
		c->startsecs=cursecs;
		c->startusecs = curusecs;
		c->writecount=0;
		c->zeroaccessed = 0;
	}
	f->size=size;
	f->mode = mode;
	f->uid = atoi(l.reply.uid);
	c->writecount += 1;
	c->lastusecs=curusecs;
	c->lastsecs=cursecs;
}

nfs_root()
{
	/* do nothing */
}

nfs_readlink()
{
	/* do nothing */
}

nfs_read()
{
	file *f;
	clirec *c;
	int count;

	if ((f=getfile(l.svr,l.call.handle1)) == NULL)
		exit(1); /* should never happen */
	if ((c=getcli(f,l.cln)) == NULL)
		exit(1);  /* should never happen */
	if (c->writecount>0) {	/* there was a write pending on this file */
		/* make sure this is a positive read */
		if (!(atoi(l.reply.count) < 0))
			return;
		emit(c);
		c->startsecs=cursecs;
		c->startusecs=curusecs;
		c->writecount=0;
		c->zeroaccessed=0;
	}
	if (atoi(l.call.offset)==0) {
		if (c->zeroaccessed) {
			emit(c);
			c->startsecs=cursecs;
			c->startusecs=curusecs;
			c->readcount=0;
		} else
			c->zeroaccessed=1;
	}
	c->readcount += atoi(l.reply.count);
	c->lastusecs=curusecs;
	c->lastsecs=cursecs;
}

nfs_writecache()
{
	/* do nothing */
}


nfs_write()
{
	file *f;
	clirec *c;

	if ((f=getfile(l.svr,l.call.handle1)) == NULL)
		exit(1); /* should never happen */
	if ((c=getcli(f,l.cln)) == NULL)
		exit(1);  /* should never happen */
	if ((c->readcount>0) ||
	    readold(c))  {	/* there was a read pending on this file */
		emit(c);
		c->startsecs=cursecs;
		c->startusecs=curusecs;
		c->zeroaccessed=0;
		c->readcount=0;
	}
	if (atoi(l.call.offset)==0) {
		if (c->zeroaccessed) {
			emit(c);
			c->startsecs=cursecs;
			c->startusecs=curusecs;
			c->writecount=0;
		} else
			c->zeroaccessed=1;
	}
	c->writecount += atoi(l.call.count);
	f->size = atoi(l.reply.size);
	c->lastusecs=curusecs;
	c->lastsecs=cursecs;
}

nfs_creat()
{
	/* do very little */
}

nfs_remove()
{
	/* do very little */
}

nfs_rename()
{
	/* do nothing */
}

nfs_lookup()
{
	file *f;
	unsigned int mode;

	/* first, make sure this is a file */
	sscanf(l.reply.mode,"%o",&mode);
	if ((mode&S_IFMT) != S_IFREG)
		return;
	/* find or create a file record */
	if ((f=getfile(l.svr,l.reply.handle)) == NULL)
		exit(1); /* should never happen */
	/* set up the file values */
	f->uid = atoi(l.reply.uid);
	f->mode = mode;
	f->size = atoi(l.reply.size);
}

nfs_link()
{
	/* do nothing */
}

nfs_symlink()
{
	/* do nothing */
}

nfs_mkdir()
{
	/* do nothing */
}

nfs_rmdir()
{
	/* do nothing */
}

nfs_readdir()
{
	/* do nothing */
}

nfs_statfs()
{
	/* do nothing */
}

char *Malloc(n)
     int n;
{
	char *m;

	if ((m=(char *)malloc(n)) == NULL) {
		gc();
		return ((char *)malloc(n));
	} else
		return (m);
}

gc()
{
	int i;
	file *f;
	file **prev;
	int count=0;

	fprintf(fd,"#collecting garbage\n");
	fflush(fd);
	for (i=0; i<HSIZE; i++) {
		f=table[i];
		prev = &table[i];
		while (f != NULL) {
			if (f->clients == NULL) {
				*prev=f->next;
				free(f);
				f = *prev;
				count++;
				nf--;
			} else {
				prev = &f->next;
				f= f->next;
			}
		}
	}
	fprintf(fd,"#GC Count=%d\n",count);
	fflush(fd);
}
SHAR_EOF
fi
if test -f 'nfsyacc.y'
then
	echo shar: "will not over-write existing file 'nfsyacc.y'"
else
cat << \SHAR_EOF > 'nfsyacc.y'
%{
/*
 * YACC Grammar for nfstrace
 * 11/91
 * Copyright 1991 Matt Blaze.
 * May be freely reproduced for non-commerical use.
 * All other rights, including use for direct commerical advantage or
 * use in a commerical product, are reserved by the author.
 */
#include <stdio.h>
#include "nfsstuff.h"

line l;

typedef char str[1024];
#define NFIELDS 64

str strings[NFIELDS];

int field=0;
int nfields;
extern FILE *fd;

%}

%token NL FS OPEN CLOSE COMMA
%token OK FAILED
%token PROC_NULL PROC_GETATTR PROC_SETATTR PROC_ROOT PROC_LOOKUP PROC_READLINK PROC_READ
%token PROC_WRITECACHE PROC_WRITE PROC_CREAT PROC_REMOVE PROC_RENAME PROC_LINK
%token PROC_SYMLINK PROC_MKDIR PROC_RMDIR PROC_READDIR PROC_STATFS
%token STRING COMMENT

%start lines

%%


lines	:
	| lines line
	;

line	: NL
	| COMMENT NL			{ fprintf(fd,"#%s\n",strings[$1]);
					   fflush(fd);
					   reset(); }
	| op NL				{ do_operation();reset(); }
	| error NL			{ fprintf(stderr,"BAD line\n");reset();}
	;

op	: hdr FS proc_null
	| hdr FS proc_getattr
	| hdr FS proc_setattr
	| hdr FS proc_root
	| hdr FS proc_lookup
	| hdr FS proc_readlink
	| hdr FS proc_read
	| hdr FS proc_writecache
	| hdr FS proc_write
	| hdr FS proc_creat
	| hdr FS proc_remove
	| hdr FS proc_rename
	| hdr FS proc_link
	| hdr FS proc_symlink
	| hdr FS proc_mkdir
	| hdr FS proc_rmdir
	| hdr FS proc_readdir
	| hdr FS proc_statfs
	;

hdr	: timestamp FS server FS client { l.time = strings[$1];
					  l.svr = strings[$3];
					  l.cln = strings[$5]; }
	;

timestamp : STRING			{ $$ = $1; }
	;

server	: STRING			{ $$ = $1; }
	;

client	: STRING			{ $$ = $1; }
	;

proc_null : PROC_NULL FS call_null FS reply_null
					{ l.op = RFS_NULL; }
	;

proc_getattr : PROC_GETATTR FS call_getattr FS reply_getattr
					{ l.op = RFS_GETATTR; }
	;

proc_setattr : PROC_SETATTR FS call_setattr FS reply_setattr
					{ l.op = RFS_SETATTR; }
	;

proc_root : PROC_ROOT FS call_root FS reply_root
					{ l.op = RFS_ROOT; }
	;

proc_lookup : PROC_LOOKUP FS call_lookup FS reply_lookup
					{ l.op = RFS_LOOKUP; }
	;

proc_readlink : PROC_READLINK FS call_readlink FS reply_readlink
					{ l.op = RFS_READLINK; }
	;

proc_read : PROC_READ FS call_read FS reply_read
					{ l.op = RFS_READ; }
	;

proc_writecache : PROC_WRITECACHE FS call_writecache FS reply_writecache
					{ l.op = RFS_WRITECACHE;}
	;

proc_write : PROC_WRITE FS call_write FS reply_write	
					{ l.op = RFS_WRITE; }
	;

proc_creat : PROC_CREAT FS call_creat FS reply_creat
					{ l.op = RFS_CREATE; }
	;

proc_remove : PROC_REMOVE FS call_remove FS reply_remove
					{ l.op = RFS_REMOVE; }
	;

proc_rename : PROC_RENAME FS call_rename FS reply_rename
					{ l.op = RFS_RENAME; }
	;

proc_link : PROC_LINK FS call_link FS reply_link
					{ l.op = RFS_LINK; }
	;

proc_symlink : PROC_SYMLINK FS call_symlink FS reply_symlink
					{ l.op = RFS_SYMLINK; }
	;

proc_mkdir : PROC_MKDIR FS call_mkdir FS reply_mkdir
					{ l.op = RFS_MKDIR; }
	;

proc_rmdir : PROC_RMDIR FS call_rmdir FS reply_rmdir
					{ l.op = RFS_RMDIR; }
	;

proc_readdir : PROC_READDIR FS call_readdir FS reply_readdir
					{ l.op = RFS_READDIR; }
	;

proc_statfs : PROC_STATFS FS call_statfs FS reply_statfs
					{ l.op = RFS_STATFS; }
	;


call_null :
	;

reply_null :
	;

call_getattr : fhandle			{ l.call.handle1 = strings[$1]; }
	;

reply_getattr : FAILED			{ l.stat = 0; }
	| OK COMMA fattr		{ l.stat = 1; }
	;

call_setattr : OPEN fhandle COMMA sattr CLOSE { l.call.handle1 = strings[$2]; }
	;

reply_setattr : FAILED			{ l.stat = 0; }
	| OK COMMA fattr		{ l.stat = 1; }
	;

call_root :
	;

reply_root :
	;

call_lookup : diropargs
	;

reply_lookup : FAILED			{ l.stat = 0; }
	| OK COMMA diropres		{ l.stat = 1; }
	;

call_readlink : fhandle			{ l.call.handle1 = strings[$1]; }
	;

reply_readlink : FAILED			{ l.stat = 0; }
	| OK COMMA STRING		{ l.stat = 1;
					  l.reply.name = strings[$2]; }
	;

call_read : OPEN fhandle COMMA STRING COMMA STRING CLOSE 	{
					l.call.handle1 = strings[$2];
					l.call.offset = strings[$4];
					l.call.count = strings[$6]; }
	;

reply_read : FAILED			{ l.stat = 0; }
	| OK COMMA STRING		{ l.stat = 1;
					  l.reply.count = strings[$3]; }

call_writecache :
	;

reply_writecache :
	;

call_write : OPEN fhandle COMMA STRING COMMA STRING CLOSE 	{
					l.call.handle1 = strings[$2];
					l.call.offset = strings[$4];
					l.call.count = strings[$6]; }
	;

reply_write : FAILED			{ l.stat = 0; }
	| OK COMMA fattr		{ l.stat = 1; }
	;

call_creat : OPEN fhandle COMMA STRING COMMA sattr CLOSE 	{
					l.call.handle1 = strings[$2];
					l.call.name1 = strings[$4]; }
	;

reply_creat : FAILED			{ l.stat = 0; }
	| OK COMMA OPEN fhandle COMMA fattr CLOSE 	{
					l.stat = 1;
					l.reply.handle = strings[$4]; }
	;

call_remove : diropargs
	;

reply_remove : FAILED			{ l.stat = 0; }
	| OK				{ l.stat = 1; }
	;

call_rename : diropargs COMMA diropargs2
	;

reply_rename: FAILED			{ l.stat = 0; }
	| OK				{ l.stat = 1; }
	;

call_link : OPEN fhandle COMMA diropargs2 CLOSE {
					l.call.handle1 = strings[$2]; }
	;

reply_link : FAILED			{ l.stat = 0; }
	| OK				{ l.stat = 1; }
	;

call_symlink : OPEN diropargs COMMA STRING COMMA sattr CLOSE {
					l.call.name2 = strings[$4]; }
	;

reply_symlink : FAILED			{ l.stat = 0; }
	| OK				{ l.stat = 1; }
	;

call_mkdir : OPEN fhandle COMMA STRING COMMA sattr CLOSE {
					l.call.handle1 = strings[$2];
					l.call.name1 = strings[$4]; }
	;

reply_mkdir : FAILED			{ l.stat = 0; }
	| OK COMMA OPEN fhandle COMMA fattr CLOSE  {
					l.stat = 1;
					l.reply.handle = strings[$4]; }
	;

call_rmdir : diropargs
	;

reply_rmdir : FAILED			{ l.stat = 0; }
	| OK				{ l.stat = 1; }
	;

call_readdir : fhandle
	;

reply_readdir : FAILED
	| OK
	;

call_statfs :
	;

reply_statfs :
	;

fhandle : STRING			{ $$ = $1; }
	;

diropargs : OPEN fhandle COMMA STRING CLOSE {
					l.call.handle1 = strings[$2];
					l.call.name1 = strings[$4]; }
	;

diropargs2 : OPEN fhandle COMMA STRING CLOSE {
					l.call.handle2 = strings[$2];
					l.call.name2 = strings[$4]; }
	;

fattr : OPEN STRING COMMA STRING COMMA STRING COMMA STRING CLOSE {
					l.reply.mode = strings[$2];
					l.reply.uid = strings[$4];
					l.reply.size = strings[$6]; }
	;

sattr : OPEN STRING COMMA STRING COMMA STRING CLOSE {
					l.call.mode = strings[$2];
					l.call.uid = strings[$4];
					l.call.size = strings[$6]; }
	;

diropres : OPEN fhandle COMMA fattr CLOSE {
					l.reply.handle = strings[$2]; }
	;


%%
addstring(s)
     char *s;
{
	strcpy(strings[field],s);
	strings[field];
	return (field++);
}

reset()
{
	field=0;
}


#include "lex.yy.c"
SHAR_EOF
fi
if test -f 'nit.c'
then
	echo shar: "will not over-write existing file 'nit.c'"
else
cat << \SHAR_EOF > 'nit.c'
#include<sys/types.h>
#include<sys/socket.h>
#include <rpc/rpc.h>
#include<net/if.h>
#include<netinet/in_systm.h>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<netinet/udp.h>
#include<netinet/if_ether.h>
#include<rpc/types.h>
#include<sys/param.h>
#include<sys/time.h>
#include <sys/file.h>
#include <sys/errno.h>
#include <sys/ioctl.h>

#include <signal.h>
#include<netdb.h>
#include "rpcspy.h"
#include <sys/stropts.h>
#include<net/packetfilt.h>
#include <stdio.h>


int dropped=0;
int pr=0;
extern int ok, call, fgc;
long secs;	/* current time in secs */
int ser;	/* packets this second */
int clk();
int die();

struct packetfilt filt = {
	10, 4,
	ENF_PUSHWORD + 6,
	ENF_PUSHLIT, 0x0800,
	ENF_EQ
};


char pint[64];

net_open(interface)
     char *interface;
{
	int efd;
	unsigned long bits;
	int one=1;
	unsigned int back=32;
	struct strioctl si;
	struct ifreq ifr;
	unsigned long sl=0;
	struct timeval to;

	strcpy(pint,interface);
	if ((efd=open("/dev/nit",O_RDONLY)) <0) {
		return(efd);
	}
	/* install filter */
	if (ioctl(efd,I_PUSH,"pf")<0)
		return -1;
	si.ic_cmd=NIOCSETF;
	si.ic_len=sizeof filt;
	si.ic_dp= (char *)&filt;
	if (ioctl(efd,I_STR,&si)<0)
		return -1;
	ioctl(efd,I_SRDOPT,RMSGD); 
	si.ic_timout=INFTIM;
	strcpy(ifr.ifr_name,interface);
	si.ic_cmd=NIOCBIND;
	si.ic_len=sizeof ifr;
	si.ic_dp= (char *)&ifr;
	if (ioctl(efd,I_STR,&si)<0)
		return -1;
	to.tv_sec=0;
	to.tv_usec=0;
	si.ic_cmd=NIOCSSNAP;
	si.ic_len=sizeof sl;
	si.ic_dp=(char *)&sl;
	if (ioctl(efd,I_STR,&si)<0)
		return -1;
	bits =
#ifdef STAMPS
		NI_TIMESTAMP | NI_DROPS |
#endif
		NI_PROMISC ;
	si.ic_cmd=NIOCSFLAGS;
	si.ic_len=sizeof bits;
	si.ic_dp=(char *)&bits;
	if (ioctl(efd,I_STR,&si)<0)
		return -1;
        signal(SIGINT, die);
        signal(SIGHUP, die);
	ioctl(efd,I_FLUSH,FLUSHR);
	return efd;
}

net_read(efd,buf,len)
     int efd;
     ether_packet *buf;
     int len;
{
	int r;
	char *cp;
	struct timeval tv;
	struct timezone tvp;

	r=read(efd,buf,len);
	if (r<0)
		return r;
#ifdef STAMPS
	cp=(char *)buf;
	secs=((struct nit_iftime *)cp)->nh_timestamp.tv_sec;
	ser=((struct nit_iftime *)cp)->nh_timestamp.tv_usec;
	cp += sizeof(struct nit_iftime);
	dropped = ((struct nit_ifdrops *)cp)->nh_drops;
#else /* for screwed up timestamps */

	gettimeofday(&tv,&tvp);
	secs=tv.tv_sec;
	ser=tv.tv_usec;
	/* fprintf(stderr,"%d %d %d\n",secs,ser,dropped); */
#endif
	pr++;
	return r;
}



die()
{
	printf("\n# flush (processed = %d, dropped = %d, call=%d, ok=%d, fgc=%d)\n",
	pr,dropped,call,ok,fgc);
	exit(0);
}
SHAR_EOF
fi
if test -f 'pfilt.c'
then
	echo shar: "will not over-write existing file 'pfilt.c'"
else
cat << \SHAR_EOF > 'pfilt.c'
#include<sys/types.h>
#include<sys/socket.h>
#include <rpc/rpc.h>
#include<net/if.h>
#include<netinet/in_systm.h>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<netinet/udp.h>
#include<netinet/if_ether.h>
#include<rpc/types.h>
#include<sys/param.h>
#include<sys/time.h>
#include <sys/file.h>
#include <sys/errno.h>
#include <sys/ioctl.h>

#include <signal.h>
#include<netdb.h>
#include "rpcspy.h"
#include <stdio.h>
#define RES 1	/* tick every second */


int dropped=0;
int pr=0;
extern int ok, call, fgc;
long secs;	/* current time in secs */
int ser;	/* packets this second */
int clk();
int die();

struct enfilter filt = {
	10, 4,
	ENF_PUSHWORD + 6,
	ENF_PUSHLIT, 0x0008,
	ENF_EQ
};


char pint[64];

net_open(interface)
     char *interface;
{
	int efd;
	unsigned short bits;
	int one=1;
	unsigned int back=32;

	if ((efd=pfopen(interface,O_RDONLY)) <0) {
		return(efd);
	}
	strcpy(pint,interface);
	/* install filter */
	if (ioctl(efd,EIOCSETF,&filt)<0)
		return -1;
	/*these fail if not root. thats ok */
	ioctl(efd,EIOCALLOWPROMISC,&one);
	ioctl(efd,EIOCALLOWCOPYALL,&one);

	ioctl(efd, EIOCSETW, &back);
	bits=ENNONEXCL|ENTSTAMP|ENCOPYALL|ENPROMISC;
	if (ioctl(efd,EIOCMBIS,&bits)<0)
		return -1;
	/* set prom. mode */
        signal(SIGINT, die);
        signal(SIGHUP, die);
	return efd;
}

net_read(efd,buf,len)
     int efd;
     ether_packet *buf;
     int len;
{
	int r;
	struct enstamp e;

	r=read(efd,buf,len);
	if (r<0)
		return r;
	bcopy(buf,&e,sizeof(struct enstamp));;
	secs=e.ens_tstamp.tv_sec;
	ser=e.ens_tstamp.tv_usec;
	dropped += e.ens_dropped;
	pr++;
	return r;
}

die()
{
	printf("\n# flush (processed = %d, dropped = %d, call=%d, ok=%d, fgc=%d)\n",
	pr,dropped,call,ok,fgc);
	exit(0);
}
SHAR_EOF
fi
if test -f 'rpcspy.8'
then
	echo shar: "will not over-write existing file 'rpcspy.8'"
else
cat << \SHAR_EOF > 'rpcspy.8'
.TH RPCSPY 8 ""
.SH NAME
rpcspy
.SH SYNOPSIS
.B rpcspy
[ \-\fBt\fP \fIsecs\fP ]
[ \-\fBh\fP \fIbytes\fP ]
[ \-\fBh\fP \fIhost:bytes\fP ]
[ \-\fBi\fP \fIinterface\fP ]
[ \-\fBs\fP \fIsuffix\fP ]
[ \-\fBc\fP ]
.SH DESCRIPTION
.I rpcspy
produces a trace of low level NFS commands by monitoring Ethernet traffic.  Its main purpose is
to provide input to the
.I nfstrace(8)
program, although it can be used by itself to get a snapshot of low-level NFS traffic.
.LP
The output format consists of 8 vertical-bar-separated fields:
.br
.I
timestamp | execution-time | server | client | command-name | arguments | reply-data
.br
where
.I timestamp
is the time the reply message was received,
.I "execution-time"
is the time (in microseconds) that elapsed between the call and reply,
.I server
is the name (or IP address) of the server,
.I client
is the name (or IP address) of the client followed by the userid that
issued the command,
.I "command-name"
is the name of the particular program invoked
.I
(read, write, getattr,
etc.), and
.I "arguments"
and
.I "reply-data"
are the command dependent arguments and return values passed to and
from the RPC program, respectively.  For a complete grammar, see the 
nfstrace yacc file.
.LP
Normally,
.I rpcspy 
just runs until interrupted; if the \-\fBt\fP \fIsecs\fP argument is specified the
program terminates after \fIsecs\fP seconds.
.LP
The name if the Ethernet interface to monitor can be specified with the \-\fBi\fP flag.
.LP
File handles are emitted as hexadecimal strings.
A default file handle size (in bytes) can be specified with the \-\fBh\fP flag.  If the
handle size is 0 (the default), a guess is made on a case-by-case basis.  Handle sizes can be
restricted to a particular server by specifying \-\fBh\fP \fIhost:bytes\fP.  Arbitrarily many
\fB\-h\fP flags can be given to allow for multiple handle sizes on the same network.
.LP
The \-\fBs\fP flag can be used to specify a suffix that is removed from the host names before they
are emitted.  This is useful if your name server always returns fully-qualified
host names for your local network.
The default is "Princeton.EDU", which is probably not what you want.
.LP
If the \-\fBc\fP flag is given, the output is more concise, with the arguments and reply data
omitted.  This is mainly useful if you are feeding the output to an NFS statistics collection script.
.LP
Note that the program must run as root for the NIT version.  For the ULTRIX version, the user must
be root or the packetfilter interface must be configured to allow promiscuous mode and
copy-all mode.  It's probably not safe to install \fIrpcspy\fP as a setuid program.
.SH FILES
/dev/nit (for the SunOS version)
.br
/dev/pf/* (for the ULTRIX Packetfilter version)
.SH SEE ALSO
nfstrace(8), nit(4), packetfilter(4)
.SH BUGS
Not all the NFS commands have complete output.  Really, it's just enough for nfstrace(8) to work
with.  There should be more control over the level of detail.
.LP
It's easy to break the program by specifying completely bogus command line arguments.
.LP
Timestamps are sometimes wrong on the NIT version, and are at best approximate in any case.
.LP
The packetfilter version changes the state of the filter flags when run as root.
.LP
The file handle size guessing algorithm is an ugly hack.
.LP
It should be easier to add other RPC services.
.SH AUTHOR
Matt Blaze, mab@cs.princeton.edu
SHAR_EOF
fi
if test -f 'rpcspy.c'
then
	echo shar: "will not over-write existing file 'rpcspy.c'"
else
cat << \SHAR_EOF > 'rpcspy.c'
/*
 * rpcspy 11/91
 *
 * release 0.9
 *
 * Matt Blaze
 * Copyright 1991 Matt Blaze.
 * May be freely reproduced for non-commerical use.
 * All other rights, including use for direct commerical advantage or
 * use in a commerical product, are reserved by the author.
 *
 * bug reports, etc -> mab@cs.princeton.edu
 *
 * This is the main module.
 * It is much too long and way too ugly.
 */
#include<sys/types.h>
#include<sys/socket.h>
#include <rpc/rpc.h>
#include<net/if.h>
#include<netinet/in_systm.h>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<netinet/udp.h>
#include<rpc/types.h>
#include<sys/param.h>
#include<sys/time.h>
#include <sys/file.h>
#include <sys/errno.h>
#include <sys/ioctl.h>

/* generic handle type. this SEEMS to work most places */
#include <nfs/nfs.h>
typedef struct {
        long val[2];                    /* file system id type */
} fsid_t;
struct fhs {
        fsid_t  f1;
        u_short f2;
        char    f3[4];
};
/* stolen from lots of places */
#define FHMAXDATA   (NFS_FHSIZE - sizeof(struct fhs) + 4)

typedef struct sfh {
        fsid_t  fh_fsid;
        u_short fh_len;
        char    fh_data[FHMAXDATA];
}sfh;
#define fhandle_t sfh


#include <netdb.h>
#include <stdio.h>
#include <signal.h>
#include <strings.h>

#include "rpcspy.h"

int die();

/* defs for arg/reply procs */
int 	call_nfs_null(),
	call_nfs_getattr(),
	call_nfs_setattr(),
	call_nfs_root(),
	call_nfs_lookup(),
	call_nfs_readlink(),
	call_nfs_read(),
	call_nfs_writecache(),
	call_nfs_write(),
	call_nfs_create(),
	call_nfs_remove(),
	call_nfs_rename(),
	call_nfs_link(),
	call_nfs_symlink(),
	call_nfs_mkdir(),
	call_nfs_rmdir(),
	call_nfs_readdir(),
	call_nfs_statfs(),
	repl_nfs_null(),
	repl_nfs_getattr(),
	repl_nfs_setattr(),
	repl_nfs_root(),
	repl_nfs_lookup(),
	repl_nfs_readlink(),
	repl_nfs_read(),
	repl_nfs_writecache(),
	repl_nfs_write(),
	repl_nfs_create(),
	repl_nfs_remove(),
	repl_nfs_rename(),
	repl_nfs_link(),
	repl_nfs_symlink(),
	repl_nfs_mkdir(),
	repl_nfs_rmdir(),
	repl_nfs_readdir(),
	repl_nfs_statfs(),
	emit_nfs_null(),
	emit_nfs_getattr(),
	emit_nfs_setattr(),
	emit_nfs_root(),
	emit_nfs_lookup(),
	emit_nfs_readlink(),
	emit_nfs_read(),
	emit_nfs_writecache(),
	emit_nfs_write(),
	emit_nfs_create(),
	emit_nfs_remove(),
	emit_nfs_rename(),
	emit_nfs_link(),
	emit_nfs_symlink(),
	emit_nfs_mkdir(),
	emit_nfs_rmdir(),
	emit_nfs_readdir(),
	emit_nfs_statfs();


/* tables for the arg/reply procs */

int (*nfs_call_proc[])() = {
	call_nfs_null,		/*  0 */
	call_nfs_getattr,	/*  1 */
	call_nfs_setattr,	/*  2 */
	call_nfs_root,		/*  3 */
	call_nfs_lookup,	/*  4 */
	call_nfs_readlink,	/*  5 */
	call_nfs_read,		/*  6 */
	call_nfs_writecache,	/*  7 */
	call_nfs_write,		/*  8 */
	call_nfs_create,	/*  9 */
	call_nfs_remove,	/* 10 */
	call_nfs_rename,	/* 11 */
	call_nfs_link,		/* 12 */
	call_nfs_symlink,	/* 13 */
	call_nfs_mkdir,		/* 14 */
	call_nfs_rmdir,		/* 15 */
	call_nfs_readdir,	/* 16 */
	call_nfs_statfs		/* 17 */
};
int (*nfs_repl_proc[])() = {
	repl_nfs_null,		/*  0 */
	repl_nfs_getattr,	/*  1 */
	repl_nfs_setattr,	/*  2 */
	repl_nfs_root,		/*  3 */
	repl_nfs_lookup,	/*  4 */
	repl_nfs_readlink,	/*  5 */
	repl_nfs_read,		/*  6 */
	repl_nfs_writecache,	/*  7 */
	repl_nfs_write,		/*  8 */
	repl_nfs_create,	/*  9 */
	repl_nfs_remove,	/* 10 */
	repl_nfs_rename,	/* 11 */
	repl_nfs_link,		/* 12 */
	repl_nfs_symlink,	/* 13 */
	repl_nfs_mkdir,		/* 14 */
	repl_nfs_rmdir,		/* 15 */
	repl_nfs_readdir,	/* 16 */
	repl_nfs_statfs		/* 17 */
};

int (*nfs_emit_proc[])() = {
	emit_nfs_null,		/*  0 */
	emit_nfs_getattr,	/*  1 */
	emit_nfs_setattr,	/*  2 */
	emit_nfs_root,		/*  3 */
	emit_nfs_lookup,	/*  4 */
	emit_nfs_readlink,	/*  5 */
	emit_nfs_read,		/*  6 */
	emit_nfs_writecache,	/*  7 */
	emit_nfs_write,		/*  8 */
	emit_nfs_create,	/*  9 */
	emit_nfs_remove,	/* 10 */
	emit_nfs_rename,	/* 11 */
	emit_nfs_link,		/* 12 */
	emit_nfs_symlink,	/* 13 */
	emit_nfs_mkdir,		/* 14 */
	emit_nfs_rmdir,		/* 15 */
	emit_nfs_readdir,	/* 16 */
	emit_nfs_statfs		/* 17 */
};


char *nfs_proc_name[] = {
	"null",
	"getattr",
	"setattr",
	"root",
	"lookup",
	"readlink",
	"read",
	"writecache",
	"write",
	"creat",
	"remove",
	"rename",
	"link",
	"symlink",
	"mkdir",
	"rmdir",
	"readdir",
	"statfs"
};

char *stat_name[] = {
	"ok",		/* 0 */
};


/* data structure for nfs transaction (both call and reply) */
typedef struct nfstrans {
	char src[64], dst[64];	/* with respect to the CALL */
	u_long s, d;		/* in versions of src & dest */
	u_long xid;		/* xid + s + d is a unique identifier */
	u_int uid;
	u_long timestamp;	/* arrival time of the CALL */
	int ser;		/* ditto */
	long proc;		/* what procedure - tag for union */
	union {			/* call/reply struct for each proc */
		struct {	/* nfs_null*/
			int call; /* no args*/
			int repl;
		} null;
		struct {	/* nfs_getattr */
			fhandle_t call;
			struct nfsattrstat reply;
		} getattr;
		struct {	/* nfs_setattr */
			struct nfssaargs call;
			struct nfsattrstat reply;
		} setattr;
		struct {	/* nfs_root */
			int call;
			int reply;
		} root; /* should never get this one */
		struct {	/* nfs_lookup */
			struct nfsdiropargs call;
			struct nfsdiropres reply;
		} lookup;
		struct {	/* nfs_readlink */
			fhandle_t call;
			struct nfsrdlnres reply;
		} readlink;
		struct {	/* nfs_read */
			struct nfsreadargs call;
			struct nfsrdresult reply; /* we ignore data */
		} read;
		struct {	/* nfs_writecache */
			int call;
			int result;
		} writecache; /* should also never get this */
		struct {	/* nfs_write */
			struct nfswriteargs call;
			struct nfsattrstat reply;
		} write;
		struct {	/* nfs_create */
			struct nfscreatargs call;
			struct nfsdiropres reply;
		} create;
		struct {	/* nfs_remove */
			struct nfsdiropargs call;
			enum nfsstat reply;
		} remove;
		struct {	/* nfs_rename */
			struct nfsrnmargs call;
			enum nfsstat reply;
		} rename;
		struct {	/* nfs_link */
			struct nfslinkargs call;
			enum nfsstat reply;
		} link;
		struct {	/* nfs_symlink */
			struct nfsslargs call;
			enum nfsstat reply;
		} symlink;
		struct {	/* nfs_mkdir */
			struct nfscreatargs call;
			struct nfsdiropres reply;
		} mkdir;
		struct {	/* nfs_rmdir */
			struct nfsdiropargs call;
			enum nfsstat reply;
		} rmdir;
		struct {	/* nfs_readdir */
			struct nfsrddirargs call;
			struct nfsrddirres reply;
		} readdir;
		struct {	/* nfs_statfs */
			fhandle_t call;
			struct nfsstatfs reply;
		} statfs;
	} args;
	/* some data blocks so we can just malloc and free once */
	char b1[NFS_MAXPATHLEN];
	char b2[NFS_MAXPATHLEN];
	char b3[NFS_MAXNAMLEN];
	char b4[NFS_MAXNAMLEN];
	char b5[1024];	/* for whatever */
	struct nfstrans *next;
} nfstrans;

#define QSIZE 257
static nfstrans *transqueue[QSIZE]; /* hash table of trans queue entries */

#define MAXAGE 3		/* nuke any call over MAXAGE secs old */
#define MAXTRANS 1000

typedef struct hostrec {
	char name[64];
	u_long addr;
	int handlen;	/* or 0 for generic */
	struct hostrec *next;
} hostcache_t;

hostcache_t *hostcache[QSIZE];

extern long secs;	/* current time in secs */
extern int ser;	/* packets this second */

int defaultsize=0;
int size=0;	/* set before call to emit - used by phandle */

int good=0;
int bad=0;

int ok=0; notok=0; call=0;
int ngc=0, fgc=0;
int nt=0;
int lastgc=0;
int terseflag=0;

char *nuke[64]="Princeton.EDU";
char *host();

/*
 * This has the command line processing, the setup code, and the main loop.
 */
main(argc,argv)
int argc;
char **argv;
{
	int efd;
	ether_packet pkt;
	long pb[MTU/sizeof(long)];
	int len;
	int i;
	static char interface[32]=DEFAULT;
	char *flag;
	char *pn;
	char *c;
	int runtime;

	pn=argv[0];
	while (--argc) {
		if (**++argv == '-') {	/* flag */
			for (flag = ++*argv; *flag; flag++) {
				switch (*flag) {
				    case 'c':	/* concise output */
					terseflag++;
					break;
				    case 'h':	/* handle size */
				    case 'i':	/* interface */
				    case 't':	/* run time */
				    case 's':	/* suf. to nuke */
					enq(*flag);
					break;
				    default:
					usage(pn);
					exit(-1);
				}
			}
		} else {
			switch (deq()) {
			    case -1:
				usage(pn);
				exit(-1);
			    case 'h':	/* handle size */
				if ((c=index(*argv,':')) == NULL) {
					if(sscanf(*argv,"%d",&defaultsize)!=1) {
						usage(pn);
						exit(-1);
					}
				} else {
					*c++='\0';
					if (sscanf(c,"%d",&size)!=1) {
						usage(pn);
						exit(-1);
					}
					addsize(*argv,size);
				}
				break;
			    case 'i':	/* interface */
				strcpy(interface,*argv);
				break;
			    case 't':	/* run time */
				runtime=atoi(*argv);
				if (runtime) {
					signal(SIGALRM,die);
					alarm(runtime);
				} else {
					usage(pn);
					exit(-1);
				}
				break;
			    case 's':	/* nuke */
				strcpy(nuke,*argv);
				break;
			    default:	/* should never happen */
				fprintf(stderr,"Internal error\n");
				exit(-2);
			}
		}

	}
	if ((deq() != -1)) {
		usage(pn);
		exit(-1);
	}
	for (i=0; i<QSIZE; i++) {
		transqueue[i]=NULL;
		hostcache[i]=NULL;
	}
	if ((efd=net_open(interface)) <0) {
		perror(interface);
		exit(1);
	}
	sethostent(1);

	/*
	 * MAIN LOOP
	 */
	while(1) {
		if ((len=net_read(efd,&pkt,MTU))<0) {
			perror("net_read");
#ifdef NIT
			exit(-1);
#else
			/* this is to fix a sporatic ultrix bug */
			printf("# net_read returned <0!\n");
			close(efd);
			fflush(stdout);
			if ((efd=net_open(interface))<0)
				exit(1);
			continue;
#endif
		}
		bcopy(pkt.data,pb,len);
		if (do_inet(pb,len-14))
			good++;
		else
			bad++;
		ser++;
		if ((nt>MAXTRANS) && ((secs-lastgc)> MAXAGE))
			gc();
	}
}

usage(s)
     char *s;
{
	fprintf(stderr,"usage: %s [-t runtime] [-h <handlesize]\n", s);
	fprintf (stderr,"  [-h host:handle] [-i interface] [-s suf] [-c]\n");
}


#define QS 10
struct {
	int data[QS];
	int head;
	int tail;
} argq = {{0},0,0};

enq(f)
     char f;
{
	argq.tail++;
	argq.tail %= QS;
	if (argq.head==argq.tail) {
		fprintf(stderr,"Can't deal with this\n");
		exit(-2);
	}
	argq.data[argq.tail]=f;
}

deq()
{
	if (argq.head==argq.tail)
		return -1;
	argq.head++;
	argq.head %= QS;
	return(argq.data[argq.head]);
}

typedef struct sizelist {
	char name[64];
	int size;
	struct sizelist *next;
} sizelist;

sizelist *firstsize=NULL;

/* add to handle size list.
 * should check for duplicates.
 * should really be a hash table.
 */
addsize(h,s)
     char *h;
     int s;
{
	sizelist *sl;

	if ((sl=(sizelist *)malloc(sizeof (sizelist))) == NULL) {
		fprintf(stderr,"Give up! can't even malloc handle record\n");
		exit(-2);
	}
	strcpy(sl->name,h);
	sl->size=s;
	sl->next=firstsize;
	firstsize=sl;
}

/* look up host- return default size if not found */
hsize(h)
     char *h;
{
	sizelist *sl;
	sl=firstsize;
	while (sl!=NULL) {
		if (strcmp(h,sl->name)==0)
			return sl->size;
		sl=sl->next;
	}
	return defaultsize;
}
	


do_inet(buf,len)
char *buf;
int len;
{
	struct ip *pkt;
	int hl, plen;
	int offset;
	int proto;
	char *data;

	pkt=(struct ip *)buf;
	/* first make sure we have enough of the packet to deal with */
	if (len<sizeof(struct ip))
		return 0;
	hl = pkt->ip_hl*4;
	plen = ntohs(pkt->ip_len);
	if ((len<hl) || ((plen-hl>=8)&&(len<hl+8)))
		return 0;
	offset = htons(pkt->ip_off) & ~IP_DF & ~IP_MF;
	if (offset != 0)
		return 0;	/* make sure it's the first fragment */
	
	proto = pkt->ip_p;
	
	data=(char *)pkt + hl;
	switch (proto) {
	    case IPPROTO_UDP:	/* UDP, which is all we know how to deal w/ */
		return(udp(&pkt->ip_src,&pkt->ip_dst,data,len-hl));
		break;
	    default:
		return 0;
	}
	return 0;
}


/*
 * deal with udp packet
 */
udp(s,d,pkt,len)
     struct in_addr *s, *d; /* yes, really */
     char *pkt;
     int len;
{
	char src[255], dst[255];
	struct hostent *n;
	struct udphdr *u;
	struct rpc_msg *r;
	int direction;
	
	/* first, figure out whether its an nfs packet */
	u=(struct udphdr *)pkt;
	r=(struct rpc_msg *) ((char *)pkt+sizeof(struct udphdr));
	
	/*
	 * first make sure it's an nfs packet (or whatever else we deal w/).
	 * we make the unwholesome assumption that everybody uses NFS_PORT
	 * as the nfs port.  this is probably always true (i think).
	 * should probably fix this to allow it to be specified at runtime
	 * for each server, tho.
	 * do some simple sanity checks on the packet first
	 */
	direction=ntohl(r->rm_direction); /* this is cheating */
	if (ntohs(u->uh_sport) == NFS_PORT) {
		if (direction != REPLY)
			return 0; /* bad packet */
	} else if (ntohs(u->uh_dport) == NFS_PORT) {
		if (direction != CALL)
			return 0; /* bad packet */
	} else return 0;	/* insert code for other services here */

	strcpy(src,host(s));
	strcpy(dst,host(d));
	return (nfs(src,dst,*(int*)s,*(int*)d,r,len-sizeof(struct udphdr)));
}


/*
 * host name lookup
 */
char *host(h)
     struct in_addr *h;
{
	hostcache_t *cache;
	struct hostent *hr;
	u_long bucket;
	unsigned char *c;

	bucket=h->s_addr%QSIZE;
	cache= hostcache[bucket];
	while (cache != NULL) {
		if (cache->addr = h->s_addr)
			return (cache->name);
		cache=cache->next;
	}
	if ((cache = (hostcache_t *)malloc(sizeof(hostcache_t))) == NULL) {
		fprintf(stderr,"No memory for host cache!\n");
		die();
	}
	if ((hr=gethostbyaddr(h,sizeof(u_long),AF_INET)) == NULL) {
		c=(unsigned char*)h;
		sprintf(cache->name,"%d.%d.%d.%d",c[0],c[1],c[2],c[3]);
	} else {
		strcpy(cache->name,hr->h_name);
		eliminate(cache->name,nuke);
	}
	cache->addr = h->s_addr;
	cache->next = hostcache[bucket];
	hostcache[bucket] = cache;
	return(cache->name);
}

eliminate(s1,s2)
     char *s1, *s2;
{
	if ((*s1 == '\0') || (*s2=='\0'))
		return;
	while (*++s1)
		if (strcmp(s1,s2)==0) {
			*--s1='\0';
			return;
		}
}


/*
 * deal with rpc/nfs packet
 */
nfs(src,dst,s,d,b,len)
     char *src;
     char *dst;
     u_long s;	/* we know these are really u_long */
     u_long d;	/* should be in_addr */
     struct rpc_msg *b;
     int len;
{
	static struct rpc_msg rb;
	static struct rpc_msg *r = &rb;
	u_int uid;
	XDR xs;
	int stat=0;
	static char x[MAX_AUTH_BYTES];	/* we should fix this. */
	
	/* first, unxdr the rpc message header */
	xdrmem_create(&xs,b,len,XDR_DECODE);	/* create the stream */
	
	switch (ntohl(b->rm_direction)) { /* we have to peek first */
	    case CALL:	/* it's a call, so we have to queue it */
		/* un xdr it */
		r->rm_call.cb_cred.oa_base=x;
		if (!xdr_mycallmsg(&xs,r)) { /* the library one just sucks */
			printf ("#CALL: bad\n");
			break;
		}
		/* first, be sure it's really nfs */
		if (r->rm_call.cb_prog != NFS_PROGRAM) {
			fprintf(stderr,"  Non-NFS program on NFS_PORT\n");
			break;
		}
		/* extract auth info */
		uid = -1;	/* default */
		if (r->rm_call.cb_cred.oa_flavor == AUTH_UNIX) {
			XDR xx;
			xdrmem_create(&xx,x,r->rm_call.cb_cred.oa_length,
					 XDR_DECODE);
			xdr_uid(&xx,&uid);
			xdr_destroy(&xx);
		}
		stat = nfscall(src,dst,s,d,uid,&xs,
				r->rm_xid,r->rm_call.cb_proc);
		break;
	    case REPLY:
		if (!xdr_myreplymsg(&xs,r)) {  /* the library one also sucks */
			printf("#REPL: bad\n");
			break;
		}
		stat = nfsreply(src,dst,s,d,&xs,
				 r->rm_xid,r->rm_reply.rp_stat,
				 r->acpted_rply.ar_stat);
				/* ar_stat contains garbage if it wasn't */
				/* really accepted, but we never look @ it */
		break;
	    default:
		printf ("#bad direction\n");
	}
	xdr_destroy(&xs);
	return stat;
}

		



nfscall(src,dst,s,d,uid,arg,xid,proc)
     char *src;
     char *dst;
     u_long s,d;
     u_int uid;
     XDR *arg;
     u_long xid;
     long proc;
{
	nfstrans *t;
	u_long hashval;
	
	/* first make sure it's a reasonable proc # */
	if ((proc<0) || !(proc<RFS_NPROC))
		return 0;

	if ((t=(nfstrans *)malloc(sizeof(nfstrans))) == NULL) {
		gc();
		/* try again */
		if((t=(nfstrans *)malloc(sizeof(nfstrans))) == NULL) {
			fprintf(stderr,"Damn, out of memory\n");
			printf("#out transaction memory\n");
			die();
		}
	}
	if ( (*nfs_call_proc[proc])(arg,t)) {
		/* init the transaction and stick it in place on the queue */
		t->s = s;
		t->d = d;
		t->xid=xid;
		t->uid=uid;
		strcpy(t->src,src);
		strcpy(t->dst,dst);
		t->proc = proc;
		t->timestamp = secs;
		t->ser = ser;
		hashval = xid%QSIZE;
		if (hashval>QSIZE) {
			fprintf(stderr,"%u!!!\n",hashval);
			die();
		}
		t->next = transqueue[hashval];
		transqueue[hashval] = t;
		call++;
		nt++;
		return 1;
	} else {
		free(t);
		return 0;
	}
}

nfstrans *findanddq();


nfsreply(src,dst,s,d,arg,xid,stat,astat)
     char *src;
     char *dst;
     u_long s,d;
     XDR *arg;
     long xid;
     long stat;
     long astat;
{
	nfstrans *t;

	if ((t=findanddq(s,d,xid)) == NULL) {
		notok++;
		return 0;
	}
	if ((stat!=0)||(astat!=0) || ((*nfs_repl_proc[t->proc])(arg,t) == 0)) {
		ok++;
		nt--;
		free (t);
		return 0;
	}
	size=hsize(t->dst);	/* zero = unknown size */
	emit (t);
	nt--;
	free (t);
	ok++;
	return 1;
}



nfstrans *findanddq(s,d,xid)
     u_long s,d,xid;
{
	nfstrans *t;
	nfstrans *tt;
	nfstrans **ptr;
	u_long hashval;

	hashval = xid%QSIZE;
	t=transqueue[hashval];
	ptr = &transqueue[hashval];
	while (t != NULL) {
		if ((t->xid == xid) && (t->s == d) && (t->d == s)) {
			*ptr = t->next;
			return t;
		}
		/* quick gc test */
		if ((secs - t->timestamp) > MAXAGE) {
			fgc++;
			*ptr = t->next;
			tt=t;
			t=t->next;
			nt--;
			free (tt);
		}
		else {
			ptr = &t->next;
			t=t->next;
		}
	}
	return NULL;
}


gc()
{
	static int i;
	static nfstrans *t, **ptr, *tt;

	ngc++;
	printf("# garbage collecting %d\n",nt);
	for (i=0; i<QSIZE; i++) {
		t=transqueue[i];
		ptr = &transqueue[i];
		while (t!=NULL)
			if ((secs-t->timestamp)>MAXAGE) {
				*ptr = t->next;
				tt=t;
				t=t->next;
				nt--;
				free (tt);
			} else {
				ptr = &t->next;
				t=t->next;
			}
	}
	lastgc=secs;
	printf("#done %d\n",nt);
}

emit(t)
     nfstrans *t;
{
	printf("%d.%06d | %s | %s.%d | %s | ",
	       secs,ser,t->dst,t->src,t->uid,nfs_proc_name[t->proc]);
	if (!terseflag)
		(*nfs_emit_proc[t->proc])(t);
	printf("\n");
}


/*
 * functions for the various nfs procedures
 */


/* NFS_CALL */

call_nfs_null(xs,t)
     XDR *xs;
     nfstrans *t;
{
	/* this one's easy; no xdr data */
	return 1;
}

repl_nfs_null(xs,t)
     XDR *xs;
     nfstrans *t;
{
	return 1;
}

emit_nfs_null(t)
     nfstrans *t;
{
	printf("|");
}


/* NFS_GETATTR */

call_nfs_getattr(xs,t)
     XDR *xs;
     nfstrans *t;
{
	return(xdr_fhandle(xs,&t->args.getattr.call));
}

repl_nfs_getattr(xs,t)
     XDR *xs;
     nfstrans *t;
{
	return(xdr_attrstat(xs,&t->args.getattr.reply));
}

emit_nfs_getattr(t)
     nfstrans *t;
{
	fhandle_t *call;
	struct nfsattrstat *reply;

	call = &t->args.getattr.call;
	reply= &t->args.getattr.reply;
	phandle(call);
	printf("| %s",reply->ns_status?"failed":"ok");
	if (reply->ns_status == NFS_OK) {
		printf(", ");
		printfattr(&reply->ns_attr);
	}
}

/* NFS_SETATTR */

call_nfs_setattr(xs,t)
     XDR *xs;
     nfstrans *t;
{
	return(xdr_saargs(xs,&t->args.setattr.call));
}

repl_nfs_setattr(xs,t)
     XDR *xs;
     nfstrans *t;
{
	return(xdr_attrstat(xs,&t->args.setattr.reply));
}

emit_nfs_setattr(t)
     nfstrans *t;
{
	struct nfssaargs *call;
	struct nfsattrstat *reply;

	call = &t->args.setattr.call;
	reply= &t->args.setattr.reply;
	printf("{");
	phandle(&call->saa_fh);
	printf(", ");
	printsattr(&call->saa_sa);
	printf("} | %s",reply->ns_status?"failed":"ok");
	if (reply->ns_status == NFS_OK) {
		printf(", ");
		printfattr(&reply->ns_attr);
	}
}

/* NFS_ROOT */

call_nfs_root(xs,t)
     XDR *xs;
     nfstrans *t;
{
	return 1;
}

repl_nfs_root(xs,t)
     XDR *xs;
     nfstrans *t;
{
	return 1;
}

emit_nfs_root(t)
     nfstrans *t;
{
	printf("|");
}

/* NFS_LOOKUP */

call_nfs_lookup(xs,t)
     XDR *xs;
     nfstrans *t;
{
	/* ok, this one's a bit tricky, because we have to set up pointers */
	t->args.lookup.call.da_name = t->b1;
	return(xdr_diropargs(xs,&t->args.lookup.call));
}

repl_nfs_lookup(xs,t)
     XDR *xs;
     nfstrans *t;
{
	return(xdr_diropres(xs,&t->args.lookup.reply));
}

emit_nfs_lookup(t)
     nfstrans *t;
{
	struct nfsdiropargs *call;
	struct nfsdiropres *reply;

	call = &t->args.lookup.call;
	reply= &t->args.lookup.reply;
	printdiropargs(call);
	printf(" | %s",reply->dr_status?"failed":"ok");
	if (reply->dr_status == NFS_OK) {
		printf(", ");
		printdiropres(reply);
	}
}

/* NFS_READLINK */

call_nfs_readlink(xs,t)
     XDR *xs;
     nfstrans *t;
{
	return(xdr_fhandle(xs,&t->args.readlink.call));
}

repl_nfs_readlink(xs,t)
     XDR *xs;
     nfstrans *t;
{
	t->args.readlink.reply.rl_data = t->b1;
	return(xdr_rdlnres(xs,&t->args.readlink.reply));
}

emit_nfs_readlink(t)
     nfstrans *t;
{
	fhandle_t *call;
	struct nfsrdlnres *reply;

	call = &t->args.readlink.call;
	reply= &t->args.readlink.reply;
	phandle(call);
	printf("| %s",reply->rl_status?"failed":"ok");
	if (reply->rl_status == NFS_OK) {
		printf(", ");
		reply->rl_data[reply->rl_count] = '\0';
		printstr(reply->rl_data);
	}
}

/* NFS_READ */

call_nfs_read(xs,t)
     XDR *xs;
     nfstrans *t;
{
	return(xdr_readargs(xs,&t->args.read.call));
}

repl_nfs_read(xs,t)
     XDR *xs;
     nfstrans *t;
{
	static char buf[MTU];	/* garbage place to store data */

	t->args.read.reply.rr_data = buf;
	return(xdr_rdresult(xs,&t->args.read.reply));
}

emit_nfs_read(t)
     nfstrans *t;
{
	struct nfsreadargs *call;
	struct nfsrdresult *reply;

	call = &t->args.read.call;
	reply= &t->args.read.reply;
	printf("{");
	phandle(&call->ra_fhandle);
	printf(", %d, %d} |",
	       call->ra_offset,
	       call->ra_count);
	printf(" %s",reply->rr_status?"failed":"ok");
	if (reply->rr_status == NFS_OK) {
		printf(", %d",reply->rr_count);
	}
}

/* NFS_WRITECACHE */

call_nfs_writecache(xs,t)
     XDR *xs;
     nfstrans *t;
{
	return 1;
}

repl_nfs_writecache(xs,t)
     XDR *xs;
     nfstrans *t;
{
	return 1;
}

emit_nfs_writecache(t)
     nfstrans *t;
{
	printf("|");
}

/* NFS_WRITE */

call_nfs_write(xs,t)
     XDR *xs;
     nfstrans *t;
{
	static char buf[MTU];	/* another garbage place */

	t->args.write.call.wa_data = buf;
	return(xdr_writeargs(xs,&t->args.write.call));
}

repl_nfs_write(xs,t)
     XDR *xs;
     nfstrans *t;
{
	return(xdr_attrstat(xs,&t->args.write.reply));
}

emit_nfs_write(t)
     nfstrans *t;
{
	struct nfswriteargs *call;
	struct nfsattrstat *reply;

	call = &t->args.write.call;
	reply= &t->args.write.reply;
	printf("{");
	phandle(&call->wa_fhandle);
	printf(", %d, %d} |",
	       call->wa_offset,
	       call->wa_count);
	printf(" %s",reply->ns_status?"failed":"ok");
	if (reply->ns_status == NFS_OK) {
		printf(", ");
		printfattr(&reply->ns_attr);
	}
}

/* NFS_CREATE */

call_nfs_create(xs,t)
     XDR *xs;
     nfstrans *t;
{
	t->args.create.call.ca_da.da_name = t->b3;
	return(xdr_creatargs(xs,&t->args.create.call));
}

repl_nfs_create(xs,t)
     XDR *xs;
     nfstrans *t;
{
	return(xdr_diropres(xs,&t->args.create.reply));
}

emit_nfs_create(t)
     nfstrans *t;
{
	struct nfscreatargs *call;
	struct nfsdiropres *reply;

	call = &t->args.create.call;
	reply= &t->args.create.reply;
	printf("{");
	phandle(&call->ca_da.da_fhandle);
	printf(", ");
	printstr(call->ca_da.da_name);
	printf(", ");
	printsattr(&call->ca_sa);
	printf("} | %s",reply->dr_status?"failed":"ok");
	if (reply->dr_status == NFS_OK) {
		printf(", {");
		phandle(&reply->dr_fhandle);
		printf(", ");
		printfattr(&reply->dr_attr);
		printf("}");
	}
}

/* NFS_REMOVE */

call_nfs_remove(xs,t)
     XDR *xs;
     nfstrans *t;
{
	t->args.remove.call.da_name = t->b3;
	return(xdr_diropargs(xs,&t->args.remove.call));
}

repl_nfs_remove(xs,t)
     XDR *xs;
     nfstrans *t;
{
	return(xdr_enum(xs,&t->args.remove.reply));
}


emit_nfs_remove(t)
     nfstrans *t;
{
	struct nfsdiropargs *call;
	enum nfsstat *reply;

	call = &t->args.remove.call;
	reply= &t->args.remove.reply;
	printdiropargs(call);
	printf(" | %s", *reply?"failed":"ok");
}

/* NFS_RENAME */

call_nfs_rename(xs,t)
     XDR *xs;
     nfstrans *t;
{
	t->args.rename.call.rna_from.da_name = t->b3;
	t->args.rename.call.rna_to.da_name = t->b4;
	return(xdr_rnmargs(xs,&t->args.rename.call));
}

repl_nfs_rename(xs,t)
     XDR *xs;
     nfstrans *t;
{
	return(xdr_enum(xs,&t->args.rename.reply));
}


emit_nfs_rename(t)
     nfstrans *t;
{
	struct nfsrnmargs *call;
	enum nfsstat *reply;

	call = &t->args.rename.call;
	reply= &t->args.rename.reply;
	printdiropargs(&call->rna_from);
	printf(", ");
	printdiropargs(&call->rna_to);
	printf(" | %s",*reply?"failed":"ok");
}

/* NFS_LINK */

call_nfs_link(xs,t)
     XDR *xs;
     nfstrans *t;
{
	t->args.link.call.la_to.da_name = t->b3;
	return(xdr_linkargs(xs,&t->args.link.call));
}

repl_nfs_link(xs,t)
     XDR *xs;
     nfstrans *t;
{
	return(xdr_enum(xs,&t->args.link.reply));
}

emit_nfs_link(t)
     nfstrans *t;
{
	struct nfslinkargs *call;
	enum nfsstat *reply;

	call = &t->args.link.call;
	reply= &t->args.link.reply;
	printf("{");
	phandle(&call->la_from);
	printf(", ");
	printdiropargs(&call->la_to);
	printf("} | %s",*reply?"failed":"ok");
}

/* NFS_SYMLINK */

call_nfs_symlink(xs,t)
     XDR *xs;
     nfstrans *t;
{
	t->args.symlink.call.sla_from.da_name = t->b3;
	t->args.symlink.call.sla_tnm = t->b1;
	return(xdr_slargs(xs,&t->args.symlink.call));
}

repl_nfs_symlink(xs,t)
     XDR *xs;
     nfstrans *t;
{
	return(xdr_enum(xs,&t->args.symlink.reply));
}


emit_nfs_symlink(t)
     nfstrans *t;
{
	struct nfsslargs *call;
	enum nfsstat *reply;

	call = &t->args.symlink.call;
	reply= &t->args.symlink.reply;
	printf("{");
	printdiropargs(&call->sla_from);
	printf(", ");
	printstr(call->sla_tnm);
	printf(", ");
	printsattr(&call->sla_sa);
	printf("} | %s",*reply?"failed":"ok");
}

/* NFS_MKDIR */

call_nfs_mkdir(xs,t)
   XDR *xs;
   nfstrans *t;
{
	t->args.mkdir.call.ca_da.da_name = t->b3;
	return(xdr_creatargs(xs,&t->args.mkdir.call));
}

repl_nfs_mkdir(xs,t)
     XDR *xs;
     nfstrans *t;
{
	return(xdr_diropres(xs,&t->args.mkdir.reply));
}

emit_nfs_mkdir(t)
     nfstrans *t;
{
	struct nfscreatargs *call;
	struct nfsdiropres *reply;

	call = &t->args.mkdir.call;
	reply= &t->args.mkdir.reply;
	printf("{");
	phandle(&call->ca_da.da_fhandle);
	printf(", ");
	printstr(call->ca_da.da_name);
	printf(", ");
	printsattr(&call->ca_sa);
	printf("} | %s",reply->dr_status?"failed":"ok");
	if (reply->dr_status == NFS_OK) {
		printf(", {");
		phandle(&reply->dr_fhandle);
		printf(", ");
		printfattr(&reply->dr_attr);
		printf("}");
	}
}

/* NFS_RMDIR */

call_nfs_rmdir(xs,t)
     XDR *xs;
     nfstrans *t;
{
	t->args.rmdir.call.da_name = t->b3;
	return(xdr_diropargs(xs,&t->args.rmdir.call));
}

repl_nfs_rmdir(xs,t)
     XDR *xs;
     nfstrans *t;
{
	return(xdr_enum(xs,&t->args.rmdir.reply));
}

emit_nfs_rmdir(t)
     nfstrans *t;
{
	struct nfsdiropargs *call;
	enum nfsstat *reply;

	call = &t->args.rmdir.call;
	reply= &t->args.rmdir.reply;
	printdiropargs(call);
	printf(" | %s",*reply?"failed":"ok");
}

/* NFS_READDIR */

call_nfs_readdir(xs,t)
     XDR *xs;
     nfstrans *t;
{
	return(xdr_rddirargs(xs,&t->args.readdir.call));
}

repl_nfs_readdir(xs,t)
     XDR *xs;
     nfstrans *t;
{
	t->args.readdir.reply.rd_entries = NULL;  /* we dont use */
	return(xdr_getrddirres(xs,&t->args.readdir.reply));
}

emit_nfs_readdir(t)
     nfstrans *t;
{
	fhandle_t *call;
	struct nfsattrstat *reply;

	phandle(&t->args.readdir.call.rda_fh);
	printf(" | %s",t->args.readdir.reply.rd_status?"failed":"ok");

}

/* NFS_STATFS */

call_nfs_statfs(xs,t)
     XDR *xs;
     nfstrans *t;
{
	return(xdr_fhandle(xs,&t->args.statfs.call));
}

repl_nfs_statfs(xs,t)
     XDR *xs;
     nfstrans *t;
{
	return(xdr_statfs(xs,&t->args.readdir.reply));
}

emit_nfs_statfs(t)
     nfstrans *t;
{
	fhandle_t *call;
	struct nfsattrstat *reply;

	printf(" |");
	return 1;
}

#define HSIZE 14 /* max reasonable handle size for funny file handles */
#define min(x,y) ((x)>(y)?(y):(x))

phandle(h)
     fhandle_t *h;
{
	u_long i;
	u_long j;
	u_char *p;
	unsigned char buf[64];
	int si;

	buf[0]='\0';
	j=min(h->fh_len,ntohs(h->fh_len));
	j-=4;
	si=size;
	if (si) {
		p=(u_char *)h;
		while(si--)
			sprintf(buf,"%s%02x",buf,*(p++));
	} else if (((j<0) || (j>FHMAXDATA)) || /* guess size */
	    !(h->fh_fsid.val[0]+h->fh_fsid.val[1])) {/* funny (utrix/mach) */
		p = (u_char *) h;
		for (i=0; i<HSIZE; i++)
			sprintf(buf,"%s%02x",buf,p[i]);
	} else {				/* regular kind */
		sprintf(buf,"%08x%08x",(h->fh_fsid.val[0]),
		       (h->fh_fsid.val[1]));
		for(i=0;i<j; i++)
			sprintf(buf,"%s%02x",buf,(unsigned char)h->fh_data[i]);
	}
	p=buf;
	while (*p && (*p=='0'))
		p++;
	printf("\"%s\"",p);
}


/*
 * XDR routines
 */


xdr_uid(xs,u)
     XDR *xs;
     u_int *u;
{
	static char junk[255];
	u_int stamp;
	return (xdr_int(xs,&stamp) && xdr_string(xs,junk,255)
	    && xdr_u_int(xs,u));
}


xdr_myreplymsg(xs,r)
     XDR *xs;
     struct rpc_msg *r;
{
	static char x[MAX_AUTH_BYTES];
	
	if (xdr_u_long(xs, &(r->rm_xid)) &&
	    xdr_enum(xs, &(r->rm_direction)) &&
	    (r->rm_direction==REPLY) &&
	    xdr_enum(xs, &(r->rm_reply.rp_stat)) &&
	    (r->rm_reply.rp_stat==MSG_ACCEPTED) &&  /* ignore if rejected */
	    ((r->acpted_rply.ar_verf.oa_base=x),1) && /* this is disgusting */
	    xdr_opaque_auth(xs, &(r->acpted_rply.ar_verf)))
		return (xdr_enum(xs, &(r->acpted_rply.ar_stat)));
	return (FALSE);
	/* note that we now point to the begining of the reply data */
}

xdr_mycallmsg(xs,r)
     XDR *xs;
     struct rpc_msg *r;
{
	static char x[MAX_AUTH_BYTES];	/* stupid ugly hack */

        if (xdr_u_long(xs, &(r->rm_xid)) &&
            xdr_enum(xs, &(r->rm_direction)) &&
            (r->rm_direction == CALL) &&
            xdr_u_long(xs, &(r->rm_call.cb_rpcvers)) &&
            (r->rm_call.cb_rpcvers == RPC_MSG_VERSION) &&
            xdr_u_long(xs, &(r->rm_call.cb_prog)) &&
            xdr_u_long(xs, &(r->rm_call.cb_vers)) &&
            xdr_u_long(xs, &(r->rm_call.cb_proc)) &&
/*	    ((r->rm_call.cb_cred.oa_base=x),1) && */
            xdr_opaque_auth(xs, &(r->rm_call.cb_cred))) {
		r->rm_call.cb_verf.oa_base=x;
		return (xdr_opaque_auth(xs, &(r->rm_call.cb_verf)));
	}
	return FALSE;
}


printdiropres(r)
     struct nfsdiropres *r;
{
	printf("{");
	phandle(&r->dr_fhandle);
	printf(", ");
	printfattr(&r->dr_attr);
	printf("}");
}


printfattr(f)
     struct nfsfattr *f;
{
	printf("{0%o, %d, %d, 0x%x}",
       		f->na_mode,
      		f->na_uid,
       		f->na_size,
	        f->na_nodeid);
}

printsattr(s)
     struct nfssattr *s;
{
	printf("{0%o, %d, %d}",
	       s->sa_mode,
	       s->sa_uid,
	       s->sa_size);
}


printdiropargs(d)
     struct nfsdiropargs *d;
{
	printf("{");
	phandle(&d->da_fhandle);
	printf(", ");
	printstr(d->da_name);
	printf("}");
}

printstr(s)
     char *s;
{
	putchar('\"');
	while (*s) {
		switch (*s) {
		    case '\\':
		    case '\n':
		    case '\"':
		    case '|':
			putchar('\\');
		    default:
			putchar (*s);
		}
		++s;
	}
	putchar('\"');
}
SHAR_EOF
fi
if test -f 'rpcspy.h'
then
	echo shar: "will not over-write existing file 'rpcspy.h'"
else
cat << \SHAR_EOF > 'rpcspy.h'
#define MTU 1536

#ifdef NIT
#include <net/nit_if.h>
#include <net/nit_pf.h>
#define DEFAULT "le0"
#else
#include <net/pfilt.h>
#define DEFAULT "ln0"
#endif
typedef struct ether_packet {
#ifdef NIT	/* sunos */
#ifdef STAMPS
	unsigned char stamp[sizeof(struct nit_iftime)+sizeof(struct nit_ifdrops)];
#endif
#else	/* ultrix packet filter */
	unsigned char  stamp[sizeof (struct enstamp)];
#endif
	unsigned char  dhost[6];
	unsigned char  shost[6];
	unsigned short type;
	unsigned char data[MTU];
} ether_packet;

SHAR_EOF
fi
if test -f 'xdr_nfs.c'
then
	echo shar: "will not over-write existing file 'xdr_nfs.c'"
else
cat << \SHAR_EOF > 'xdr_nfs.c'
/* NFS XDR decoding functions
 * Matt Blaze
 * Copyright 1991 Matt Blaze.
 * May be freely reproduced for non-commerical use.
 * All other rights, including use for direct commerical advantage or
 * use in a commerical product, are reserved by the author. 
 * 
 *
 * Note: these are not complete - just enough for rpcspy.
 * In particular, a lot of the data pointers are never filled in,
 * and many functions are decode-only.
 * If you want to use this for a real nfs client or server, you'll need to
 * change the readreply, writeargs, directory reply and statfs functions.
 * If it's going to be in the kernel, you probably will need to change things
 * to deal with mbufs as well.
 * These functions are probably too slow for serious use.
 */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/errno.h>
#include <rpc/types.h>
#include <rpc/xdr.h>
#include <nfs/nfs.h>
#include <netinet/in.h>

bool_t xdr_fattr();
bool_t xdr_drok ();
bool_t xdr_srok ();
bool_t xdr_rrok ();

struct xdr_discrim d_attrstat[2] = {
	{NFS_OK, xdr_fattr},{__dontcare__, NULL}};

xdr_attrstat(xp, as)
     XDR *xp;
     struct nfsattrstat *as;
{
	return(xdr_union(xp, &as->ns_status, &as->ns_attr,
			 d_attrstat,NULL));
}

xdr_creatargs(xp,ca)
     XDR *xp;
     struct nfscreatargs *ca;
{
	return(xdr_diropargs(xp, &ca->ca_da) &&
	       xdr_sattr(xp, &ca->ca_sa));
}

xdr_diropargs(xp,da)
     XDR *xp;
     struct nfsdiropargs *da;
{
	return(xdr_fhandle(xp, &da->da_fhandle) &&
	       xdr_string(xp, &da->da_name,NFS_MAXNAMLEN));
}

struct xdr_discrim d_diropres[2]={
	{NFS_OK, xdr_drok},
	{__dontcare__,NULL}};

xdr_diropres(xp,dr)
     XDR *xp;
     struct nfsdiropres *dr;
{
	return (xdr_union(xp,&dr->dr_status, &dr->dr_drok,
			  d_diropres, NULL));
}

xdr_drok(xp,dok)
     XDR *xp;
     struct nfsdrok *dok;
{
	return(xdr_fhandle(xp,&dok->drok_fhandle) &&
	       xdr_fattr(xp,&dok->drok_attr));
}

xdr_fattr(xp,fa)
     XDR *xp;
     struct nfsfattr *fa;
{
	return(xdr_enum(xp,&fa->na_type) &&
	       xdr_u_long(xp,&fa->na_mode)&&
	       xdr_u_long(xp,&fa->na_nlink)&&
	       xdr_u_long(xp,&fa->na_uid)&&
	       xdr_u_long(xp,&fa->na_gid)&&
	       xdr_u_long(xp,&fa->na_size)&&
	       xdr_u_long(xp,&fa->na_blocksize)&&
	       xdr_u_long(xp,&fa->na_rdev)&&
	       xdr_u_long(xp,&fa->na_blocks)&&
	       xdr_u_long(xp,&fa->na_fsid)&&
	       xdr_u_long(xp,&fa->na_nodeid)&&
	       xdr_timeval(xp,&fa->na_atime)&&
	       xdr_timeval(xp,&fa->na_mtime)&&
	       xdr_timeval(xp,&fa->na_ctime));
}

xdr_fhandle(xp, fh)
     XDR *xp;
     fhandle_t *fh;
{
	return(xdr_opaque(xp,fh,NFS_FHSIZE));
}

xdr_linkargs(xp, la)
     XDR *xp;
     struct nfslinkargs *la;
{
	return(xdr_fhandle(xp, &la->la_from) &&
	       xdr_diropargs(xp, &la->la_to));
}

xdr_rddirargs(xp, ra)
     XDR *xp;
     struct nfsrddirargs *ra;
{
	return (xdr_fhandle(xp, &ra->rda_fh) &&
		xdr_u_long(xp, &ra->rda_offset)&&
		xdr_u_long(xp, &ra->rda_count));
}

/* this is incomplete! just decodes the status. no put function, either */
xdr_getrddirres(xp, rr)
     XDR *xp;
     struct nfsrddirres *rr;
{
	return(xdr_enum(xp, &rr->rd_status));
}


struct xdr_discrim d_rdlnres[2]={
	{NFS_OK, xdr_srok},
	{__dontcare__,NULL}};

xdr_rdlnres(xp, rr)
     XDR *xp;
     struct nfsrdlnres *rr;
{
	return (xdr_union(xp, &rr->rl_status, &rr->rl_srok,
			  d_rdlnres, NULL));
}

struct xdr_discrim d_rdres[2]={
	{NFS_OK, xdr_rrok},
	{__dontcare__,NULL}};

xdr_rdresult(xp, rr)
     XDR *xp;
     struct nfsrdresult *rr;
{
	return(xdr_union(xp, &rr->rr_status, &rr->rr_ok,
			 d_rdres, NULL));
}

xdr_readargs(xp, ra)
     XDR *xp;
     struct nfsreadargs *ra;
{
	return (xdr_fhandle(xp, &ra->ra_fhandle)&&
		xdr_long(xp, &ra->ra_offset)&&
		xdr_long(xp, &ra->ra_count)&&
		xdr_long(xp, &ra->ra_totcount));
}

xdr_rnmargs(xp, ra)
     XDR *xp;
     struct nfsrnmargs *ra;
{
	return (xdr_diropargs(xp, &ra->rna_from) &&
		xdr_diropargs(xp, &ra->rna_to));
}




/* this isn't complete.  doesnt fill in the data field */
xdr_rrok(xp, rok)
     XDR *xp;
     struct nfsrrok *rok;
{
	return (xdr_fattr(xp, &rok->rrok_attr) &&
		xdr_int(xp, &rok->rrok_count));
	/* should fill in count bytes of data to be complete */
}

xdr_saargs(xp, sa)
     XDR *xp;
     struct nfssaargs *sa;
{
	return(xdr_fhandle(xp, &sa->saa_fh)&&
	       xdr_sattr(xp, &sa->saa_sa));
}

xdr_sattr(xp, sa)
     XDR *xp;
     struct nfssattr *sa;
{
	return (xdr_u_long(xp, &sa->sa_mode)&&
		xdr_u_long(xp, &sa->sa_uid)&&
		xdr_u_long(xp, &sa->sa_gid)&&
		xdr_u_long(xp, &sa->sa_size)&&
		xdr_timeval(xp, &sa->sa_atime)&&
		xdr_timeval(xp, &sa->sa_mtime));
}

xdr_slargs(xp,sa)
     XDR *xp;
     struct nfsslargs *sa;
{
	return (xdr_diropargs(xp, &sa->sla_from)&&
		xdr_string(xp, &sa->sla_tnm, NFS_MAXPATHLEN)&&
		xdr_sattr(xp, &sa->sla_sa));
}

xdr_srok(xp, sr)
     XDR *xp;
     struct nfssrok *sr;
{
	return (xdr_bytes(xp, &sr->srok_data,
			  &sr->srok_count, NFS_MAXPATHLEN));
}

xdr_timeval(xp, tv)
     XDR *xp;
     struct timeval *tv;
{
	return (xdr_long(xp, &tv->tv_sec)&&
		xdr_long(xp, &tv->tv_usec));
}

/* this is incomplete! does not fill in data field */
xdr_writeargs(xp, wa)
     XDR *xp;
     struct nfswriteargs *wa;
{
	return(xdr_fhandle(xp, &wa->wa_fhandle)&&
	       xdr_long(xp, &wa->wa_begoff)&&
	       xdr_long(xp, &wa->wa_offset)&&
	       xdr_long(xp, &wa->wa_totcount)&&
	       xdr_long(xp, &wa->wa_count)); 	/* should be xdr_bytes */
}


/* this is incomplete! just checks the return status */
xdr_statfs(xp, fs)
     XDR *xp;
     struct nfsstatfs *fs;
{
	return(xdr_enum(xp, &fs->fs_status));
}
SHAR_EOF
fi
exit 0
#	End of shell archive
