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