/*
 * Copyright (c) 1991, 1999 by Sun Microsystems, Inc.
 * All rights reserved.
 */

#ident	"@(#)snoop_nfs3.c	1.12	99/08/20 SMI"	/* SunOS	*/

#include <sys/types.h>
#include <sys/errno.h>
#include <sys/tiuser.h>
#include <setjmp.h>
#include <string.h>

#include <rpc/types.h>
#include <rpc/xdr.h>
#include <rpc/auth.h>
#include <rpc/clnt.h>
#include <rpc/rpc_msg.h>
#include "snoop.h"
#include "snoop_nfs.h"

#include <sys/stat.h>
#include <sys/param.h>
#include <rpcsvc/nfs_prot.h>

#ifndef MIN
#define	MIN(a, b)	((a) < (b) ? (a) : (b))
#endif

extern jmp_buf xdr_err;

static void nfscall3(int);
static void nfsreply3(int);
static char *perms(int);
static char *filetype(int);
static char *sum_access(void);
static char *sum_readdirres(void);
static char *sum_readdirplusres(void);
static char *sum_createhow(void);
static char *sum_stablehow(void);
static void detail_sattr3(void);
static void detail_diropargs3(void);
static void detail_readdirres(void);
static void detail_readdirplusres(void);
static void detail_fattr3(void);
static void detail_access(void);
static void detail_mode(int);
static void detail_wcc_attr(void);
static void detail_pre_op_attr(char *);
static void detail_wcc_data(char *);
static void skip_postop(void);
static void skip_wcc_data(void);
static void skip_sattr3(void);

#define	DONT_CHANGE		0
#define	SET_TO_SERVER_TIME	1
#define	SET_TO_CLIENT_TIME	2

#define	UNCHECKED	0
#define	GUARDED		1
#define	EXCLUSIVE	2

#define	ACCESS3_READ	0x0001
#define	ACCESS3_LOOKUP	0x0002
#define	ACCESS3_MODIFY	0x0004
#define	ACCESS3_EXTEND	0x0008
#define	ACCESS3_DELETE	0x0010
#define	ACCESS3_EXECUTE	0x0020

#define	UNSTABLE	0
#define	DATA_SYNC	1
#define	FILE_SYNC	2

#define	NF3REG		1	/* regular file */
#define	NF3DIR		2	/* directory */
#define	NF3BLK		3	/* block special */
#define	NF3CHR		4	/* character special */
#define	NF3LNK		5	/* symbolic link */
#define	NF3SOCK		6	/* unix domain socket */
#define	NF3FIFO		7	/* named pipe */

#define	NFS3_FHSIZE	64

static char *procnames_short[] = {
	"NULL3",	/*  0 */
	"GETATTR3",	/*  1 */
	"SETATTR3",	/*  2 */
	"LOOKUP3",	/*  3 */
	"ACCESS3",	/*  4 */
	"READLINK3",	/*  5 */
	"READ3",	/*  6 */
	"WRITE3",	/*  7 */
	"CREATE3",	/*  8 */
	"MKDIR3",	/*  9 */
	"SYMLINK3",	/* 10 */
	"MKNOD3",	/* 11 */
	"REMOVE3",	/* 12 */
	"RMDIR3",	/* 13 */
	"RENAME3",	/* 14 */
	"LINK3",	/* 15 */
	"READDIR3",	/* 16 */
	"READDIRPLUS3",	/* 17 */
	"FSSTAT3",	/* 18 */
	"FSINFO3",	/* 19 */
	"PATHCONF3",	/* 20 */
	"COMMIT3",	/* 21 */
};

static char *procnames_long[] = {
	"Null procedure",		/*  0 */
	"Get file attributes",		/*  1 */
	"Set file attributes",		/*  2 */
	"Look up file name",		/*  3 */
	"Check access permission",	/*  4 */
	"Read from symbolic link",	/*  5 */
	"Read from file",		/*  6 */
	"Write to file",		/*  7 */
	"Create file",			/*  8 */
	"Make directory",		/*  9 */
	"Make symbolic link",		/* 10 */
	"Make special file",		/* 11 */
	"Remove file",			/* 12 */
	"Remove directory",		/* 13 */
	"Rename",			/* 14 */
	"Link",				/* 15 */
	"Read from directory",		/* 16 */
	"Read from directory - plus",	/* 17 */
	"Get filesystem statistics",	/* 18 */
	"Get filesystem information",	/* 19 */
	"Get POSIX information",	/* 20 */
	"Commit to stable storage",	/* 21 */
};

#define	MAXPROC	21

void
interpret_nfs3(flags, type, xid, vers, proc, data, len)
	int flags, type, xid, vers, proc;
	char *data;
	int len;
{
	char *line;
	char buff[NFS_MAXPATHLEN + 1];	/* protocol allows longer */
	u_longlong_t off;
	int sz, how;
	char *fh, *name;

	if (proc < 0 || proc > MAXPROC)
		return;

	if (flags & F_SUM) {
		line = get_sum_line();

		if (type == CALL) {
			(void) sprintf(line, "NFS C %s",
				procnames_short[proc]);
			line += strlen(line);
			switch (proc) {
			case NFSPROC3_GETATTR:
			case NFSPROC3_READLINK:
			case NFSPROC3_FSSTAT:
			case NFSPROC3_FSINFO:
			case NFSPROC3_PATHCONF:
				(void) sprintf(line, sum_nfsfh3());
				break;
			case NFSPROC3_SETATTR:
				(void) sprintf(line, sum_nfsfh3());
				break;
			case NFSPROC3_READDIR:
				fh = sum_nfsfh3();
				off = getxdr_u_longlong();
				(void) getxdr_u_longlong();
				sz = getxdr_u_long();
				(void) sprintf(line, "%s Cookie=%llu for %lu",
					fh, off, sz);
				break;
			case NFSPROC3_READDIRPLUS:
				fh = sum_nfsfh3();
				off = getxdr_u_longlong();
				(void) getxdr_u_longlong();
				sz = getxdr_u_long();
				(void) sprintf(line,
						"%s Cookie=%llu for %lu/%lu",
						fh, off, sz, getxdr_u_long());
				break;
			case NFSPROC3_ACCESS:
				fh = sum_nfsfh3();
				(void) sprintf(line, "%s (%s)",
					fh, sum_access());
				break;
			case NFSPROC3_LOOKUP:
			case NFSPROC3_REMOVE:
			case NFSPROC3_RMDIR:
			case NFSPROC3_MKDIR:
				fh = sum_nfsfh3();
				(void) sprintf(line, "%s %s",
					fh, getxdr_string(buff,
						NFS_MAXPATHLEN));
				break;
			case NFSPROC3_CREATE:
				fh = sum_nfsfh3();
				name = getxdr_string(buff, NFS_MAXPATHLEN);
				(void) sprintf(line, "%s (%s) %s",
					fh, sum_createhow(), name);
				break;
			case NFSPROC3_MKNOD:
				fh = sum_nfsfh3();
				name = getxdr_string(buff, NFS_MAXPATHLEN);
				how = getxdr_long();
				(void) sprintf(line, "%s (%s) %s",
					fh, filetype(how), name);
				break;
			case NFSPROC3_READ:
				fh = sum_nfsfh3();
				off = getxdr_u_longlong();
				sz = getxdr_u_long();
				(void) sprintf(line, "%s at %llu for %lu",
					fh, off, sz);
				break;
			case NFSPROC3_WRITE:
				fh = sum_nfsfh3();
				off = getxdr_u_longlong();
				sz = getxdr_u_long();
				(void) sprintf(line, "%s at %llu for %lu (%s)",
					fh, off, sz, sum_stablehow());
				break;
			case NFSPROC3_SYMLINK:
				fh = sum_nfsfh3();
				(void) sprintf(line, "%s %s",
					fh, getxdr_string(buff,
						NFS_MAXPATHLEN));
				skip_sattr3();
				line += strlen(line);
				(void) sprintf(line, " to %s",
					getxdr_string(buff, NFS_MAXPATHLEN));
				break;
			case NFSPROC3_RENAME:
				fh = sum_nfsfh3();
				(void) sprintf(line, "%s %s",
					fh, getxdr_string(buff,
						NFS_MAXPATHLEN));
				line += strlen(line);
				fh = sum_nfsfh3();
				(void) sprintf(line, " to%s %s",
					fh, getxdr_string(buff,
						NFS_MAXPATHLEN));
				break;
			case NFSPROC3_LINK:
				fh = sum_nfsfh3();
				(void) sprintf(line, "%s", fh);
				line += strlen(line);
				fh = sum_nfsfh3();
				(void) sprintf(line, " to%s %s",
					fh, getxdr_string(buff,
						NFS_MAXPATHLEN));
				break;
			case NFSPROC3_COMMIT:
				fh = sum_nfsfh3();
				off = getxdr_u_longlong();
				sz  = getxdr_u_long();
				(void) sprintf(line, "%s at %llu for %lu",
					fh, off, sz);
				break;
			default:
				break;
			}

			check_retransmit(line, xid);
		} else {
			(void) sprintf(line, "NFS R %s ",
				procnames_short[proc]);
			line += strlen(line);
			switch (proc) {
			case NFSPROC3_LOOKUP:
				if (sum_nfsstat3(line) == NFS3_OK)
					(void) strcat(line, sum_nfsfh3());
				break;
			case NFSPROC3_CREATE:
			case NFSPROC3_MKDIR:
			case NFSPROC3_SYMLINK:
			case NFSPROC3_MKNOD:
				if (sum_nfsstat3(line) == NFS3_OK) {
					if (getxdr_bool())
						(void) strcat(line,
							    sum_nfsfh3());
				}
				break;
			case NFSPROC3_READLINK:
				if (sum_nfsstat3(line) == NFS3_OK) {
					line += strlen(line);
					skip_postop();
					(void) sprintf(line, " (Path=%s)",
						getxdr_string(buff,
						    NFS_MAXPATHLEN));
				}
				break;
			case NFSPROC3_GETATTR:
			case NFSPROC3_SETATTR:
			case NFSPROC3_REMOVE:
			case NFSPROC3_RMDIR:
			case NFSPROC3_RENAME:
			case NFSPROC3_LINK:
			case NFSPROC3_FSSTAT:
			case NFSPROC3_FSINFO:
			case NFSPROC3_PATHCONF:
				(void) sum_nfsstat3(line);
				break;
			case NFSPROC3_ACCESS:
				if (sum_nfsstat3(line) == NFS3_OK) {
					line += strlen(line);
					skip_postop();
					(void) sprintf(line, " (%s)",
						sum_access());
				}
				break;
			case NFSPROC3_WRITE:
				if (sum_nfsstat3(line) == NFS3_OK) {
					line += strlen(line);
					skip_wcc_data();
					sz = getxdr_u_long();
					(void) sprintf(line, " %d (%s)",
						sz, sum_stablehow());
				}
				break;
			case NFSPROC3_READDIR:
				if (sum_nfsstat3(line) == NFS3_OK)
					(void) strcat(line, sum_readdirres());
				break;
			case NFSPROC3_READ:
				if (sum_nfsstat3(line) == NFS3_OK) {
					line += strlen(line);
					skip_postop();
					(void) sprintf(line, " (%lu bytes)",
						getxdr_u_long());
					if (getxdr_bool())
						(void) strcat(line, " EOF");
				}
				break;
			case NFSPROC3_READDIRPLUS:
				if (sum_nfsstat3(line) == NFS3_OK)
					(void) strcat(line,
						    sum_readdirplusres());
				break;
			case NFSPROC3_COMMIT:
				(void) sum_nfsstat3(line);
				break;
			default:
				break;
			}
		}
	}

	if (flags & F_DTAIL) {
		show_header("NFS:  ", "Sun NFS", len);
		show_space();
		(void) sprintf(get_line(0, 0), "Proc = %d (%s)",
			proc, procnames_long[proc]);
		if (type == CALL)
			nfscall3(proc);
		else
			nfsreply3(proc);
		show_trailer();
	}
}

/*
 *  Print out version 3 NFS call packets
 */
static void
nfscall3(proc)
	int proc;
{
	int h;

	switch (proc) {
	case NFSPROC3_GETATTR:
	case NFSPROC3_READLINK:
	case NFSPROC3_FSINFO:
	case NFSPROC3_FSSTAT:
	case NFSPROC3_PATHCONF:
		detail_nfsfh3();
		break;
	case NFSPROC3_SETATTR:
		detail_nfsfh3();
		detail_sattr3();
		if (getxdr_bool())
			(void) showxdr_date_ns("Guard = %s");
		break;
	case NFSPROC3_LOOKUP:
	case NFSPROC3_REMOVE:
	case NFSPROC3_RMDIR:
		detail_diropargs3();
		break;
	case NFSPROC3_ACCESS:
		detail_nfsfh3();
		detail_access();
		break;
	case NFSPROC3_MKDIR:
		detail_diropargs3();
		detail_sattr3();
		break;
	case NFSPROC3_CREATE:
		detail_diropargs3();
		h = getxdr_u_long();
		if (h == EXCLUSIVE)
			showxdr_hex(8, "Guard = %s");
		else {
			(void) sprintf(get_line(0, 0), "Method = %s",
			h == UNCHECKED ? "Unchecked" : "Guarded");
			detail_sattr3();
		}
		break;
	case NFSPROC3_MKNOD:
		detail_diropargs3();
		h = getxdr_u_long();
		(void) sprintf(get_line(0, 0), "File type = %s",
			filetype(h));
		switch (h) {
		case NF3CHR:
		case NF3BLK:
			detail_sattr3();
			showxdr_u_long("Major = %lu");
			showxdr_u_long("Minor = %lu");
			break;
		case NF3SOCK:
		case NF3FIFO:
			detail_sattr3();
			break;
		}
		break;
	case NFSPROC3_WRITE:
		detail_nfsfh3();
		(void) showxdr_u_longlong("Offset = %llu");
		(void) showxdr_u_long("Size   = %lu");
		(void) sprintf(get_line(0, 0), "Stable = %s",
				sum_stablehow());
		break;
	case NFSPROC3_RENAME:
		detail_diropargs3();
		detail_diropargs3();
		break;
	case NFSPROC3_LINK:
		detail_nfsfh3();
		detail_diropargs3();
		break;
	case NFSPROC3_SYMLINK:
		detail_diropargs3();
		detail_sattr3();
		(void) showxdr_string(MAXPATHLEN, "Path = %s");
		break;
	case NFSPROC3_READDIR:
		detail_nfsfh3();
		(void) showxdr_u_longlong("Cookie   = %llu");
		(void) showxdr_hex(8, "Verifier = %s");
		(void) showxdr_u_long("Count = %lu");
		break;
	case NFSPROC3_READDIRPLUS:
		detail_nfsfh3();
		(void) showxdr_u_longlong("Cookie   = %llu");
		(void) showxdr_hex(8, "Verifier = %s");
		(void) showxdr_u_long("Dircount = %lu");
		(void) showxdr_u_long("Maxcount = %lu");
		break;
	case NFSPROC3_READ:
	case NFSPROC3_COMMIT:
		detail_nfsfh3();
		(void) showxdr_u_longlong("Offset = %llu");
		(void) showxdr_long("Count = %lu");
		break;
	default:
		break;
	}
}

/*
 *  Print out version 3 NFS reply packets
 */
static void
nfsreply3(proc)
	int proc;
{
	int bits;

	switch (proc) {
	case NFSPROC3_GETATTR:
		if (detail_nfsstat3() == NFS3_OK) {
			detail_fattr3();
		}
		break;
	case NFSPROC3_SETATTR:
		(void) detail_nfsstat3();
		detail_wcc_data("");
		break;
	case NFSPROC3_WRITE:
		if (detail_nfsstat3() == NFS3_OK) {
			detail_wcc_data("");
			(void) showxdr_u_long("Count = %lu bytes written");
			(void) sprintf(get_line(0, 0), "Stable = %s",
					sum_stablehow());
			(void) showxdr_hex(8, "Verifier = %s");
		} else
			detail_wcc_data("");
		break;
	case NFSPROC3_LOOKUP:
		if (detail_nfsstat3() == NFS3_OK) {
			detail_nfsfh3();
			detail_post_op_attr("(object)");
		}
		detail_post_op_attr("(directory)");
		break;
	case NFSPROC3_CREATE:
	case NFSPROC3_MKDIR:
	case NFSPROC3_SYMLINK:
	case NFSPROC3_MKNOD:
		if (detail_nfsstat3() == NFS3_OK) {
			if (getxdr_bool())
				detail_nfsfh3();
			else
				(void) sprintf(get_line(0, 0),
						"(No file handle available)");
			detail_post_op_attr("");
		}
		detail_wcc_data("");
		break;
	case NFSPROC3_READLINK:
		if (detail_nfsstat3() == NFS3_OK) {
			detail_post_op_attr("");
			(void) showxdr_string(MAXPATHLEN, "Path = %s");
		} else
			detail_post_op_attr("");
		break;
	case NFSPROC3_READ:
		if (detail_nfsstat3() == NFS3_OK) {
			detail_post_op_attr("");
			(void) showxdr_u_long("Count = %lu bytes read");
			(void) showxdr_bool("End of file = %s");
		} else
			detail_post_op_attr("");
		break;
	case NFSPROC3_ACCESS:
		if (detail_nfsstat3() == NFS3_OK) {
			detail_post_op_attr("");
			(void) sprintf(get_line(0, 0), "Access = %s",
					sum_access());
		} else
			detail_post_op_attr("");
		break;
	case NFSPROC3_REMOVE:
	case NFSPROC3_RMDIR:
		(void) detail_nfsstat3();
		detail_wcc_data("");
		break;
	case NFSPROC3_RENAME:
		(void) detail_nfsstat3();
		detail_wcc_data("(from directory)");
		detail_wcc_data("(to directory)");
		break;
	case NFSPROC3_LINK:
		(void) detail_nfsstat3();
		detail_post_op_attr("");
		detail_wcc_data("");
		break;
	case NFSPROC3_READDIR:
		if (detail_nfsstat3() == NFS3_OK) {
			detail_readdirres();
		} else
			detail_post_op_attr("");
		break;
	case NFSPROC3_READDIRPLUS:
		if (detail_nfsstat3() == NFS3_OK) {
			detail_readdirplusres();
		} else
			detail_post_op_attr("");
		break;
	case NFSPROC3_FSSTAT:
		if (detail_nfsstat3() == NFS3_OK) {
			detail_post_op_attr("");
			(void) showxdr_u_longlong(
				"Total space = %llu bytes");
			(void) showxdr_u_longlong(
				"Available space = %llu bytes");
			(void) showxdr_u_longlong(
				"Available space - this user = %llu bytes");
			(void) showxdr_u_longlong(
				"Total file slots = %llu");
			(void) showxdr_u_longlong(
				"Available file slots = %llu");
			(void) showxdr_u_longlong(
				"Available file slots - this user = %llu");
			(void) showxdr_u_long("Invariant time = %lu sec");
		} else
			detail_post_op_attr("");
		break;
	case NFSPROC3_FSINFO:
		if (detail_nfsstat3() == NFS3_OK) {
			detail_post_op_attr("");
			(void) show_line("Read transfer sizes:");
			(void) showxdr_u_long("   Maximum = %lu bytes");
			(void) showxdr_u_long("   Preferred = %lu bytes");
			(void) showxdr_u_long(
			    "   Suggested multiple = %lu bytes");
			(void) show_line("Write transfer sizes:");
			(void) showxdr_u_long("   Maximum = %lu bytes");
			(void) showxdr_u_long("   Preferred = %lu bytes");
			(void) showxdr_u_long(
			    "   Suggested multiple = %lu bytes");
			(void) show_line("Directory read size:");
			(void) showxdr_u_long("   Preferred = %lu bytes");
			(void) show_line("File system limits:");
			(void) showxdr_u_longlong(
			    "   Max file size = %llu bytes");
			(void) showxdr_date_ns(
			    "   Server minimum time discrimination = %s sec");
			bits = showxdr_u_long("Properties = 0x%02x");
			(void) sprintf(get_line(0, 0), "	%s",
				getflag(bits, FSF3_LINK,
				"Hard links supported",
				"(hard links not supported)"));
			(void) sprintf(get_line(0, 0), "	%s",
				getflag(bits, FSF3_SYMLINK,
				"Symbolic links supported",
				"(symbolic links not supported)"));
			(void) sprintf(get_line(0, 0), "	%s",
				getflag(bits, FSF3_HOMOGENEOUS,
				"Pathconf cannot vary per file",
				"(pathconf can vary per file)"));
			(void) sprintf(get_line(0, 0), "	%s",
				getflag(bits, FSF3_CANSETTIME,
				"Server can always set file times",
				"(server cannot always set file times)"));
		} else
			detail_post_op_attr("");
		break;
	case NFSPROC3_PATHCONF:
		if (detail_nfsstat3() == NFS3_OK) {
			detail_post_op_attr("");
			(void) showxdr_u_long("Link max = %lu");
			(void) showxdr_u_long("Name max = %lu");
			(void) showxdr_bool("No trunc         = %s");
			(void) showxdr_bool("Chown restricted = %s");
			(void) showxdr_bool("Case insensitive = %s");
			(void) showxdr_bool("Case preserving  = %s");
		} else
			detail_post_op_attr("");
		break;
	case NFSPROC3_COMMIT:
		if (detail_nfsstat3() == NFS3_OK) {
			detail_wcc_data("");
			(void) showxdr_hex(8, "Verifier = %s");
		} else
			detail_wcc_data("");
		break;
	default:
		break;
	}
}

static void
detail_diropargs3()
{

	detail_nfsfh3();
	(void) showxdr_string(MAXPATHLEN, "File name = %s");
}

int
sum_nfsstat3(line)
	char *line;
{
	ulong_t status;
	char *p;

	status = getxdr_long();
	switch (status) {
	case NFS3_OK:		p = "OK"; break;
	case NFS3ERR_PERM:	p = "Not owner"; break;
	case NFS3ERR_NOENT:	p = "No such file or directory"; break;
	case NFS3ERR_IO:	p = "I/O error"; break;
	case NFS3ERR_NXIO:	p = "No such device or address"; break;
	case NFS3ERR_ACCES:	p = "Permission denied"; break;
	case NFS3ERR_EXIST:	p = "File exists"; break;
	case NFS3ERR_XDEV:	p = "Attempted cross-device link"; break;
	case NFS3ERR_NODEV:	p = "No such device"; break;
	case NFS3ERR_NOTDIR:	p = "Not a directory"; break;
	case NFS3ERR_ISDIR:	p = "Is a directory"; break;
	case NFS3ERR_INVAL:	p = "Invalid argument"; break;
	case NFS3ERR_FBIG:	p = "File too large"; break;
	case NFS3ERR_NOSPC:	p = "No space left on device"; break;
	case NFS3ERR_ROFS:	p = "Read-only file system"; break;
	case NFS3ERR_MLINK:	p = "Too many links"; break;
	case NFS3ERR_NAMETOOLONG:p = "File name too long"; break;
	case NFS3ERR_NOTEMPTY:	p = "Directory not empty"; break;
	case NFS3ERR_DQUOT:	p = "Disc quota exceeded"; break;
	case NFS3ERR_STALE:	p = "Stale NFS file handle"; break;
	case NFS3ERR_REMOTE:	p = "Too many levels of remote in path"; break;
	case NFS3ERR_BADHANDLE:	p = "Illegal NFS file handle"; break;
	case NFS3ERR_NOT_SYNC:	p = "Update synch mismatch"; break;
	case NFS3ERR_BAD_COOKIE:p = "Readdir cookie is stale"; break;
	case NFS3ERR_NOTSUPP:	p = "Operation not supported"; break;
	case NFS3ERR_TOOSMALL:	p = "Buffer/request too small"; break;
	case NFS3ERR_SERVERFAULT:p = "Server fault"; break;
	case NFS3ERR_BADTYPE:	p = "Bad type"; break;
	default:		p = "(unknown error)"; break;
	}

	(void) strcpy(line, p);
	return (status);
}

int
detail_nfsstat3()
{
	ulong_t status;
	char buff[64];
	int pos;

	pos = getxdr_pos();
	status = sum_nfsstat3(buff);

	(void) sprintf(get_line(pos, getxdr_pos()), "Status = %d (%s)",
		status, buff);

	return ((int)status);
}

static void
skip_postop()
{

	if (getxdr_bool())
		xdr_skip(21 * 4);	/* XDR size of fattr3 */
}

static void
skip_wcc_data()
{

	if (getxdr_bool() > 0)
		xdr_skip(3 * 8);
	skip_postop();
}

static void
skip_sattr3()
{

	if (getxdr_bool() > 0)
		xdr_skip(4);		/* mode */
	if (getxdr_bool() > 0)
		xdr_skip(4);		/* uid */
	if (getxdr_bool() > 0)
		xdr_skip(4);		/* gid */
	if (getxdr_bool() > 0)
		xdr_skip(8);		/* size */
	if (getxdr_bool() > 0)
		xdr_skip(8);		/* atime */
	if (getxdr_bool() > 0)
		xdr_skip(8);		/* mtime */
}

char *
sum_nfsfh3()
{
	int len;
	int fh;
	static char buff[16];

	len = getxdr_long();
	fh = sum_filehandle(len);
	(void) sprintf(buff, " FH=%04X", fh & 0xFFFF);
	return (buff);
}

void
detail_nfsfh3()
{
	int pos;
	int i, l, len;
	int fh;

	len = getxdr_long();
	pos = getxdr_pos();
	fh = sum_filehandle(len);
	setxdr_pos(pos);
	(void) sprintf(get_line(0, 0), "File handle = [%04X]", fh & 0xFFFF);
	i = 0;
	while (i < len) {
		l = MIN(len - i, 32);
		(void) showxdr_hex(l, " %s");
		i += l;
	}
}

static char *
sum_access()
{
	int bits;
	static char buff[64];

	bits = getxdr_u_long();
	buff[0] = '\0';

	if (bits & ACCESS3_READ)
		(void) strcat(buff, "read,");
	if (bits & ACCESS3_LOOKUP)
		(void) strcat(buff, "lookup,");
	if (bits & ACCESS3_MODIFY)
		(void) strcat(buff, "modify,");
	if (bits & ACCESS3_EXTEND)
		(void) strcat(buff, "extend,");
	if (bits & ACCESS3_DELETE)
		(void) strcat(buff, "delete,");
	if (bits & ACCESS3_EXECUTE)
		(void) strcat(buff, "execute,");
	if (buff[0] != '\0')
		buff[strlen(buff) - 1] = '\0';

	return (buff);
}

static void
detail_access()
{
	uint_t bits;

	bits = showxdr_u_long("Access bits = 0x%08x");
	(void) sprintf(get_line(0, 0), "	%s",
		getflag(bits, ACCESS3_READ, "Read", "(no read)"));
	(void) sprintf(get_line(0, 0), "	%s",
		getflag(bits, ACCESS3_LOOKUP, "Lookup", "(no lookup)"));
	(void) sprintf(get_line(0, 0), "	%s",
		getflag(bits, ACCESS3_MODIFY, "Modify", "(no modify)"));
	(void) sprintf(get_line(0, 0), "	%s",
		getflag(bits, ACCESS3_EXTEND, "Extend", "(no extend)"));
	(void) sprintf(get_line(0, 0), "	%s",
		getflag(bits, ACCESS3_DELETE, "Delete", "(no delete)"));
	(void) sprintf(get_line(0, 0), "	%s",
		getflag(bits, ACCESS3_EXECUTE, "Execute", "(no execute)"));
}

static void
detail_mode(mode)
	int mode;
{

	(void) sprintf(get_line(0, 0), "  Mode = 0%o", mode);
	(void) sprintf(get_line(0, 0),
		"   Setuid = %d, Setgid = %d, Sticky = %d",
		(mode & S_ISUID) != 0,
		(mode & S_ISGID) != 0,
		(mode & S_ISVTX) != 0);
	(void) sprintf(get_line(0, 0), "   Owner's permissions = %s",
		perms(mode >> 6 & 0x7));
	(void) sprintf(get_line(0, 0), "   Group's permissions = %s",
		perms(mode >> 3 & 0x7));
	(void) sprintf(get_line(0, 0), "   Other's permissions = %s",
		perms(mode & 0x7));
}

static void
detail_fattr3()
{
	uint_t fltype, mode, nlinks, uid, gid;
	uint_t major, minor;
	u_longlong_t size, used, fsid, fileid;

	fltype  = getxdr_u_long();
	mode	= getxdr_u_long();
	nlinks	= getxdr_u_long();
	uid	= getxdr_u_long();
	gid	= getxdr_u_long();
	size	= getxdr_u_longlong();
	used 	= getxdr_u_longlong();
	major	= getxdr_u_long();
	minor	= getxdr_u_long();
	fsid	= getxdr_u_longlong();
	fileid	= getxdr_u_longlong();

	(void) sprintf(get_line(0, 0),
		"  File type = %d (%s)",
		fltype, filetype(fltype));
	detail_mode(mode);
	(void) sprintf(get_line(0, 0),
		"  Link count = %u, User ID = %u, Group ID = %u",
		nlinks, uid, gid);
	(void) sprintf(get_line(0, 0),
		"  File size = %llu, Used = %llu",
		size, used);
	(void) sprintf(get_line(0, 0),
		"  Special: Major = %u, Minor = %u",
		major, minor);
	(void) sprintf(get_line(0, 0),
		"  File system id = %llu, File id = %llu",
		fsid, fileid);
	(void) showxdr_date_ns("  Last access time      = %s");
	(void) showxdr_date_ns("  Modification time     = %s");
	(void) showxdr_date_ns("  Attribute change time = %s");
	(void) show_line("");
}

static void
detail_sattr3()
{
	int t;

	if (getxdr_bool())
		detail_mode(getxdr_u_long());
	else
		(void) sprintf(get_line(0, 0), "Mode = (not set)");
	if (getxdr_bool())
		(void) showxdr_long("User ID = %d");
	else
		(void) sprintf(get_line(0, 0), "User ID = (not set)");
	if (getxdr_bool())
		(void) showxdr_long("Group ID = %d");
	else
		(void) sprintf(get_line(0, 0), "Group ID = (not set)");
	if (getxdr_bool())
		(void) showxdr_u_longlong("Size = %llu");
	else
		(void) sprintf(get_line(0, 0), "Size = (not set)");

	if ((t = getxdr_u_long()) == SET_TO_CLIENT_TIME)
		(void) showxdr_date("Access time = %s (set to client time)");
	else if (t == SET_TO_SERVER_TIME)
		(void) sprintf(get_line(0, 0),
				"Access time = (set to server time)");
	else
		(void) sprintf(get_line(0, 0), "Access time = (do not set)");

	if ((t = getxdr_u_long()) == SET_TO_CLIENT_TIME) {
		(void) showxdr_date(
				"Modification time = %s (set to client time)");
	} else if (t == SET_TO_SERVER_TIME)
		(void) sprintf(get_line(0, 0),
				"Modification time = (set to server time)");
	else
		(void) sprintf(get_line(0, 0),
				"Modification time = (do not set)");
	(void) show_line("");
}

static char *
filetype(n)
	int n;
{

	switch (n) {
	case NF3REG:
		return ("Regular File");
	case NF3DIR:
		return ("Directory");
	case NF3BLK:
		return ("Block special");
	case NF3CHR:
		return ("Character special");
	case NF3LNK:
		return ("Symbolic Link");
	case NF3SOCK:
		return ("Unix domain socket");
	case NF3FIFO:
		return ("Named pipe");
	default:
		return ("?");
	}
	/* NOTREACHED */
}

static char *
perms(n)
	int n;
{
	static char buff[4];

	buff[0] = n & 4 ? 'r' : '-';
	buff[1] = n & 2 ? 'w' : '-';
	buff[2] = n & 1 ? 'x' : '-';
	buff[3] = '\0';
	return (buff);
}

static void
detail_wcc_attr()
{

	(void) showxdr_u_longlong("  Size = %llu bytes");
	(void) showxdr_date_ns("  Modification time      = %s");
	(void) showxdr_date_ns("  Attribute change time  = %s");
	(void) show_line("");
}

static void
detail_pre_op_attr(str)
	char *str;
{

	if (getxdr_bool()) {
		(void) sprintf(get_line(0, 0),
			"Pre-operation attributes: %s", str);
		detail_wcc_attr();
	} else
		(void) sprintf(get_line(0, 0),
			"Pre-operation attributes: %s (not available)", str);
}

void
detail_post_op_attr(str)
	char *str;
{

	if (getxdr_bool()) {
		(void) sprintf(get_line(0, 0),
			"Post-operation attributes: %s", str);
		detail_fattr3();
	} else
		(void) sprintf(get_line(0, 0),
			"Post-operation attributes: %s (not available)", str);
}

static void
detail_wcc_data(str)
	char *str;
{

	detail_pre_op_attr(str);
	detail_post_op_attr(str);
}

static char *
sum_readdirres()
{
	static char buff[NFS_MAXNAMLEN + 1]; /* protocol allows longer names */
	static int entries;

	entries = 0;
	if (setjmp(xdr_err)) {
		(void) sprintf(buff, " %d+ entries (incomplete)", entries);
		return (buff);
	}
	skip_postop();
	xdr_skip(8);	/* cookieverf */
	while (getxdr_bool()) {
		entries++;
		xdr_skip(8);				/* fileid */
		(void) getxdr_string(buff, NFS_MAXNAMLEN); /* name */
		xdr_skip(8);				/* cookie */
	}

	(void) sprintf(buff, " %d entries (%s)",
		entries, getxdr_bool() ? "No more" : "More");
	return (buff);
}

static char *
sum_readdirplusres()
{
	static char buff[NFS_MAXNAMLEN + 1]; /* protocol allows longer */
	static int entries;
	int skip;

	entries = 0;
	if (setjmp(xdr_err)) {
		(void) sprintf(buff, " %d+ entries (incomplete)", entries);
		return (buff);
	}
	skip_postop();
	xdr_skip(8);	/* cookieverf */
	while (getxdr_bool()) {
		entries++;
		xdr_skip(8);				/* fileid */
		(void) getxdr_string(buff, NFS_MAXNAMLEN); /* name */
		xdr_skip(8);				/* cookie */
		skip_postop();				/* post-op */
		if (getxdr_bool()) {
			skip = getxdr_long();
			xdr_skip(RNDUP(skip));		/* fhandle */
		}
	}

	(void) sprintf(buff, " %d entries (%s)",
		entries, getxdr_bool() ? "No more" : "More");
	return (buff);
}

static void
detail_readdirres()
{
	static int entries;
	u_longlong_t fileid, cookie;
	char *name;
	char buff[NFS_MAXNAMLEN + 1];	/* protocol allows longer names */

	entries = 0;
	detail_post_op_attr("");
	(void) showxdr_hex(8, "Cookie verifier = %s");
	(void) show_line("");
	(void) sprintf(get_line(0, 0), " File id  Cookie Name");

	if (setjmp(xdr_err)) {
		(void) sprintf(get_line(0, 0),
			"  %d+ entries. (Frame is incomplete)",
			entries);
		return;
	}
	while (getxdr_bool()) {
		entries++;
		fileid = getxdr_u_longlong();
		name = (char *)getxdr_string(buff, NFS_MAXNAMLEN);
		cookie = getxdr_u_longlong();
		(void) sprintf(get_line(0, 0),
			" %7llu%8llu %s",
			fileid, cookie, name);
	}

	(void) sprintf(get_line(0, 0), "  %d entries", entries);
	(void) showxdr_bool("EOF = %s");
}

static void
detail_readdirplusres()
{
	static int entries;

	entries = 0;
	detail_post_op_attr("");
	(void) showxdr_hex(8, "Cookie verifier = %s");
	(void) show_line("");

	if (setjmp(xdr_err)) {
		(void) sprintf(get_line(0, 0),
			"  %d+ entries. (Frame is incomplete)",
			entries);
		return;
	}
	while (getxdr_bool()) {
		entries++;
		(void) sprintf(get_line(0, 0),
			"------------------ entry #%d",
			entries);
		(void) showxdr_u_longlong("File ID = %llu");
		(void) showxdr_string(NFS_MAXNAMLEN, "Name = %s");
		(void) showxdr_u_longlong("Cookie = %llu");
		detail_post_op_attr("");
		if (getxdr_bool())
			detail_nfsfh3();
		else
			(void) sprintf(get_line(0, 0),
					"(No file handle available)");
	}

	(void) show_line("");
	(void) sprintf(get_line(0, 0), "  %d entries", entries);
	(void) showxdr_bool("EOF = %s");
}

static char *
sum_createhow()
{
	long how;

	how = getxdr_long();
	switch (how) {
	case UNCHECKED:
		return ("UNCHECKED");
	case GUARDED:
		return ("GUARDED");
	case EXCLUSIVE:
		return ("EXCLUSIVE");
	default:
		return ("?");
	}
	/* NOTREACHED */
}

static char *
sum_stablehow()
{
	long stable;

	stable = getxdr_long();
	switch (stable) {
	case UNSTABLE:
		return ("ASYNC");
	case DATA_SYNC:
		return ("DSYNC");
	case FILE_SYNC:
		return ("FSYNC");
	default:
		return ("?");
	}
	/* NOTREACHED */
}
