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