/*
 * Copyright 1993, 1994 by Ulrich Khn. All rights reserved.
 *
 * THIS PROGRAM COMES WITH ABSOLUTELY NO WARRANTY, NOT
 * EVEN THE IMPLIED WARRANTIES OF MERCHANTIBILITY OR
 * FITNESS FOR A PARTICULAR PURPOSE. USE AT YOUR OWN
 * RISK.
 */

/*
 * netdev.c networking filesystem driver, device driver functions
 */


#include <string.h>
#include <macros.h>
#include "atarierr.h"
#include "kernel.h"
#include "nfs.h"
#include "xdr.h"
#include "netfs.h"
#include "proto.h"
#include "config.h"



#define FUTIME  (('F' << 8) | 3)

struct _mutimbuf
{
	unsigned short actime, acdate;
	unsigned short modtime, moddate;
};




DEVDRV nfs_device = {
	nfs_open, nfs_write, nfs_read, nfs_lseek, nfs_ioctl, nfs_datime,
	nfs_close, nfs_select, nfs_unselect,
};


long
nfs_open(FILEPTR *f)
{
	NFS_INDEX *ni = (NFS_INDEX*)f->fc.index;
	long r;

	DEBUG(("nfs_open(%s, 0x%x)", ni->name, f->flags));
	if (ROOT_INDEX == ni)
	{
		DEBUG(("nfs_open: root dir is not a file, -> EACCDN"));
		return EACCDN;
	}
	if ((f->flags & O_TRUNC) && (ni->attr.size != 0))
	{
		/* The file has to be truncated...
		 * NOTE: if this file has been just created, the length field is
		 *       set to 0, so we do not get here.
		 */
		sattr attr;

		DEBUG(("nfs_open: truncating file to 0 length"));
		attr.mode = (u_long)-1L;
		attr.uid = attr.gid = (u_long)-1L;
		attr.size = 0;
		attr.atime.seconds = attr.atime.useconds = (u_long)-1L;
		attr.mtime.seconds = attr.mtime.useconds = (u_long)-1L;
		r = do_sattr(&f->fc, &attr);
		if (r != 0)
		{
			DEBUG(("nfs_open : truncation to 0 failed -> EACCDN", ni->name));
			return EACCDN;
		}
	}

#ifdef USE_MOUNT_OPT
	if (ni->opt->flags & OPT_RO)
	{
		if ( ((f->flags & O_RWMODE) == O_RDWR) ||
		     ((f->flags & O_RWMODE) == O_WRONLY) )
		{
			DEBUG(("nfs_open: mount is read-only ->EACCDN"));
			return EACCDN;
		}
	}
#endif

	DEBUG(("nfs_open(%s) -> ok", ni->name));
	return 0;
}


/* BUG: should we really allways return EWRITF? Better might be the number of
 *      already written bytes.
 */
long
nfs_write(FILEPTR *f, char *buf, long bytes)
{
	NFS_INDEX *ni = (NFS_INDEX*)f->fc.index;
	MESSAGE *mreq, *mrep, m;
	long r, pos, count, written;
	writeargs write_arg;
	attrstat write_res;
	xdrs x;

	/* TL: If we get an NFSERR_IO we'll reduce the wsize for this request
	 * and retry.
	 * It would be better to adjust wsize dynamically but this is better
	 * than nothing.
	 */
	long wsize = ni->opt->wsize;

	/* TL: If somehow mounted with too big wsize reduce it here. */
	if (wsize > MAXDATA)
		wsize = ni->opt->wsize = MAXDATA;

	if (ROOT_INDEX == ni)
	{
		DEBUG(("nfs_write: attempt to write root dir! -> 0"));
		return 0;
	}

#ifdef USE_MOUNT_OPT
	if (ni->opt->flags & OPT_RO)
	{
		DEBUG(("nfs_write: mount is read-only -> EACCDN"));
		return EACCDN;
	}
#endif

	TRACE(("nfs_write: writing %ld bytes to file '%s'", bytes, ni->name));
	written = 0;
	pos = f->pos;
	while (bytes > 0)
	{
		count = (bytes > wsize) ? wsize : bytes;
		write_arg.file = ni->handle;
		write_arg.beginoffset = 0;
		write_arg.offset = pos;
		write_arg.totalcount = count;
		write_arg.data_val = buf+written;
		write_arg.data_len = count;
		mreq = alloc_message(&m, NULL, 0, xdr_size_writeargs(&write_arg));
		if (!mreq)
		{
			DEBUG(("nfs_write: could not allocate message buffer -> EWRITF"));
			return EWRITF;
		}
		xdr_init(&x, mreq->data, mreq->data_len, XDR_ENCODE, NULL);
		if (!xdr_writeargs(&x, &write_arg))
		{
			DEBUG(("nfs_write: failed to encode arguments -> EWRITF"));
			return EWRITF;
		}

		r = rpc_request(&ni->opt->server, mreq, NFSPROC_WRITE, &mrep);
		if (r != 0)
		{
			DEBUG(("nfs_write: could not contact server -> EWRITF"));
			return EWRITF;
		}

		xdr_init(&x, mrep->data, mrep->data_len, XDR_DECODE, NULL);
		if (!xdr_attrstat(&x, &write_res))
		{
			DEBUG(("nfs_write: failed to decode results -> EWRITF"));
			free_message(mrep);
			return EWRITF;
		}
		free_message(mrep);

		if (write_res.status != NFS_OK)
		{
			/* TL: Reduce the wsize and try again */
			if (write_res.status == NFSERR_IO) {
				if (wsize > 1023) {
					wsize >>= 1;
					continue;
				} else {
					DEBUG(("nfs_write: write failed -> EWRITF"));
					return EWRITF;
				}
			}
		}

		fattr2xattr(&write_res.attrstat_u.attributes, &ni->attr);
		ni->stamp = get_timestamp();

		written += count;
		pos += count;
		bytes -= count;
	}
	f->pos = pos;
	TRACE(("nfs_write(%s) -> %ld", ni->name, written));
	return written;
}



/* BUG: should we really allways return EREADF? Better might be the number of
 *      already read bytes.
 */
long
nfs_read(FILEPTR *f, char *buf, long bytes)
{
	char req_buf[READBUFSIZE];
	NFS_INDEX *ni = (NFS_INDEX*)f->fc.index;
	MESSAGE *mreq, *mrep, m;
	long r, pos, count, read;
	xdrs x;
	readargs read_arg;
	readres read_res;
	/* TL: If we get an NFSERR_IO try to reduce the rsize for this request
	 * and retry.
	 * It would be better to adjust rsize dynamically but this is better
	 * than nothing.
	 */
	long rsize = ni->opt->rsize;

	/* TL: If somehow mounted with too big rsize reduce it here. */
	if (rsize > MAXDATA)
		rsize = ni->opt->rsize = MAXDATA;

	if (ROOT_INDEX == ni)
	{
		DEBUG(("nfs_read: attempt to read root dir! -> 0"));
		return 0;
	}
	TRACE(("nfs_read: reading %ld bytes for file '%s'", bytes, ni->name));
	read = 0;
	pos = f->pos;
	while (bytes > 0)
	{
		count = (bytes > ni->opt->rsize) ? ni->opt->rsize : bytes;
		read_arg.file = ni->handle;
		read_arg.offset = pos;
		read_arg.count = count;
		read_arg.totalcount = count;
		mreq = alloc_message(&m, req_buf, READBUFSIZE,
		                              xdr_size_readargs(&read_arg));
		if (!mreq)
		{
			DEBUG(("nfs_read: failed to allocate message buffer, -> EREADF"));
			return EREADF;
		}

		xdr_init(&x, mreq->data, mreq->data_len, XDR_ENCODE, NULL);
		if (!xdr_readargs(&x, &read_arg))
		{
			DEBUG(("nfs_read: failed to encode arguments, -> EREADF"));
			return EREADF;
		}

		r = rpc_request(&ni->opt->server, mreq, NFSPROC_READ, &mrep);
		if (r != 0)
		{
			DEBUG(("nfs_read: failed to contact server, -> EREADF"));
			return EREADF;
		}

		read_res.readres_u.read_ok.data_val = buf+read;
		xdr_init(&x, mrep->data, mrep->data_len, XDR_DECODE, NULL);
		if (!xdr_readres(&x, &read_res))
		{
			DEBUG(("nfs_read: could not decode results, -> EREADF"));
			free_message(mrep);
			return EREADF;
		}
		free_message(mrep);
		if (read_res.status != NFS_OK)
		{
/* TL: Try to reduce the rsize */
			if (read_res.status == NFSERR_IO) {
				if (rsize > 1023) {
					rsize >>= 1;
					continue;
				} else {
					/* read failed for some reason */
					DEBUG(("nfs_read: request failed, -> EREADF"));
					return EREADF;
				}
			}
		}
		r = read_res.readres_u.read_ok.data_len;
		read += r;
		pos += r;
		bytes -= r;
		fattr2xattr(&read_res.readres_u.read_ok.attributes, &ni->attr);
		ni->stamp = get_timestamp();
		if (r < count)  /* no more data */
		{
			DEBUG(("nfs_read: read only %ld bytes, -> ok", r));
			break;
		}
	}
	f->pos = pos;
	return read;
}


long
nfs_lseek(FILEPTR *f, long where, _wORD whence)
{
	long r;
	NFS_INDEX *ni = (NFS_INDEX*)f->fc.index;

	TRACE(("nfs_lseek(.., %ld, %d)", where, whence));
	switch (whence)
	{
		case SEEK_SET:
			if (where < 0)
				return ERANGE;
			f->pos = where;
			return f->pos;
		case SEEK_CUR:
			if (f->pos + where < 0)
				return ERANGE;
			f->pos += where;
			return f->pos;
		case SEEK_END:
			r = nfs_getxattr(&f->fc, NULL);
			if (r != 0)
			{
				DEBUG(("nfs_lseek: nfs_getxattr failed while SEEK_END, -> %ld", r));
				return r;
			}
			if (where <  - ni->attr.size)  /* seek before beginning of file */
				return ERANGE;
			return (f->pos = ni->attr.size+where);
	}
	return ERANGE;
}


long
nfs_ioctl(FILEPTR *f, _wORD mode, void *buf)
{
	struct _mutimbuf *tp = (struct _mutimbuf*)buf;

	if (FUTIME == mode)
	{
		sattr attr;

		if (!tp)
			return EINVFN;
		attr.uid = (u_long)-1L;
		attr.gid = (u_long)-1L;
		attr.mode = (u_long)-1L;
		attr.size = (u_long)-1L;
		attr.atime.seconds = Unixtime(tp->actime, tp->acdate);
		attr.atime.useconds = 0;
		attr.mtime.seconds = Unixtime(tp->modtime, tp->moddate);
		attr.mtime.useconds = 0;
		return do_sattr(&f->fc, &attr);
	}
	if (FIONREAD == mode)
	{
		NFS_INDEX *ni = (NFS_INDEX*)f->fc.index;
		long r = nfs_getxattr(&f->fc, NULL);

		if (r != 0)
		{
			DEBUG(("nf_ioctl: cant get file attributes, -> %ld", r));
			return r;
		}
		*(long*)buf = min(ni->attr.size-f->pos, 0);
		return 0;
	}
	if (FIONWRITE == mode)
	{
		NFS_INDEX *ni = (NFS_INDEX*)f->fc.index;

		*(long*)buf = ni->opt->wsize;
		return 0;
	}	
	return EINVFN;
}


long
nfs_datime(FILEPTR *f, _wORD *timeptr, _wORD rwflag)
{
	if (rwflag)
	{
		/* set access and modification time of the file; we dont
		 * have any chance to change the creation time, as the nfs
		 * protcol does not specify this
		 */
		sattr attr;

		attr.uid = (u_long)-1L;
		attr.gid = (u_long)-1L;
		attr.mode = (u_long)-1L;
		attr.size = (u_long)-1L;
		attr.atime.seconds = Unixtime(timeptr[0], timeptr[1]);
		attr.atime.useconds = 0;
		attr.mtime = attr.atime;
		return do_sattr(&f->fc, &attr);
	}
	else
	{
		long r;
		NFS_INDEX *ni = (NFS_INDEX*)f->fc.index;

		r = nfs_getxattr(&f->fc, NULL);  /* update cache if necessary */
		if (r != 0)
		{
			DEBUG(("nfs_datime: nfs_getattr failed, -> %ld", r));
			return r;
		}
		timeptr[0] = ni->attr.atime;
		timeptr[1] = ni->attr.adate;
		return 0;
	}
}


long
nfs_close(FILEPTR *f, _wORD pid)
{
	TRACE(("nfs_close -> ok"));
	/* nothing to do */
	return 0;
}


long
nfs_select(FILEPTR *f, long proc, _wORD mode)
{
	TRACE(("nfs_select"));
	return 1;
}


void
nfs_unselect(FILEPTR *f, long proc, _wORD mode)
{
	TRACE(("nfs_unselect"));
	/* do nothing */
}
