xref: /openbsd-src/usr.sbin/tcpdump/print-nfs.c (revision 62a742911104f98b9185b2c6b6007d9b1c36396c)
1 /*
2  * Copyright (c) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997
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: print-nfs.c,v 1.64 97/06/30 13:51:16 leres Exp $ (LBL)";
25 #endif
26 
27 #include <sys/param.h>
28 #include <sys/time.h>
29 #include <sys/socket.h>
30 
31 #ifdef __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 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 	if (!nflag)
77 		(void)printf("%s.nfs > %s.%x: reply %s %d",
78 			     ipaddr_string(&ip->ip_src),
79 			     ipaddr_string(&ip->ip_dst),
80 			     (u_int32_t)ntohl(rp->rm_xid),
81 			     ntohl(rp->rm_reply.rp_stat) == MSG_ACCEPTED?
82 				     "ok":"ERR",
83 			     length);
84 	else
85 		(void)printf("%s.%x > %s.%x: reply %s %d",
86 			     ipaddr_string(&ip->ip_src),
87 			     NFS_PORT,
88 			     ipaddr_string(&ip->ip_dst),
89 			     (u_int32_t)ntohl(rp->rm_xid),
90 			     ntohl(rp->rm_reply.rp_stat) == MSG_ACCEPTED?
91 			     	"ok":"ERR",
92 			     length);
93 
94 	if (xid_map_find(rp, ip, &proc))
95 		interp_reply(rp, 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 true and sets proc success or false on failure */
461 static u_int32_t
462 xid_map_find(const struct rpc_msg *rp, const struct ip *ip, u_int32_t *proc)
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 			*proc = xmep->proc;
479 			return (1);
480 		}
481 		if (++i >= XIDMAPSIZE)
482 			i = 0;
483 	} while (i != xid_map_hint);
484 
485 	/* search failed */
486 	return (0);
487 }
488 
489 /*
490  * Routines for parsing reply packets
491  */
492 
493 /*
494  * Return a pointer to the beginning of the actual results.
495  * If the packet was truncated, return 0.
496  */
497 static const u_int32_t *
498 parserep(register const struct rpc_msg *rp, register u_int length)
499 {
500 	register const u_int32_t *dp;
501 	u_int len;
502 	enum accept_stat astat;
503 
504 	/*
505 	 * Portability note:
506 	 * Here we find the address of the ar_verf credentials.
507 	 * Originally, this calculation was
508 	 *	dp = (u_int32_t *)&rp->rm_reply.rp_acpt.ar_verf
509 	 * On the wire, the rp_acpt field starts immediately after
510 	 * the (32 bit) rp_stat field.  However, rp_acpt (which is a
511 	 * "struct accepted_reply") contains a "struct opaque_auth",
512 	 * whose internal representation contains a pointer, so on a
513 	 * 64-bit machine the compiler inserts 32 bits of padding
514 	 * before rp->rm_reply.rp_acpt.ar_verf.  So, we cannot use
515 	 * the internal representation to parse the on-the-wire
516 	 * representation.  Instead, we skip past the rp_stat field,
517 	 * which is an "enum" and so occupies one 32-bit word.
518 	 */
519 	dp = ((const u_int32_t *)&rp->rm_reply) + 1;
520 	TCHECK2(dp[0], 1);
521 	len = ntohl(dp[1]);
522 	if (len >= length)
523 		return (NULL);
524 	/*
525 	 * skip past the ar_verf credentials.
526 	 */
527 	dp += (len + (2*sizeof(u_int32_t) + 3)) / sizeof(u_int32_t);
528 	TCHECK2(dp[0], 0);
529 
530 	/*
531 	 * now we can check the ar_stat field
532 	 */
533 	astat = ntohl(*(enum accept_stat *)dp);
534 	switch (astat) {
535 
536 	case SUCCESS:
537 		break;
538 
539 	case PROG_UNAVAIL:
540 		printf(" PROG_UNAVAIL");
541 		nfserr = 1;		/* suppress trunc string */
542 		return (NULL);
543 
544 	case PROG_MISMATCH:
545 		printf(" PROG_MISMATCH");
546 		nfserr = 1;		/* suppress trunc string */
547 		return (NULL);
548 
549 	case PROC_UNAVAIL:
550 		printf(" PROC_UNAVAIL");
551 		nfserr = 1;		/* suppress trunc string */
552 		return (NULL);
553 
554 	case GARBAGE_ARGS:
555 		printf(" GARBAGE_ARGS");
556 		nfserr = 1;		/* suppress trunc string */
557 		return (NULL);
558 
559 	case SYSTEM_ERR:
560 		printf(" SYSTEM_ERR");
561 		nfserr = 1;		/* suppress trunc string */
562 		return (NULL);
563 
564 	default:
565 		printf(" ar_stat %d", astat);
566 		nfserr = 1;		/* suppress trunc string */
567 		return (NULL);
568 	}
569 	/* successful return */
570 	if ((sizeof(astat) + ((u_char *)dp)) < snapend)
571 		return ((u_int32_t *) (sizeof(astat) + ((char *)dp)));
572 
573 trunc:
574 	return (NULL);
575 }
576 
577 static const u_int32_t *
578 parsestatus(const u_int32_t *dp)
579 {
580 	register int errnum;
581 
582 	TCHECK(dp[0]);
583 	errnum = ntohl(dp[0]);
584 	if (errnum != 0) {
585 		if (!qflag)
586 			printf(" ERROR: %s", pcap_strerror(errnum));
587 		nfserr = 1;		/* suppress trunc string */
588 		return (NULL);
589 	}
590 	return (dp + 1);
591 trunc:
592 	return (NULL);
593 }
594 
595 static struct tok type2str[] = {
596 	{ NFNON,	"NON" },
597 	{ NFREG,	"REG" },
598 	{ NFDIR,	"DIR" },
599 	{ NFBLK,	"BLK" },
600 	{ NFCHR,	"CHR" },
601 	{ NFLNK,	"LNK" },
602 	{ 0,		NULL }
603 };
604 
605 static const u_int32_t *
606 parsefattr(const u_int32_t *dp, int verbose)
607 {
608 	const struct nfsv2_fattr *fap;
609 
610 	fap = (const struct nfsv2_fattr *)dp;
611 	if (verbose) {
612 		TCHECK(fap->fa_nfssize);
613 		printf(" %s %o ids %u/%u sz %u ",
614 		    tok2str(type2str, "unk-ft %d ",
615 		    (u_int32_t)ntohl(fap->fa_type)),
616 		    (u_int32_t)ntohl(fap->fa_mode),
617 		    (u_int32_t)ntohl(fap->fa_uid),
618 		    (u_int32_t)ntohl(fap->fa_gid),
619 		    (u_int32_t)ntohl(fap->fa_nfssize));
620 	}
621 	/* print lots more stuff */
622 	if (verbose > 1) {
623 		TCHECK(fap->fa_nfsfileid);
624 		printf("nlink %u rdev %x fsid %x nodeid %x a/m/ctime ",
625 		    (u_int32_t)ntohl(fap->fa_nlink),
626 		    (u_int32_t)ntohl(fap->fa_nfsrdev),
627 		    (u_int32_t)ntohl(fap->fa_nfsfsid),
628 		    (u_int32_t)ntohl(fap->fa_nfsfileid));
629 		TCHECK(fap->fa_nfsatime);
630 		printf("%u.%06u ",
631 		    (u_int32_t)ntohl(fap->fa_nfsatime.nfs_sec),
632 		    (u_int32_t)ntohl(fap->fa_nfsatime.nfs_usec));
633 		TCHECK(fap->fa_nfsmtime);
634 		printf("%u.%06u ",
635 		    (u_int32_t)ntohl(fap->fa_nfsmtime.nfs_sec),
636 		    (u_int32_t)ntohl(fap->fa_nfsmtime.nfs_usec));
637 		TCHECK(fap->fa_nfsctime);
638 		printf("%u.%06u ",
639 		    (u_int32_t)ntohl(fap->fa_nfsctime.nfs_sec),
640 		    (u_int32_t)ntohl(fap->fa_nfsctime.nfs_usec));
641 	}
642 	return ((const u_int32_t *)&fap[1]);
643 trunc:
644 	return (NULL);
645 }
646 
647 static int
648 parseattrstat(const u_int32_t *dp, int verbose)
649 {
650 
651 	dp = parsestatus(dp);
652 	if (dp == NULL)
653 		return (0);
654 
655 	return (parsefattr(dp, verbose) != NULL);
656 }
657 
658 static int
659 parsediropres(const u_int32_t *dp)
660 {
661 
662 	dp = parsestatus(dp);
663 	if (dp == NULL)
664 		return (0);
665 
666 	dp = parsefh(dp);
667 	if (dp == NULL)
668 		return (0);
669 
670 	return (parsefattr(dp, vflag) != NULL);
671 }
672 
673 static int
674 parselinkres(const u_int32_t *dp)
675 {
676 	dp = parsestatus(dp);
677 	if (dp == NULL)
678 		return (0);
679 
680 	putchar(' ');
681 	return (parsefn(dp) != NULL);
682 }
683 
684 static int
685 parsestatfs(const u_int32_t *dp)
686 {
687 	const struct nfsv2_statfs *sfsp;
688 
689 	dp = parsestatus(dp);
690 	if (dp == NULL)
691 		return (0);
692 
693 	if (!qflag) {
694 		sfsp = (const struct nfsv2_statfs *)dp;
695 		TCHECK(sfsp->sf_bavail);
696 		printf(" tsize %u bsize %u blocks %u bfree %u bavail %u",
697 		    (u_int32_t)ntohl(sfsp->sf_tsize),
698 		    (u_int32_t)ntohl(sfsp->sf_bsize),
699 		    (u_int32_t)ntohl(sfsp->sf_blocks),
700 		    (u_int32_t)ntohl(sfsp->sf_bfree),
701 		    (u_int32_t)ntohl(sfsp->sf_bavail));
702 	}
703 
704 	return (1);
705 trunc:
706 	return (0);
707 }
708 
709 static int
710 parserddires(const u_int32_t *dp)
711 {
712 	dp = parsestatus(dp);
713 	if (dp == NULL)
714 		return (0);
715 	if (!qflag) {
716 		TCHECK(dp[0]);
717 		printf(" offset %x", (u_int32_t)ntohl(dp[0]));
718 		TCHECK(dp[1]);
719 		printf(" size %u", (u_int32_t)ntohl(dp[1]));
720 		TCHECK(dp[2]);
721 		if (dp[2] != 0)
722 			printf(" eof");
723 	}
724 
725 	return (1);
726 trunc:
727 	return (0);
728 }
729 
730 static void
731 interp_reply(const struct rpc_msg *rp, u_int32_t proc, u_int length)
732 {
733 	register const u_int32_t *dp;
734 
735 	switch (proc) {
736 
737 #ifdef NFSPROC_NOOP
738 	case NFSPROC_NOOP:
739 		printf(" nop");
740 		return;
741 #else
742 #define NFSPROC_NOOP -1
743 #endif
744 	case NFSPROC_NULL:
745 		printf(" null");
746 		return;
747 
748 	case NFSPROC_GETATTR:
749 		printf(" getattr");
750 		dp = parserep(rp, length);
751 		if (dp != NULL && parseattrstat(dp, !qflag) != 0)
752 			return;
753 		break;
754 
755 	case NFSPROC_SETATTR:
756 		printf(" setattr");
757 		dp = parserep(rp, length);
758 		if (dp != NULL && parseattrstat(dp, !qflag) != 0)
759 			return;
760 		break;
761 
762 #if NFSPROC_ROOT != NFSPROC_NOOP
763 	case NFSPROC_ROOT:
764 		printf(" root");
765 		break;
766 #endif
767 	case NFSPROC_LOOKUP:
768 		printf(" lookup");
769 		dp = parserep(rp, length);
770 		if (dp != NULL && parsediropres(dp) != 0)
771 			return;
772 		break;
773 
774 	case NFSPROC_READLINK:
775 		printf(" readlink");
776 		dp = parserep(rp, length);
777 		if (dp != NULL && parselinkres(dp) != 0)
778 			return;
779 		break;
780 
781 	case NFSPROC_READ:
782 		printf(" read");
783 		dp = parserep(rp, length);
784 		if (dp != NULL && parseattrstat(dp, vflag) != 0)
785 			return;
786 		break;
787 
788 #if NFSPROC_WRITECACHE != NFSPROC_NOOP
789 	case NFSPROC_WRITECACHE:
790 		printf(" writecache");
791 		break;
792 #endif
793 	case NFSPROC_WRITE:
794 		printf(" write");
795 		dp = parserep(rp, length);
796 		if (dp != NULL && parseattrstat(dp, vflag) != 0)
797 			return;
798 		break;
799 
800 	case NFSPROC_CREATE:
801 		printf(" create");
802 		dp = parserep(rp, length);
803 		if (dp != NULL && parsediropres(dp) != 0)
804 			return;
805 		break;
806 
807 	case NFSPROC_REMOVE:
808 		printf(" remove");
809 		dp = parserep(rp, length);
810 		if (dp != NULL && parsestatus(dp) != 0)
811 			return;
812 		break;
813 
814 	case NFSPROC_RENAME:
815 		printf(" rename");
816 		dp = parserep(rp, length);
817 		if (dp != NULL && parsestatus(dp) != 0)
818 			return;
819 		break;
820 
821 	case NFSPROC_LINK:
822 		printf(" link");
823 		dp = parserep(rp, length);
824 		if (dp != NULL && parsestatus(dp) != 0)
825 			return;
826 		break;
827 
828 	case NFSPROC_SYMLINK:
829 		printf(" symlink");
830 		dp = parserep(rp, length);
831 		if (dp != NULL && parsestatus(dp) != 0)
832 			return;
833 		break;
834 
835 	case NFSPROC_MKDIR:
836 		printf(" mkdir");
837 		dp = parserep(rp, length);
838 		if (dp != NULL && parsediropres(dp) != 0)
839 			return;
840 		break;
841 
842 	case NFSPROC_RMDIR:
843 		printf(" rmdir");
844 		dp = parserep(rp, length);
845 		if (dp != NULL && parsestatus(dp) != 0)
846 			return;
847 		break;
848 
849 	case NFSPROC_READDIR:
850 		printf(" readdir");
851 		dp = parserep(rp, length);
852 		if (dp != NULL && parserddires(dp) != 0)
853 			return;
854 		break;
855 
856 	case NFSPROC_STATFS:
857 		printf(" statfs");
858 		dp = parserep(rp, length);
859 		if (dp != NULL && parsestatfs(dp) != 0)
860 			return;
861 		break;
862 
863 	default:
864 		printf(" proc-%u", proc);
865 		return;
866 	}
867 	if (!nfserr)
868 		fputs(" [|nfs]", stdout);
869 }
870