print-nfs.c revision 17680
1/*
2 * Copyright (c) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that: (1) source code distributions
7 * retain the above copyright notice and this paragraph in its entirety, (2)
8 * distributions including binary code include the above copyright notice and
9 * this paragraph in its entirety in the documentation or other materials
10 * provided with the distribution, and (3) all advertising materials mentioning
11 * features or use of this software display the following acknowledgement:
12 * ``This product includes software developed by the University of California,
13 * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
14 * the University nor the names of its contributors may be used to endorse
15 * or promote products derived from this software without specific prior
16 * written permission.
17 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
18 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
19 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
20 */
21
22#ifndef lint
23static char rcsid[] =
24    "@(#) $Header: print-nfs.c,v 1.56 96/07/23 14:17:25 leres Exp $ (LBL)";
25#endif
26
27#include <sys/param.h>
28#include <sys/time.h>
29#include <sys/socket.h>
30
31#if __STDC__
32struct mbuf;
33struct rtentry;
34#endif
35#include <net/if.h>
36
37#include <netinet/in.h>
38#include <netinet/if_ether.h>
39#include <netinet/in_systm.h>
40#include <netinet/ip.h>
41#include <netinet/ip_var.h>
42
43#include <rpc/rpc.h>
44
45#include <ctype.h>
46#include <pcap.h>
47#include <stdio.h>
48#include <string.h>
49
50#include "interface.h"
51#include "addrtoname.h"
52
53#include "nfsv2.h"
54#include "nfsfh.h"
55
56static void nfs_printfh(const u_int32_t *);
57static void xid_map_enter(const struct rpc_msg *, const struct ip *);
58static int32_t xid_map_find(const struct rpc_msg *, const struct ip *);
59static void interp_reply(const struct rpc_msg *, u_int32_t, u_int);
60
61void
62nfsreply_print(register const u_char *bp, u_int length,
63	       register const u_char *bp2)
64{
65	register const struct rpc_msg *rp;
66	register const struct ip *ip;
67	int32_t proc;
68
69	rp = (const struct rpc_msg *)bp;
70	ip = (const struct ip *)bp2;
71
72	if (!nflag)
73		(void)printf("%s.nfs > %s.%x: reply %s %d",
74			     ipaddr_string(&ip->ip_src),
75			     ipaddr_string(&ip->ip_dst),
76			     (u_int32_t)ntohl(rp->rm_xid),
77			     ntohl(rp->rm_reply.rp_stat) == MSG_ACCEPTED?
78				     "ok":"ERR",
79			     length);
80	else
81		(void)printf("%s.%x > %s.%x: reply %s %d",
82			     ipaddr_string(&ip->ip_src),
83			     NFS_PORT,
84			     ipaddr_string(&ip->ip_dst),
85			     (u_int32_t)ntohl(rp->rm_xid),
86			     ntohl(rp->rm_reply.rp_stat) == MSG_ACCEPTED?
87			     	"ok":"ERR",
88			     length);
89
90	proc = xid_map_find(rp, ip);
91	if (proc >= 0)
92		interp_reply(rp, (u_int32_t)proc, length);
93}
94
95/*
96 * Return a pointer to the first file handle in the packet.
97 * If the packet was truncated, return 0.
98 */
99static const u_int32_t *
100parsereq(register const struct rpc_msg *rp, register u_int length)
101{
102	register const u_int32_t *dp;
103	register u_int len;
104
105	/*
106	 * find the start of the req data (if we captured it)
107	 */
108	dp = (u_int32_t *)&rp->rm_call.cb_cred;
109	TCHECK(dp[1]);
110	len = ntohl(dp[1]);
111	if (len < length) {
112		dp += (len + (2 * sizeof(*dp) + 3)) / sizeof(*dp);
113		TCHECK(dp[1]);
114		len = ntohl(dp[1]);
115		if (len < length) {
116			dp += (len + (2 * sizeof(*dp) + 3)) / sizeof(*dp);
117			TCHECK2(dp[0], 0);
118			return (dp);
119		}
120	}
121trunc:
122	return (0);
123}
124
125/*
126 * Print out an NFS file handle and return a pointer to following word.
127 * If packet was truncated, return 0.
128 */
129static const u_int32_t *
130parsefh(register const u_int32_t *dp)
131{
132	if (dp + 8 <= (u_int32_t *)snapend) {
133		nfs_printfh(dp);
134		return (dp + 8);
135	}
136	return (0);
137}
138
139/*
140 * Print out a file name and return pointer to 32-bit word past it.
141 * If packet was truncated, return 0.
142 */
143static const u_int32_t *
144parsefn(register const u_int32_t *dp)
145{
146	register u_int32_t len;
147	register const u_char *cp;
148
149	/* Bail if we don't have the string length */
150	if ((u_char *)dp > snapend - sizeof(*dp))
151		return(0);
152
153	/* Fetch string length; convert to host order */
154	len = *dp++;
155	NTOHL(len);
156
157	cp = (u_char *)dp;
158	/* Update 32-bit pointer (NFS filenames padded to 32-bit boundaries) */
159	dp += ((len + 3) & ~3) / sizeof(*dp);
160	if ((u_char *)dp > snapend)
161		return (0);
162	/* XXX seems like we should be checking the length */
163	putchar('"');
164	(void) fn_printn(cp, len, NULL);
165	putchar('"');
166
167	return (dp);
168}
169
170/*
171 * Print out file handle and file name.
172 * Return pointer to 32-bit word past file name.
173 * If packet was truncated (or there was some other error), return 0.
174 */
175static const u_int32_t *
176parsefhn(register const u_int32_t *dp)
177{
178	dp = parsefh(dp);
179	if (dp == 0)
180		return (0);
181	putchar(' ');
182	return (parsefn(dp));
183}
184
185void
186nfsreq_print(register const u_char *bp, u_int length,
187    register const u_char *bp2)
188{
189	register const struct rpc_msg *rp;
190	register const struct ip *ip;
191	register const u_int32_t *dp;
192
193	rp = (const struct rpc_msg *)bp;
194	ip = (const struct ip *)bp2;
195	if (!nflag)
196		(void)printf("%s.%x > %s.nfs: %d",
197			     ipaddr_string(&ip->ip_src),
198			     (u_int32_t)ntohl(rp->rm_xid),
199			     ipaddr_string(&ip->ip_dst),
200			     length);
201	else
202		(void)printf("%s.%x > %s.%x: %d",
203			     ipaddr_string(&ip->ip_src),
204			     (u_int32_t)ntohl(rp->rm_xid),
205			     ipaddr_string(&ip->ip_dst),
206			     NFS_PORT,
207			     length);
208
209	xid_map_enter(rp, ip);	/* record proc number for later on */
210
211	switch (ntohl(rp->rm_call.cb_proc)) {
212#ifdef NFSPROC_NOOP
213	case NFSPROC_NOOP:
214		printf(" nop");
215		return;
216#else
217#define NFSPROC_NOOP -1
218#endif
219	case NFSPROC_NULL:
220		printf(" null");
221		return;
222
223	case NFSPROC_GETATTR:
224		printf(" getattr");
225		if ((dp = parsereq(rp, length)) != 0 && parsefh(dp) != 0)
226			return;
227		break;
228
229	case NFSPROC_SETATTR:
230		printf(" setattr");
231		if ((dp = parsereq(rp, length)) != 0 && parsefh(dp) != 0)
232			return;
233		break;
234
235#if NFSPROC_ROOT != NFSPROC_NOOP
236	case NFSPROC_ROOT:
237		printf(" root");
238		break;
239#endif
240	case NFSPROC_LOOKUP:
241		printf(" lookup");
242		if ((dp = parsereq(rp, length)) != 0 && parsefhn(dp) != 0)
243			return;
244		break;
245
246	case NFSPROC_READLINK:
247		printf(" readlink");
248		if ((dp = parsereq(rp, length)) != 0 && parsefh(dp) != 0)
249			return;
250		break;
251
252	case NFSPROC_READ:
253		printf(" read");
254		if ((dp = parsereq(rp, length)) != 0 &&
255		    (dp = parsefh(dp)) != 0) {
256			TCHECK2(dp[0], 3 * sizeof(*dp));
257			printf(" %u bytes @ %u",
258			    (u_int32_t)ntohl(dp[1]),
259			    (u_int32_t)ntohl(dp[0]));
260			return;
261		}
262		break;
263
264#if NFSPROC_WRITECACHE != NFSPROC_NOOP
265	case NFSPROC_WRITECACHE:
266		printf(" writecache");
267		if ((dp = parsereq(rp, length)) != 0 &&
268		    (dp = parsefh(dp)) != 0) {
269			TCHECK2(dp[0], 4 * sizeof(*dp));
270			printf(" %u (%u) bytes @ %u (%u)",
271			    (u_int32_t)ntohl(dp[3]),
272			    (u_int32_t)ntohl(dp[2]),
273			    (u_int32_t)ntohl(dp[1]),
274			    (u_int32_t)ntohl(dp[0]));
275			return;
276		}
277		break;
278#endif
279	case NFSPROC_WRITE:
280		printf(" write");
281		if ((dp = parsereq(rp, length)) != 0 &&
282		    (dp = parsefh(dp)) != 0) {
283			TCHECK2(dp[0], 4 * sizeof(*dp));
284			printf(" %u (%u) bytes @ %u (%u)",
285			    (u_int32_t)ntohl(dp[3]),
286			    (u_int32_t)ntohl(dp[2]),
287			    (u_int32_t)ntohl(dp[1]),
288			    (u_int32_t)ntohl(dp[0]));
289			return;
290		}
291		break;
292
293	case NFSPROC_CREATE:
294		printf(" create");
295		if ((dp = parsereq(rp, length)) != 0 && parsefhn(dp) != 0)
296			return;
297		break;
298
299	case NFSPROC_REMOVE:
300		printf(" remove");
301		if ((dp = parsereq(rp, length)) != 0 && parsefhn(dp) != 0)
302			return;
303		break;
304
305	case NFSPROC_RENAME:
306		printf(" rename");
307		if ((dp = parsereq(rp, length)) != 0 &&
308		    (dp = parsefhn(dp)) != 0) {
309			fputs(" ->", stdout);
310			if (parsefhn(dp) != 0)
311				return;
312		}
313		break;
314
315	case NFSPROC_LINK:
316		printf(" link");
317		if ((dp = parsereq(rp, length)) != 0 &&
318		    (dp = parsefh(dp)) != 0) {
319			fputs(" ->", stdout);
320			if (parsefhn(dp) != 0)
321				return;
322		}
323		break;
324
325	case NFSPROC_SYMLINK:
326		printf(" symlink");
327		if ((dp = parsereq(rp, length)) != 0 &&
328		    (dp = parsefhn(dp)) != 0) {
329			fputs(" -> ", stdout);
330			if (parsefn(dp) != 0)
331				return;
332		}
333		break;
334
335	case NFSPROC_MKDIR:
336		printf(" mkdir");
337		if ((dp = parsereq(rp, length)) != 0 && parsefhn(dp) != 0)
338			return;
339		break;
340
341	case NFSPROC_RMDIR:
342		printf(" rmdir");
343		if ((dp = parsereq(rp, length)) != 0 && parsefhn(dp) != 0)
344			return;
345		break;
346
347	case NFSPROC_READDIR:
348		printf(" readdir");
349		if ((dp = parsereq(rp, length)) != 0 &&
350		    (dp = parsefh(dp)) != 0) {
351			TCHECK2(dp[0], 2 * sizeof(*dp));
352			/*
353			 * Print the offset as signed, since -1 is common,
354			 * but offsets > 2^31 aren't.
355			 */
356			printf(" %u bytes @ %d",
357			    (u_int32_t)ntohl(dp[1]),
358			    (u_int32_t)ntohl(dp[0]));
359			return;
360		}
361		break;
362
363	case NFSPROC_STATFS:
364		printf(" statfs");
365		if ((dp = parsereq(rp, length)) != 0 && parsefh(dp) != 0)
366			return;
367		break;
368
369	default:
370		printf(" proc-%u", (u_int32_t)ntohl(rp->rm_call.cb_proc));
371		return;
372	}
373trunc:
374	fputs(" [|nfs]", stdout);
375}
376
377/*
378 * Print out an NFS file handle.
379 * We assume packet was not truncated before the end of the
380 * file handle pointed to by dp.
381 *
382 * Note: new version (using portable file-handle parser) doesn't produce
383 * generation number.  It probably could be made to do that, with some
384 * additional hacking on the parser code.
385 */
386static void
387nfs_printfh(register const u_int32_t *dp)
388{
389	my_fsid fsid;
390	ino_t ino;
391	char *sfsname = NULL;
392
393	Parse_fh((caddr_t*)dp, &fsid, &ino, NULL, &sfsname, 0);
394
395	if (sfsname) {
396	    /* file system ID is ASCII, not numeric, for this server OS */
397	    static char temp[NFS_FHSIZE+1];
398
399	    /* Make sure string is null-terminated */
400	    strncpy(temp, sfsname, NFS_FHSIZE);
401	    /* Remove trailing spaces */
402	    sfsname = strchr(temp, ' ');
403	    if (sfsname)
404		*sfsname = 0;
405
406	    (void)printf(" fh %s/%u", temp, (u_int32_t)ino);
407	}
408	else {
409	    (void)printf(" fh %u,%u/%u",
410		fsid.fsid_dev.Major,
411		fsid.fsid_dev.Minor,
412		(u_int32_t)ino);
413	}
414}
415
416/*
417 * Maintain a small cache of recent client.XID.server/proc pairs, to allow
418 * us to match up replies with requests and thus to know how to parse
419 * the reply.
420 */
421
422struct xid_map_entry {
423	u_int32_t		xid;		/* transaction ID (net order) */
424	struct in_addr	client;		/* client IP address (net order) */
425	struct in_addr	server;		/* server IP address (net order) */
426	u_int32_t		proc;		/* call proc number (host order) */
427};
428
429/*
430 * Map entries are kept in an array that we manage as a ring;
431 * new entries are always added at the tail of the ring.  Initially,
432 * all the entries are zero and hence don't match anything.
433 */
434
435#define	XIDMAPSIZE	64
436
437struct xid_map_entry xid_map[XIDMAPSIZE];
438
439int	xid_map_next = 0;
440int	xid_map_hint = 0;
441
442static void
443xid_map_enter(const struct rpc_msg *rp, const struct ip *ip)
444{
445	struct xid_map_entry *xmep;
446
447	xmep = &xid_map[xid_map_next];
448
449	if (++xid_map_next >= XIDMAPSIZE)
450		xid_map_next = 0;
451
452	xmep->xid = rp->rm_xid;
453	xmep->client = ip->ip_src;
454	xmep->server = ip->ip_dst;
455	xmep->proc = ntohl(rp->rm_call.cb_proc);
456}
457
458/* Returns NFSPROC_xxx or -1 on failure */
459static int32_t
460xid_map_find(const struct rpc_msg *rp, const struct ip *ip)
461{
462	int i;
463	struct xid_map_entry *xmep;
464	u_int32_t xid = rp->rm_xid;
465	u_int32_t clip = ip->ip_dst.s_addr;
466	u_int32_t sip = ip->ip_src.s_addr;
467
468	/* Start searching from where we last left off */
469	i = xid_map_hint;
470	do {
471		xmep = &xid_map[i];
472		if (xmep->xid == xid && xmep->client.s_addr == clip &&
473		    xmep->server.s_addr == sip) {
474			/* match */
475			xid_map_hint = i;
476			return ((int32_t)xmep->proc);
477		}
478		if (++i >= XIDMAPSIZE)
479			i = 0;
480	} while (i != xid_map_hint);
481
482	/* search failed */
483	return(-1);
484}
485
486/*
487 * Routines for parsing reply packets
488 */
489
490/*
491 * Return a pointer to the beginning of the actual results.
492 * If the packet was truncated, return 0.
493 */
494static const u_int32_t *
495parserep(register const struct rpc_msg *rp, register u_int length)
496{
497	register const u_int32_t *dp;
498	u_int len;
499	enum accept_stat astat;
500
501	/*
502	 * Portability note:
503	 * Here we find the address of the ar_verf credentials.
504	 * Originally, this calculation was
505	 *	dp = (u_int32_t *)&rp->rm_reply.rp_acpt.ar_verf
506	 * On the wire, the rp_acpt field starts immediately after
507	 * the (32 bit) rp_stat field.  However, rp_acpt (which is a
508	 * "struct accepted_reply") contains a "struct opaque_auth",
509	 * whose internal representation contains a pointer, so on a
510	 * 64-bit machine the compiler inserts 32 bits of padding
511	 * before rp->rm_reply.rp_acpt.ar_verf.  So, we cannot use
512	 * the internal representation to parse the on-the-wire
513	 * representation.  Instead, we skip past the rp_stat field,
514	 * which is an "enum" and so occupies one 32-bit word.
515	 */
516	dp = ((const u_int32_t *)&rp->rm_reply) + 1;
517	TCHECK2(dp[0], 1);
518		return(0);
519	len = ntohl(dp[1]);
520	if (len >= length)
521		return(0);
522	/*
523	 * skip past the ar_verf credentials.
524	 */
525	dp += (len + (2*sizeof(u_int32_t) + 3)) / sizeof(u_int32_t);
526	TCHECK2(dp[0], 0);
527
528	/*
529	 * now we can check the ar_stat field
530	 */
531	astat = ntohl(*(enum accept_stat *)dp);
532	switch (astat) {
533
534	case SUCCESS:
535		break;
536
537	case PROG_UNAVAIL:
538		printf(" PROG_UNAVAIL");
539		return(0);
540
541	case PROG_MISMATCH:
542		printf(" PROG_MISMATCH");
543		return(0);
544
545	case PROC_UNAVAIL:
546		printf(" PROC_UNAVAIL");
547		return(0);
548
549	case GARBAGE_ARGS:
550		printf(" GARBAGE_ARGS");
551		return(0);
552
553	case SYSTEM_ERR:
554		printf(" SYSTEM_ERR");
555		return(0);
556
557	default:
558		printf(" ar_stat %d", astat);
559		return(0);
560	}
561	/* successful return */
562	if ((sizeof(astat) + ((u_char *)dp)) < snapend)
563		return((u_int32_t *) (sizeof(astat) + ((char *)dp)));
564
565trunc:
566	return (0);
567}
568
569static const u_int32_t *
570parsestatus(const u_int32_t *dp)
571{
572	int errnum;
573
574	TCHECK(dp[0]);
575	errnum = ntohl(dp[0]);
576	if (errnum != 0) {
577		char *errmsg;
578
579		if (qflag)
580			return(0);
581
582		errmsg = pcap_strerror(errnum);
583		printf(" ERROR: %s", errmsg);
584		return(0);
585	}
586	return (dp + 1);
587trunc:
588	return (0);
589}
590
591static struct tok type2str[] = {
592	{ NFNON,	"NON" },
593	{ NFREG,	"REG" },
594	{ NFDIR,	"DIR" },
595	{ NFBLK,	"BLK" },
596	{ NFCHR,	"CHR" },
597	{ NFLNK,	"LNK" },
598	{ 0,		NULL }
599};
600
601static const u_int32_t *
602parsefattr(const u_int32_t *dp, int verbose)
603{
604	const struct nfsv2_fattr *fap;
605
606	fap = (const struct nfsv2_fattr *)dp;
607	if (verbose) {
608		TCHECK(fap->fa_nfssize);
609		printf(" %s %o ids %u/%u sz %u ",
610		    tok2str(type2str, "unk-ft %d ",
611		    (u_int32_t)ntohl(fap->fa_type)),
612		    (u_int32_t)ntohl(fap->fa_mode),
613		    (u_int32_t)ntohl(fap->fa_uid),
614		    (u_int32_t)ntohl(fap->fa_gid),
615		    (u_int32_t)ntohl(fap->fa_nfssize));
616	}
617	/* print lots more stuff */
618	if (verbose > 1) {
619		TCHECK(fap->fa_nfsfileid);
620		printf("nlink %u rdev %x fsid %x nodeid %x a/m/ctime ",
621		    (u_int32_t)ntohl(fap->fa_nlink),
622		    (u_int32_t)ntohl(fap->fa_nfsrdev),
623		    (u_int32_t)ntohl(fap->fa_nfsfsid),
624		    (u_int32_t)ntohl(fap->fa_nfsfileid));
625		TCHECK(fap->fa_nfsatime);
626		printf("%u.%06u ",
627		    (u_int32_t)ntohl(fap->fa_nfsatime.nfs_sec),
628		    (u_int32_t)ntohl(fap->fa_nfsatime.nfs_usec));
629		TCHECK(fap->fa_nfsmtime);
630		printf("%u.%06u ",
631		    (u_int32_t)ntohl(fap->fa_nfsmtime.nfs_sec),
632		    (u_int32_t)ntohl(fap->fa_nfsmtime.nfs_usec));
633		TCHECK(fap->fa_nfsctime);
634		printf("%u.%06u ",
635		    (u_int32_t)ntohl(fap->fa_nfsctime.nfs_sec),
636		    (u_int32_t)ntohl(fap->fa_nfsctime.nfs_usec));
637	}
638	return ((const u_int32_t *)&fap[1]);
639trunc:
640	return (NULL);
641}
642
643static int
644parseattrstat(const u_int32_t *dp, int verbose)
645{
646	dp = parsestatus(dp);
647	if (dp == NULL)
648		return (0);
649
650	return (parsefattr(dp, verbose) != NULL);
651}
652
653static int
654parsediropres(const u_int32_t *dp)
655{
656	dp = parsestatus(dp);
657	if (dp == NULL)
658		return (0);
659
660	dp = parsefh(dp);
661	if (dp == NULL)
662		return (0);
663
664	return (parsefattr(dp, vflag) != NULL);
665}
666
667static int
668parselinkres(const u_int32_t *dp)
669{
670	dp = parsestatus(dp);
671	if (dp == NULL)
672		return(0);
673
674	putchar(' ');
675	return (parsefn(dp) != NULL);
676}
677
678static int
679parsestatfs(const u_int32_t *dp)
680{
681	const struct nfsv2_statfs *sfsp;
682
683	dp = parsestatus(dp);
684	if (dp == NULL)
685		return(0);
686
687	if (!qflag) {
688		sfsp = (const struct nfsv2_statfs *)dp;
689		TCHECK(sfsp->sf_bavail);
690		printf(" tsize %u bsize %u blocks %u bfree %u bavail %u",
691		    (u_int32_t)ntohl(sfsp->sf_tsize),
692		    (u_int32_t)ntohl(sfsp->sf_bsize),
693		    (u_int32_t)ntohl(sfsp->sf_blocks),
694		    (u_int32_t)ntohl(sfsp->sf_bfree),
695		    (u_int32_t)ntohl(sfsp->sf_bavail));
696	}
697
698	return (1);
699trunc:
700	return (0);
701}
702
703static int
704parserddires(const u_int32_t *dp)
705{
706	dp = parsestatus(dp);
707	if (dp == 0)
708		return (0);
709	if (!qflag) {
710		TCHECK(dp[0]);
711		printf(" offset %x", (u_int32_t)ntohl(dp[0]));
712		TCHECK(dp[1]);
713		printf(" size %u", (u_int32_t)ntohl(dp[1]));
714		TCHECK(dp[2]);
715		if (dp[2] != 0)
716			printf(" eof");
717	}
718
719	return (1);
720trunc:
721	return (0);
722}
723
724static void
725interp_reply(const struct rpc_msg *rp, u_int32_t proc, u_int length)
726{
727	register const u_int32_t *dp;
728
729	switch (proc) {
730
731#ifdef NFSPROC_NOOP
732	case NFSPROC_NOOP:
733		printf(" nop");
734		return;
735#else
736#define NFSPROC_NOOP -1
737#endif
738	case NFSPROC_NULL:
739		printf(" null");
740		return;
741
742	case NFSPROC_GETATTR:
743		printf(" getattr");
744		dp = parserep(rp, length);
745		if (dp != 0 && parseattrstat(dp, !qflag) != 0)
746			return;
747		break;
748
749	case NFSPROC_SETATTR:
750		printf(" setattr");
751		dp = parserep(rp, length);
752		if (dp != 0 && parseattrstat(dp, !qflag) != 0)
753			return;
754		break;
755
756#if NFSPROC_ROOT != NFSPROC_NOOP
757	case NFSPROC_ROOT:
758		printf(" root");
759		break;
760#endif
761	case NFSPROC_LOOKUP:
762		printf(" lookup");
763		dp = parserep(rp, length);
764		if (dp != 0 && parsediropres(dp) != 0)
765			return;
766		break;
767
768	case NFSPROC_READLINK:
769		printf(" readlink");
770		dp = parserep(rp, length);
771		if (dp != 0 && parselinkres(dp) != 0)
772			return;
773		break;
774
775	case NFSPROC_READ:
776		printf(" read");
777		dp = parserep(rp, length);
778		if (dp != 0 && parseattrstat(dp, vflag) != 0)
779			return;
780		break;
781
782#if NFSPROC_WRITECACHE != NFSPROC_NOOP
783	case NFSPROC_WRITECACHE:
784		printf(" writecache");
785		break;
786#endif
787	case NFSPROC_WRITE:
788		printf(" write");
789		dp = parserep(rp, length);
790		if (dp != 0 && parseattrstat(dp, vflag) != 0)
791			return;
792		break;
793
794	case NFSPROC_CREATE:
795		printf(" create");
796		dp = parserep(rp, length);
797		if (dp != 0 && parsediropres(dp) != 0)
798			return;
799		break;
800
801	case NFSPROC_REMOVE:
802		printf(" remove");
803		dp = parserep(rp, length);
804		if (dp != 0 && parsestatus(dp) != 0)
805			return;
806		break;
807
808	case NFSPROC_RENAME:
809		printf(" rename");
810		dp = parserep(rp, length);
811		if (dp != 0 && parsestatus(dp) != 0)
812			return;
813		break;
814
815	case NFSPROC_LINK:
816		printf(" link");
817		dp = parserep(rp, length);
818		if (dp != 0 && parsestatus(dp) != 0)
819			return;
820		break;
821
822	case NFSPROC_SYMLINK:
823		printf(" symlink");
824		dp = parserep(rp, length);
825		if (dp != 0 && parsestatus(dp) != 0)
826			return;
827		break;
828
829	case NFSPROC_MKDIR:
830		printf(" mkdir");
831		dp = parserep(rp, length);
832		if (dp != 0 && parsediropres(dp) != 0)
833			return;
834		break;
835
836	case NFSPROC_RMDIR:
837		printf(" rmdir");
838		dp = parserep(rp, length);
839		if (dp != 0 && parsestatus(dp) != 0)
840			return;
841		break;
842
843	case NFSPROC_READDIR:
844		printf(" readdir");
845		dp = parserep(rp, length);
846		if (dp != 0 && parserddires(dp) != 0)
847			return;
848		break;
849
850	case NFSPROC_STATFS:
851		printf(" statfs");
852		dp = parserep(rp, length);
853		if (dp != 0 && parsestatfs(dp) != 0)
854			return;
855		break;
856
857	default:
858		printf(" proc-%u", proc);
859		return;
860	}
861	fputs(" [|nfs]", stdout);
862}
863