/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 1991-2003 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

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

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

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

static char *perms(int);
static char *filetype(int);
static char *sum_readdirres(void);
static void detail_readdirres(void);
static void detail_diroparg(void);
static void nfscall2(int);
static void nfsreply2(int);
static void detail_mode(int);
static void detail_sattr(void);
static void interpret_nfs2(int, int, int, int, int, char *, int);

extern jmp_buf xdr_err;

static char *procnames_short[] = {
	"NULL2",	/*  0 */
	"GETATTR2",	/*  1 */
	"SETATTR2",	/*  2 */
	"ROOT2",	/*  3 */
	"LOOKUP2",	/*  4 */
	"READLINK2",	/*  5 */
	"READ2",	/*  6 */
	"WRITECACHE2",	/*  7 */
	"WRITE2",	/*  8 */
	"CREATE2",	/*  9 */
	"REMOVE2",	/* 10 */
	"RENAME2",	/* 11 */
	"LINK2",	/* 12 */
	"SYMLINK2",	/* 13 */
	"MKDIR2",	/* 14 */
	"RMDIR2",	/* 15 */
	"READDIR2",	/* 16 */
	"STATFS2",	/* 17 */
};

static char *procnames_long[] = {
	"Null procedure",		/*  0 */
	"Get file attributes",		/*  1 */
	"Set file attributes",		/*  2 */
	"Get root filehandle",		/*  3 */
	"Look up file name",		/*  4 */
	"Read from symbolic link",	/*  5 */
	"Read from file",		/*  6 */
	"Write to cache",		/*  7 */
	"Write to file",		/*  8 */
	"Create file",			/*  9 */
	"Remove file",			/* 10 */
	"Rename",			/* 11 */
	"Link",				/* 12 */
	"Make symbolic link",		/* 13 */
	"Make directory",		/* 14 */
	"Remove directory",		/* 15 */
	"Read from directory",		/* 16 */
	"Get filesystem attributes",	/* 17 */
};

#define	MAXPROC	17

/* ARGSUSED */
void
interpret_nfs(flags, type, xid, vers, proc, data, len)
	int flags, type, xid, vers, proc;
	char *data;
	int len;
{

	if (vers == 2) {
		interpret_nfs2(flags, type, xid, vers, proc, data, len);
		return;
	}

	if (vers == 3) {
		interpret_nfs3(flags, type, xid, vers, proc, data, len);
		return;
	}

	if (vers == 4) {
		interpret_nfs4(flags, type, xid, vers, proc, data, len);
		return;
	}
}

static void
interpret_nfs2(flags, type, xid, vers, proc, data, len)
	int flags, type, xid, vers, proc;
	char *data;
	int len;
{
	char *line;
	char buff[NFS_MAXPATHLEN + 1];
	int off, sz;
	char *fh;

	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 NFSPROC_GETATTR:
			case NFSPROC_READLINK:
			case NFSPROC_STATFS:
			case NFSPROC_SETATTR:
				(void) sprintf(line, sum_nfsfh());
				break;
			case NFSPROC_LOOKUP:
			case NFSPROC_REMOVE:
			case NFSPROC_RMDIR:
			case NFSPROC_CREATE:
			case NFSPROC_MKDIR:
				fh = sum_nfsfh();
				(void) sprintf(line, "%s %s",
					fh,
					getxdr_string(buff, NFS_MAXNAMLEN));
				break;
			case NFSPROC_WRITE:
				fh = sum_nfsfh();
				(void) getxdr_long();	/* beginoff */
				off = getxdr_long();
				(void) getxdr_long();	/* totalcount */
				sz  = getxdr_long();
				(void) sprintf(line, "%s at %d for %d",
					fh, off, sz);
				break;
			case NFSPROC_RENAME:
				fh = sum_nfsfh();
				(void) sprintf(line, "%s %s",
					fh,
					getxdr_string(buff, NFS_MAXNAMLEN));
				line += strlen(line);
				fh = sum_nfsfh();
				(void) sprintf(line, " to%s %s",
					fh,
					getxdr_string(buff, NFS_MAXNAMLEN));
				break;
			case NFSPROC_LINK:
				fh = sum_nfsfh();
				(void) sprintf(line, "%s", fh);
				line += strlen(line);
				fh = sum_nfsfh();
				(void) sprintf(line, " to%s %s",
					fh,
					getxdr_string(buff, NFS_MAXNAMLEN));
				break;
			case NFSPROC_SYMLINK:
				fh = sum_nfsfh();
				(void) sprintf(line, "%s %s",
					fh,
					getxdr_string(buff, NFS_MAXNAMLEN));
				line += strlen(line);
				(void) sprintf(line, " to %s",
					getxdr_string(buff, NFS_MAXPATHLEN));
				break;
			case NFSPROC_READDIR:
				fh = sum_nfsfh();
				(void) sprintf(line, "%s Cookie=%lu",
					fh, getxdr_u_long());
				break;
			case NFSPROC_READ:
				fh = sum_nfsfh();
				off = getxdr_long();
				sz  = getxdr_long();
				(void) sprintf(line, "%s at %d for %d",
					fh, off, sz);
				break;
			default:
				break;
			}

			check_retransmit(line, (ulong_t)xid);
		} else {
			(void) sprintf(line, "NFS R %s ",
				procnames_short[proc]);
			line += strlen(line);
			switch (proc) {
			case NFSPROC_CREATE:
			case NFSPROC_MKDIR:
			case NFSPROC_LOOKUP:
				if (sum_nfsstat(line) == 0) {
					line += strlen(line);
					(void) sprintf(line, sum_nfsfh());
				}
				break;
			case NFSPROC_READLINK:
				if (sum_nfsstat(line) == 0) {
					line += strlen(line);
					(void) sprintf(line, " (Path=%s)",
						getxdr_string(buff,
							NFS_MAXPATHLEN));
				}
				break;
			case NFSPROC_GETATTR:
			case NFSPROC_SYMLINK:
			case NFSPROC_STATFS:
			case NFSPROC_SETATTR:
			case NFSPROC_REMOVE:
			case NFSPROC_RMDIR:
			case NFSPROC_WRITE:
			case NFSPROC_RENAME:
			case NFSPROC_LINK:
				(void) sum_nfsstat(line);
				break;
			case NFSPROC_READDIR:
				if (sum_nfsstat(line) == 0) {
					line += strlen(line);
					(void) strcat(line, sum_readdirres());
				}
				break;
			case NFSPROC_READ:
				if (sum_nfsstat(line) == 0) {
					line += strlen(line);
					xdr_skip(68); /* fattrs */
					(void) sprintf(line, " (%ld bytes)",
						getxdr_long());
				}
				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)
			nfscall2(proc);
		else
			nfsreply2(proc);
		show_trailer();
	}
}

/*
 *  Print out version 2 NFS call packets
 */
static void
nfscall2(proc)
	int proc;
{
	switch (proc) {
	case NFSPROC_GETATTR:
	case NFSPROC_READLINK:
	case NFSPROC_STATFS:
		detail_nfsfh();
		break;
	case NFSPROC_SETATTR:
		detail_nfsfh();
		detail_sattr();
		break;
	case NFSPROC_LOOKUP:
	case NFSPROC_REMOVE:
	case NFSPROC_RMDIR:
		detail_diroparg();
		break;
	case NFSPROC_MKDIR:
	case NFSPROC_CREATE:
		detail_diroparg();
		detail_sattr();
		break;
	case NFSPROC_WRITE:
		detail_nfsfh();
		(void) getxdr_long();	/* begoff */
		(void) showxdr_long("Offset = %d");
		(void) getxdr_long();	/* totalcount */
		(void) showxdr_long("(%d bytes(s) of data)");
		break;
	case NFSPROC_RENAME:
		detail_diroparg();
		detail_diroparg();
		break;
	case NFSPROC_LINK:
		detail_nfsfh();
		detail_diroparg();
		break;
	case NFSPROC_SYMLINK:
		detail_diroparg();
		(void) showxdr_string(NFS_MAXPATHLEN, "Path = %s");
		detail_sattr();
		break;
	case NFSPROC_READDIR:
		detail_nfsfh();
		(void) showxdr_u_long("Cookie = %lu");
		(void) showxdr_long("Count = %d");
		break;
	case NFSPROC_READ:
		detail_nfsfh();
		(void) showxdr_long("Offset = %d");
		(void) showxdr_long("Count = %d");
		break;
	default:
		break;
	}
}

/*
 *  Print out version 2 NFS reply packets
 */
static void
nfsreply2(proc)
	int proc;
{
	switch (proc) {
	    case NFSPROC_GETATTR:
	    case NFSPROC_SETATTR:
	    case NFSPROC_WRITE:
		/* attrstat */
		if (detail_nfsstat() == 0) {
			detail_fattr();
		}
		break;
	    case NFSPROC_LOOKUP:
	    case NFSPROC_CREATE:
	    case NFSPROC_MKDIR:
		/* diropres */
		if (detail_nfsstat() == 0) {
			detail_nfsfh();
			detail_fattr();
		}
		break;
	    case NFSPROC_READLINK:
		/* readlinkres */
		if (detail_nfsstat() == 0) {
			(void) showxdr_string(NFS_MAXPATHLEN, "Path = %s");
		}
		break;
	    case NFSPROC_READ:
		/* readres */
		if (detail_nfsstat() == 0) {
			detail_fattr();
			(void) showxdr_long("(%d byte(s) of data)");
		}
		break;
	    case NFSPROC_REMOVE:
	    case NFSPROC_RENAME:
	    case NFSPROC_LINK:
	    case NFSPROC_SYMLINK:
	    case NFSPROC_RMDIR:
		/* stat */
		detail_nfsstat();
		break;
	    case NFSPROC_READDIR:
		/* readdirres */
		if (detail_nfsstat() == 0)
			detail_readdirres();
		break;
	    case NFSPROC_STATFS:
		/* statfsres */
		if (detail_nfsstat() == 0) {
			(void) showxdr_long("Transfer size = %d");
			(void) showxdr_long("Block size = %d");
			(void) showxdr_long("Total blocks = %d");
			(void) showxdr_long("Free blocks = %d");
			(void) showxdr_long("Available blocks = %d");
		}
		break;
	    default:
		break;
	}
}

static void
detail_diroparg()
{
	detail_nfsfh();
	(void) showxdr_string(NFS_MAXPATHLEN, "File name = %s");
}

/*
 * V2 NFS protocol was implicitly linked with SunOS errnos.
 * Some of the errno values changed in SVr4.
 * Need to map errno value so that SVr4 snoop will interpret
 * them correctly.
 */
static char *
statusmsg(status)
	ulong_t status;
{
	switch (status) {
	case NFS_OK: return ("OK");
	case NFSERR_PERM: return ("Not owner");
	case NFSERR_NOENT: return ("No such file or directory");
	case NFSERR_IO: return ("I/O error");
	case NFSERR_NXIO: return ("No such device or address");
	case NFSERR_ACCES: return ("Permission denied");
	case NFSERR_EXIST: return ("File exists");
	case NFSERR_XDEV: return ("Cross-device link");
	case NFSERR_NODEV: return ("No such device");
	case NFSERR_NOTDIR: return ("Not a directory");
	case NFSERR_ISDIR: return ("Is a directory");
	case NFSERR_INVAL: return ("Invalid argument");
	case NFSERR_FBIG: return ("File too large");
	case NFSERR_NOSPC: return ("No space left on device");
	case NFSERR_ROFS: return ("Read-only file system");
	case NFSERR_OPNOTSUPP: return ("Operation not supported");
	case NFSERR_NAMETOOLONG: return ("File name too long");
	case NFSERR_NOTEMPTY: return ("Directory not empty");
	case NFSERR_DQUOT: return ("Disc quota exceeded");
	case NFSERR_STALE: return ("Stale NFS file handle");
	case NFSERR_REMOTE: return ("Object is remote");
	case NFSERR_WFLUSH: return ("write cache flushed");
	default: return ("(unknown error)");
	}
	/* NOTREACHED */
}

int
sum_nfsstat(line)
	char *line;
{
	ulong_t status;

	status = getxdr_long();
	(void) strcpy(line, statusmsg(status));
	return (status);
}

int
detail_nfsstat()
{
	ulong_t status;
	int pos;

	pos = getxdr_pos();
	status = getxdr_long();
	(void) sprintf(get_line(pos, getxdr_pos()),
		"Status = %lu (%s)",
		status, statusmsg(status));

	return ((int)status);
}

int
sum_filehandle(len)
	int len;
{
	int i, l;
	int fh = 0;

	for (i = 0; i < len; i += 4) {
		l = getxdr_long();
		fh ^= (l >> 16) ^ l;
	}

	return (fh);
}

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

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

void
detail_nfsfh()
{
	int pos;
	int fh;

	pos = getxdr_pos();
	fh = sum_filehandle(NFS_FHSIZE);
	setxdr_pos(pos);
	(void) sprintf(get_line(0, 0), "File handle = [%04X]", fh & 0xFFFF);
	(void) showxdr_hex(NFS_FHSIZE, " %s");
}

static void
detail_mode(mode)
	int mode;
{
	char *str;

	switch (mode & S_IFMT) {
	case S_IFDIR: str = "Directory";	break;
	case S_IFCHR: str = "Character";	break;
	case S_IFBLK: str = "Block";		break;
	case S_IFREG: str = "Regular file";	break;
	case S_IFLNK: str = "Link";		break;
	case S_IFSOCK: str = "Socket";		break;
	case S_IFIFO: str = "Fifo";		break;
	default: str = "?";			break;
	}

	(void) sprintf(get_line(0, 0), "Mode = 0%o", mode);
	(void) sprintf(get_line(0, 0), " Type = %s", str);
	(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));
}

void
detail_fattr()
{
	int fltype, mode, nlinks, uid, gid, size, blksz;
	int rdev, blocks, fsid, fileid;

	fltype = getxdr_long();
	mode = getxdr_long();
	nlinks = getxdr_long();
	uid = getxdr_long();
	gid = getxdr_long();
	size = getxdr_long();
	blksz = getxdr_long();
	rdev = getxdr_long();
	blocks = getxdr_long();
	fsid = getxdr_long();
	fileid = getxdr_long();

	(void) sprintf(get_line(0, 0),
		"File type = %d (%s)",
		fltype, filetype(fltype));
	detail_mode(mode);
	(void) sprintf(get_line(0, 0),
		"Link count = %d, UID = %d, GID = %d, Rdev = 0x%x",
		nlinks, uid, gid, rdev);
	(void) sprintf(get_line(0, 0),
		"File size = %d, Block size = %d, No. of blocks = %d",
		size, blksz, blocks);
	(void) sprintf(get_line(0, 0),
		"File system id = %d, File id = %d",
		fsid, fileid);
	(void) showxdr_date("Access time       = %s");
	(void) showxdr_date("Modification time = %s");
	(void) showxdr_date("Inode change time = %s");
}

static void
detail_sattr()
{
	int mode;

	mode = getxdr_long();
	detail_mode(mode);
	(void) showxdr_long("UID = %d");
	(void) showxdr_long("GID = %d");
	(void) showxdr_long("Size = %d");
	(void) showxdr_date("Access time       = %s");
	(void) showxdr_date("Modification time = %s");
}

static char *
filetype(n)
	int n;
{
	switch (n) {
	    case NFREG: return ("Regular File");
	    case NFDIR: return ("Directory");
	    case NFBLK: return ("Block special");
	    case NFCHR: return ("Character special");
	    case NFLNK: return ("Symbolic Link");
	    default:	return ("?");
	}
}

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 char *
sum_readdirres()
{
	static char buff[NFS_MAXNAMLEN + 1];
	int entries = 0;

	if (setjmp(xdr_err)) {
		(void) sprintf(buff, " %d+ entries (incomplete)", entries);
		return (buff);
	}
	while (getxdr_long()) {
		entries++;
		(void) getxdr_long();			/* fileid */
		(void) getxdr_string(buff, NFS_MAXNAMLEN); /* name */
		(void) getxdr_u_long();			/* cookie */
	}

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

static void
detail_readdirres()
{
	ulong_t fileid, cookie;
	int entries = 0;
	char *name;
	char buff[NFS_MAXNAMLEN + 1];

	(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_long()) {
		entries++;
		fileid = getxdr_long();
		name = (char *)getxdr_string(buff, NFS_MAXNAMLEN);
		cookie = getxdr_u_long();
		(void) sprintf(get_line(0, 0),
			" %7lu %7lu %s",
			fileid, cookie, name);
	}

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

void
skip_fattr()
{

	xdr_skip(17 * 4);	/* XDR sizeof nfsfattr */
}