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