/*
 * 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 2004 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Simple nfs V4 ops
 */

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

#include <rpc/types.h>
#include <rpc/auth.h>
#include <sys/t_lock.h>
#include "clnt.h"
#include <sys/fcntl.h>
#include <sys/vfs.h>
#include <errno.h>
#include <sys/promif.h>
#include <rpc/xdr.h>
#include "nfs_inet.h"
#include <sys/stat.h>
#include <sys/bootvfs.h>
#include <sys/bootdebug.h>
#include <sys/salib.h>
#include <sys/sacache.h>
#include <rpc/rpc.h>
#include "brpc.h"
#include <rpcsvc/nfs4_prot.h>

#define	dprintf	if (boothowto & RB_DEBUG) printf

static struct timeval zero_timeout = {0, 0};	/* default */

/*
 * NFS Version 4 specific functions
 */

ssize_t
nfs4read(struct nfs_file *filep, char *buf, size_t size)
{
	enum clnt_stat	status;
	read4arg_t	readargs;
	read4res_t	readres;
	char		*buf_offset;
	uint_t		count = 0;
	uint_t		readcnt = 0;
	bool_t		done = FALSE;
	struct timeval	timeout;
	int		framing_errs = 0;
#ifndef i386
	static uint_t	pos;
	static char	ind[] = "|/-\\";
	static int	blks_read;
#endif
	utf8string	str;
	char		tagname[] = "inetboot read";

	bzero(&readres, sizeof (readres));

	str.utf8string_len = sizeof (tagname) - 1;
	str.utf8string_val = tagname;

	/*
	 * read
	 */
	buf_offset = buf;

	if (nfs_readsize == 0)
		nfs_readsize = READ4_SIZE;

	if (size < nfs_readsize)
		readargs.r_count = size;
	else
		readargs.r_count = nfs_readsize;

	if (filep->fh.fh4.len > 0)
		compound_init(&readargs.r_arg, &str, 0, 2, &filep->fh.fh4);
	else
		compound_init(&readargs.r_arg, &str, 0, 2, NULL);

	readargs.r_opread = OP_READ;
	/*
	 * zero out the stateid field
	 */
	bzero(&readargs.r_stateid, sizeof (readargs.r_stateid));
	readargs.r_offset = filep->offset;

	do {
		readres.r_data_val = buf_offset;

		if ((count + readargs.r_count) > size)
			readargs.r_count = size - count;

		timeout.tv_sec = NFS_REXMIT_MIN;
		timeout.tv_usec = 0;

		do {
			status = CLNT_CALL(root_CLIENT, NFSPROC4_COMPOUND,
					xdr_read4_args, (caddr_t)&readargs,
					xdr_read4_res, (caddr_t)&readres,
					timeout);

			if (status == RPC_TIMEDOUT) {
	dprintf("NFS read(%d) timed out. Retrying...\n", readargs.r_count);
				if (errno == ETIMEDOUT)
					framing_errs++;

				if (framing_errs > NFS_MAX_FERRS &&
				    readargs.r_count > NFS_READ_DECR) {
					readargs.r_count /= 2;
					nfs_readsize /= 2;
					dprintf("NFS read size now %d.\n",
						nfs_readsize);
					timeout.tv_sec = NFS_REXMIT_MIN;
					framing_errs = 0;
				} else {
					if (timeout.tv_sec < NFS_REXMIT_MAX)
						timeout.tv_sec++;
					else
						timeout.tv_sec = 0;
				}
			}
		} while (status == RPC_TIMEDOUT);

		if (status != RPC_SUCCESS)
			return (-1);

		if (readres.r_status != NFS4_OK) {
			nfs4_error(readres.r_status);
			return (-1);
		}

		readcnt = readres.r_data_len;

		if (readres.r_eof == TRUE)
			done = TRUE;

		if (readcnt < readargs.r_count) {
#ifdef NFS_OPS_DEBUG
			if ((boothowto & DBFLAGS) == DBFLAGS)
		printf("nfs4read: partial read %d instead of %d\n", readcnt,
			readargs.count);
#endif
			done = TRUE;
		}

		count += readcnt;
		filep->offset += readcnt;
		buf_offset += readcnt;
		readargs.r_offset += readcnt;
#ifndef i386
		if ((blks_read++ & 0x3) == 0)
			printf("%c\b", ind[pos++ & 3]);
#endif
	} while (count < size && !done);

	return (count);
}


static vtype_t nf4_to_vt[] = {
	VBAD, VREG, VDIR, VBLK, VCHR, VLNK, VSOCK, VFIFO
};

int
nfs4getattr(struct nfs_file *nfp, struct vattr *vap)
{
	enum clnt_stat	status;
	attr4_bitmap1_t bitmap1;
	attr4_bitmap2_t bitmap2;
	getattr4arg_t	getattrargs;
	getattr4res_t	getattrres;
	b_fattr4_t	*bfattr4;
	utf8string	str;
	char		tagname[] = "inetboot getattr";

	bzero(&getattrres, sizeof (getattrres));
	/*
	 * Putfh
	 */
	str.utf8string_len = sizeof (tagname) - 1;
	str.utf8string_val = tagname;

	if (nfp->fh.fh4.len > 0)
		compound_init(&getattrargs.ga_arg, &str, 0, 2, &nfp->fh.fh4);
	else
		compound_init(&getattrargs.ga_arg, &str, 0, 2, NULL);

	/*
	 * getattr
	 */
	getattrargs.ga_opgetattr = OP_GETATTR;
	/*
	 * Set up the attribute bitmap.  We pretty much need everything
	 * except for the filehandle and supported attrs.
	 */
	bitmap1.word = 0;
	bitmap1.bm_fattr4_type = 1;
	bitmap1.bm_fattr4_size = 1;
	bitmap1.bm_fattr4_fileid = 1;
	bitmap2.word = 0;
	bitmap2.bm_fattr4_mode = 1;
	bitmap2.bm_fattr4_time_access = 1;
	bitmap2.bm_fattr4_time_metadata = 1;
	bitmap2.bm_fattr4_time_modify = 1;

	getattrargs.ga_attr_req.b_bitmap_len = NFS4_MAX_BITWORDS;
	getattrargs.ga_attr_req.b_bitmap_val[0] = bitmap1.word;
	getattrargs.ga_attr_req.b_bitmap_val[1] = bitmap2.word;

	status = CLNT_CALL(root_CLIENT, NFSPROC4_COMPOUND, xdr_getattr4_args,
				(caddr_t)&getattrargs, xdr_getattr4_res,
				(caddr_t)&getattrres, zero_timeout);

	if (status != RPC_SUCCESS) {
		dprintf("nfs4getattr: RPC error %d\n", status);
		return (-1);
	}

	if (getattrres.gr_attr_status != NFS4_OK) {
		nfs4_error(getattrres.gr_attr_status);
		return (getattrres.gr_attr_status);
	}

	bfattr4 = &getattrres.gr_attrs;
	if (vap->va_mask & AT_TYPE) {
		if (bfattr4->b_fattr4_type < NF4REG ||
		    bfattr4->b_fattr4_type > NF4FIFO)
			vap->va_type = VBAD;
		else
			vap->va_type = nf4_to_vt[bfattr4->b_fattr4_type];
	}
	if (vap->va_mask & AT_MODE)
		vap->va_mode = (mode_t)bfattr4->b_fattr4_mode;
	if (vap->va_mask & AT_SIZE)
		vap->va_size = (u_offset_t)bfattr4->b_fattr4_size;
	if (vap->va_mask & AT_NODEID)
		vap->va_nodeid = (uint64_t)bfattr4->b_fattr4_fileid;
	/*
	 * XXX - may need to do something more here.
	 */
	if (vap->va_mask & AT_ATIME) {
		vap->va_atime.tv_sec = bfattr4->b_fattr4_time_access.seconds;
		vap->va_atime.tv_nsec = bfattr4->b_fattr4_time_access.nseconds;
	}
	if (vap->va_mask & AT_CTIME) {
		vap->va_ctime.tv_sec = bfattr4->b_fattr4_time_metadata.seconds;
		vap->va_ctime.tv_nsec =
			bfattr4->b_fattr4_time_metadata.nseconds;
	}
	if (vap->va_mask & AT_MTIME) {
		vap->va_mtime.tv_sec = bfattr4->b_fattr4_time_modify.seconds;
		vap->va_mtime.tv_nsec = bfattr4->b_fattr4_time_modify.nseconds;
	}

	return (NFS4_OK);
}

/*
 * Display nfs error messages.
 */
/*ARGSUSED*/
void
nfs4_error(enum nfsstat4 status)
{
	if (!(boothowto & RB_DEBUG))
		return;

	switch (status) {
	case NFS4_OK:
		printf("NFS: No error.\n");
		break;
	case NFS4ERR_PERM:
		printf("NFS: Not owner.\n");
		break;
	case NFS4ERR_NOENT:
#ifdef	NFS_OPS_DEBUG
		printf("NFS: No such file or directory.\n");
#endif	/* NFS_OPS_DEBUG */
		break;
	case NFS4ERR_IO:
		printf("NFS: IO ERROR occurred on NFS server.\n");
		break;
	case NFS4ERR_NXIO:
		printf("NFS: No such device or address.\n");
		break;
	case NFS4ERR_ACCESS:
		printf("NFS: Permission denied.\n");
		break;
	case NFS4ERR_EXIST:
		printf("NFS: File exists.\n");
		break;
	case NFS4ERR_XDEV:
		printf("NFS: Cross device hard link.\n");
		break;
	case NFS4ERR_NOTDIR:
		printf("NFS: Not a directory.\n");
		break;
	case NFS4ERR_ISDIR:
		printf("NFS: Is a directory.\n");
		break;
	case NFS4ERR_INVAL:
		printf("NFS: Invalid argument.\n");
		break;
	case NFS4ERR_FBIG:
		printf("NFS: File too large.\n");
		break;
	case NFS4ERR_NOSPC:
		printf("NFS: No space left on device.\n");
		break;
	case NFS4ERR_ROFS:
		printf("NFS: Read-only filesystem.\n");
		break;
	case NFS4ERR_MLINK:
		printf("NFS: Too many hard links.\n");
		break;
	case NFS4ERR_NAMETOOLONG:
		printf("NFS: File name too long.\n");
		break;
	case NFS4ERR_NOTEMPTY:
		printf("NFS: Directory not empty.\n");
		break;
	case NFS4ERR_DQUOT:
		printf("NFS: Disk quota exceeded.\n");
		break;
	case NFS4ERR_STALE:
		printf("NFS: Stale file handle.\n");
		break;
	case NFS4ERR_BADHANDLE:
		printf("NFS: Illegal NFS file handle.\n");
		break;
	case NFS4ERR_BAD_COOKIE:
		printf("NFS: Stale Cookie.\n");
		break;
	case NFS4ERR_NOTSUPP:
		printf("NFS: Operation is not supported.\n");
		break;
	case NFS4ERR_TOOSMALL:
		printf("NFS: Buffer too small.\n");
		break;
	case NFS4ERR_SERVERFAULT:
		printf("NFS: Server fault.\n");
		break;
	case NFS4ERR_BADTYPE:
		printf("NFS: Unsupported object type.\n");
		break;
	case NFS4ERR_BAD_STATEID:
		printf("NFS: Bad stateid\n");
		break;
	case NFS4ERR_BAD_SEQID:
		printf("NFS: Bad seqid\n");
		break;
	default:
		printf("NFS: unknown error.\n");
		break;
	}
}

/*
 * lookup one component.  for multicomponent lookup use a driver like lookup().
 */
struct nfs_file *
nfs4lookup(struct nfs_file *dir, char *name, int *nstat)
{
	static struct nfs_file	cd;
	attr4_bitmap1_t		bitmap1;
	lookup4arg_t		lookupargs;
	lookup4res_t		lookupres;
	enum clnt_stat		status;
	utf8string		str;
	char			tagname[] = "inetboot lookup";

	/*
	 * NFSv4 uses a special LOOKUPP op
	 * for looking up the parent directory.
	 */
	if (strcmp(name, "..") == 0)
		return (nfs4lookupp(dir, nstat, NULL));

	*nstat = (int)NFS4_OK;

	bzero(&lookupres, sizeof (lookupres));

	/*
	 * Check if we have a filehandle and initialize the compound
	 * with putfh or putrootfh appropriately.
	 */
	str.utf8string_len = sizeof (tagname) - 1;
	str.utf8string_val = tagname;

	if (dir->fh.fh4.len > 0)
		compound_init(&lookupargs.la_arg, &str, 0, 3, &dir->fh.fh4);
	else
		compound_init(&lookupargs.la_arg, &str, 0, 3, NULL);

	/*
	 * lookup
	 */
	lookupargs.la_oplookup = OP_LOOKUP;
	/*
	 * convert the pathname from char * to utf8string
	 */
	lookupargs.la_pathname.utf8string_len = strlen(name);
	lookupargs.la_pathname.utf8string_val =
		bkmem_alloc(lookupargs.la_pathname.utf8string_len);
	if (lookupargs.la_pathname.utf8string_val == NULL) {
		dprintf("nfs4lookup: bkmem_alloc failed\n");
		return (NULL);
	}
	bcopy(name, lookupargs.la_pathname.utf8string_val,
		lookupargs.la_pathname.utf8string_len);

	/*
	 * Setup the attr bitmap.  All we need is the type and filehandle info
	 */
	lookupargs.la_opgetattr = OP_GETATTR;
	bitmap1.word = 0;
	bitmap1.bm_fattr4_type = 1;
	bitmap1.bm_fattr4_filehandle = 1;
	lookupargs.la_attr_req.b_bitmap_len = 1;
	lookupargs.la_attr_req.b_bitmap_val[0] = bitmap1.word;
	lookupargs.la_attr_req.b_bitmap_val[1] = 0;

	status = CLNT_CALL(root_CLIENT, NFSPROC4_COMPOUND, xdr_lookup4_args,
			(caddr_t)&lookupargs, xdr_lookup4_res,
			(caddr_t)&lookupres, zero_timeout);

	if (status != RPC_SUCCESS) {
		dprintf("nfs4lookup: RPC error. status %d\n", status);
		return (NULL);
	}

	if (lookupres.lr_lookup_status != NFS4_OK) {
#ifdef DEBUG
		dprintf("nfs4lookup: lookup status = %d\n",
			lookupres.lr_lookup_status);
#endif
		nfs4_error(lookupres.lr_lookup_status);
		*nstat = (int)lookupres.lr_lookup_status;
		if (lookupargs.la_pathname.utf8string_val != NULL)
			bkmem_free(lookupargs.la_pathname.utf8string_val,
					lookupargs.la_pathname.utf8string_len);
		return (NULL);
	}

	if (lookupres.lr_attr_status != NFS4_OK) {
#ifdef DEBUG
		dprintf("nfs4lookup: getattr status = %d\n",
			lookupres.lr_attr_status);
#endif
		nfs4_error(lookupres.lr_attr_status);
		*nstat = (int)lookupres.lr_attr_status;
		if (lookupargs.la_pathname.utf8string_val != NULL)
			bkmem_free(lookupargs.la_pathname.utf8string_val,
					lookupargs.la_pathname.utf8string_len);
		return (NULL);
	}

	/*
	 * We have all the information we need to update the file pointer
	 */
	bzero((caddr_t)&cd, sizeof (struct nfs_file));
	cd.version = NFS_V4;
	cd.ftype.type4 = lookupres.lr_attrs.b_fattr4_type;
	cd.fh.fh4.len = lookupres.lr_attrs.b_fattr4_filehandle.len;
	bcopy(lookupres.lr_attrs.b_fattr4_filehandle.data, cd.fh.fh4.data,
		cd.fh.fh4.len);

	/*
	 * Free the arg string
	 */
	if (lookupargs.la_pathname.utf8string_val != NULL)
		bkmem_free(lookupargs.la_pathname.utf8string_val,
				lookupargs.la_pathname.utf8string_len);

	return (&cd);
}

/*
 * lookup parent directory.
 */
struct nfs_file *
nfs4lookupp(struct nfs_file *dir, int *nstat, uint64_t *fileid)
{
	static struct nfs_file	cd;
	attr4_bitmap1_t		bitmap1;
	lookupp4arg_t		lookuppargs;
	lookup4res_t		lookupres;
	enum clnt_stat		status;
	utf8string		str;
	char			tagname[] = "inetboot lookupp";

	*nstat = (int)NFS4_OK;

	bzero(&lookupres, sizeof (lookupres));

	/*
	 * Check if we have a filehandle and initialize the compound
	 * with putfh or putrootfh appropriately.
	 */
	str.utf8string_len = sizeof (tagname) - 1;
	str.utf8string_val = tagname;

	if (dir->fh.fh4.len > 0)
		compound_init(&lookuppargs.la_arg, &str, 0, 3, &dir->fh.fh4);
	else
		compound_init(&lookuppargs.la_arg, &str, 0, 3, NULL);

	/*
	 * lookupp
	 */
	lookuppargs.la_oplookupp = OP_LOOKUPP;
	/*
	 * Setup the attr bitmap.  Normally, all we need is the type and
	 * filehandle info, but getdents might require the fileid of the
	 * parent.
	 */
	lookuppargs.la_opgetattr = OP_GETATTR;
	bitmap1.word = 0;
	bitmap1.bm_fattr4_type = 1;
	bitmap1.bm_fattr4_filehandle = 1;
	if (fileid != NULL)
		bitmap1.bm_fattr4_fileid = 1;
	lookuppargs.la_attr_req.b_bitmap_len = 1;
	lookuppargs.la_attr_req.b_bitmap_val[0] = bitmap1.word;
	lookuppargs.la_attr_req.b_bitmap_val[1] = 0;

	status = CLNT_CALL(root_CLIENT, NFSPROC4_COMPOUND, xdr_lookupp4_args,
			(caddr_t)&lookuppargs, xdr_lookup4_res,
			(caddr_t)&lookupres, zero_timeout);

	if (status != RPC_SUCCESS) {
		dprintf("nfs4lookupp: RPC error. status %d\n", status);
		return (NULL);
	}

	if (lookupres.lr_lookup_status != NFS4_OK) {
#ifdef DEBUG
		dprintf("nfs4lookupp: lookupp status = %d\n",
			lookupres.lr_lookup_status);
#endif
		nfs4_error(lookupres.lr_lookup_status);
		*nstat = (int)lookupres.lr_lookup_status;
		return (NULL);
	}

	if (lookupres.lr_attr_status != NFS4_OK) {
#ifdef DEBUG
		dprintf("nfs4lookupp: getattr status = %d\n",
			lookupres.lr_attr_status);
#endif
		nfs4_error(lookupres.lr_attr_status);
		*nstat = (int)lookupres.lr_attr_status;
		return (NULL);
	}

	/*
	 * We have all the information we need to update the file pointer
	 */
	bzero((caddr_t)&cd, sizeof (struct nfs_file));
	cd.version = NFS_V4;
	cd.ftype.type4 = lookupres.lr_attrs.b_fattr4_type;
	cd.fh.fh4.len = lookupres.lr_attrs.b_fattr4_filehandle.len;
	bcopy(lookupres.lr_attrs.b_fattr4_filehandle.data, cd.fh.fh4.data,
		cd.fh.fh4.len);

	/*
	 * Fill in the fileid if the user passed in one
	 */
	if (fileid != NULL)
		*fileid = lookupres.lr_attrs.b_fattr4_fileid;

	return (&cd);
}

/*
 * Gets symbolic link into pathname.
 */
int
nfs4getsymlink(struct nfs_file *cfile, char **path)
{
	enum clnt_stat	status;
	readlink4arg_t	readlinkargs;
	readlink4res_t	readlinkres;
	static char	symlink_path[NFS_MAXPATHLEN];
	int		spathlen;
	utf8string	str;
	char		tagname[] = "inetboot getsymlink";
	int		error = NFS4_OK;

	bzero(&readlinkres, sizeof (readlinkres));

	/*
	 * readlink
	 */
	str.utf8string_len = sizeof (tagname) - 1;
	str.utf8string_val = tagname;

	if (cfile->fh.fh4.len > 0)
		compound_init(&readlinkargs.rl_arg, &str, 0, 2,
				&cfile->fh.fh4);
	else
		compound_init(&readlinkargs.rl_arg, &str, 0, 2,	NULL);

	readlinkargs.rl_opreadlink = OP_READLINK;
	status = CLNT_CALL(root_CLIENT, NFSPROC4_COMPOUND, xdr_readlink4_args,
				(caddr_t)&readlinkargs, xdr_readlink4_res,
				(caddr_t)&readlinkres, zero_timeout);

	if (status != RPC_SUCCESS) {
		dprintf("nfs4getsymlink: RPC readlink error %d\n", status);
		error = -1;
		goto out;
	}

	if (readlinkres.rl_status != NFS4_OK) {
		nfs4_error(readlinkres.rl_status);
		error = readlinkres.rl_status;
		goto out;
	}

	/*
	 * Convert the utf8string to a normal character string
	 */
	spathlen = readlinkres.rl_link.utf8string_len;
	if (spathlen <= 0 || readlinkres.rl_link.utf8string_val == NULL) {
		*path = NULL;
		error = readlinkres.rl_status;
		goto out;
	}

	bcopy(readlinkres.rl_link.utf8string_val, symlink_path, spathlen);
	symlink_path[spathlen] = '\0';
	*path = symlink_path;

out:
	/*
	 * Free the results
	 */
	if (readlinkres.rl_link.utf8string_val != NULL)
		bkmem_free(readlinkres.rl_link.utf8string_val, spathlen);

	return (error);
}

/*
 * Should just forget about the tag, but will leave in support for the time
 * being.
 */
void
compound_init(b_compound_t *cp, utf8string *str, uint_t mvers, uint_t arglen,
		struct nfs_bfh4 *pfh)
{
	if (str == NULL || str->utf8string_len == 0) {
		cp->ca_tag.utf8string_len = 0;
		cp->ca_tag.utf8string_val = NULL;
	} else {
		cp->ca_tag.utf8string_len = str->utf8string_len;
		cp->ca_tag.utf8string_val = str->utf8string_val;
	}
	cp->ca_minorversion = mvers;
	cp->ca_argarray_len = arglen;
	if (pfh == NULL) {
		cp->ca_isputrootfh = TRUE;
		cp->ca_opputfh.pf_opnum = OP_PUTROOTFH;
	} else {
		cp->ca_isputrootfh = FALSE;
		cp->ca_opputfh.pf_opnum = OP_PUTFH;
		cp->ca_opputfh.pf_filehandle.len = pfh->len;
		bcopy(pfh->data, cp->ca_opputfh.pf_filehandle.data, pfh->len);
	}
}