/*
 * 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.
 */

/*
 * netdir.c networking filesystem driver, directory 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"


/* Add this to the length of the buffer holding the decoded entries of
 * a directory. It is necessary as the strings in it contain an terminating
 * zero, whereas the XDRed representations don't. This value is too big,
 * so we are on the safe side.
 */
#define ADD_BUF_LEN   (MAX_READDIR_LEN/16)


/* this is placed in the fsstuff field of a dir handle */
typedef struct
{
	char *buffer;   /* current entry buffer */
	entry *curr_entry;  /* this is the entry who is returned next */
	nfscookie lastcookie;   /* this is for further requests to the server */
	short eof;      /* if set, this buffer is the last in the dir */
} NETFS_STUFF;




long
nfs_opendir(DIR *dirh, _wORD flags)
{
	NETFS_STUFF *stuff = (NETFS_STUFF*)&dirh->fsstuff;
	NFS_INDEX *ni = (NFS_INDEX*)dirh->fc.index;

	if (ROOT_INDEX != ni)
	{
		if (get_handle(ni) != 0)
		{
			DEBUG(("nfs_opendir(%s): no handle for dir, -> EPTHNF", ni->name));
			return EPTHNF;
		}
		stuff->buffer = Kmalloc(MAX_READDIR_LEN+ADD_BUF_LEN);
		if (!stuff->buffer)
		{
			DEBUG(("nfs_opendir: out of memory -> ENSMEM"));
			return ENSMEM;
		}
	}
	else
		stuff->buffer = NULL;
	stuff->curr_entry = NULL;
	*(long*)&stuff->lastcookie[0] = 0L;
	stuff->eof = 0;
	dirh->index = 0;
	TRACE(("nfs_opendir(%s) -> ok", (ni) ? ni->name : "root"));
	return 0;
}


long
nfs_rewinddir(DIR *dirh)
{
	NETFS_STUFF *stuff = (NETFS_STUFF*)&dirh->fsstuff;

	if (ROOT_INDEX != (NFS_INDEX*)dirh->fc.index)
	{
		stuff->curr_entry = NULL;
		*(long*)&stuff->lastcookie[0] = 0L;
		stuff->eof = 0;
	}
	dirh->index = 0;
	TRACE(("nfs_rewinddir -> ok"));
	return 0;
}


long
nfs_closedir(DIR *dirh)
{
	NETFS_STUFF *stuff = (NETFS_STUFF*)&dirh->fsstuff;

	if (ROOT_INDEX != (NFS_INDEX*)dirh->fc.index)
	{
		if (stuff->buffer)
			Kfree(stuff->buffer);
	}
	TRACE(("nfs_closedir -> ok"));
	return 0;
}


#define XDR_SIZE_READDIRRES	(3*sizeof(long))
#define MAX_XDR_BUF		(MAX_READDIR_LEN+XDR_SIZE_READDIRRES)

long
nfs_readdir(DIR *dirh, char *name, _wORD namelen, fcookie *fc)
{
	char req_buf[READDIRBUFSIZE];
	int giveindex = dirh->flags == 0;
	int i, dom;
	entry *entp;
	NFS_INDEX *ni = (NFS_INDEX*)dirh->fc.index;
	NFS_INDEX *newi;
	MESSAGE *mreq, *mrep, m;
	long r;
	readdirargs read_arg;
	readdirres read_res;
	xdrs x;
	NETFS_STUFF *stuff = (NETFS_STUFF*)dirh->fsstuff;

	/* we know that ni has a handle, as we did get one in
	 * or before nfs_opendir
	 */
	dom = Pdomain(-1);
	if (ROOT_INDEX == ni)
	{
		/* read the root dir of the file sys */
		TRACE(("nfs_readdir(root)"));
		if (giveindex)
		{
			namelen -= sizeof(long);
			if (namelen <= 0)
				return ERANGE;
			*((long *)name) = dirh->index;
			name += sizeof(long);
		}

		/* Skip the given amount of used indices. Especially skip unused 
		 * indices without counting them.
		 */
		ni = mounted;
		while (ni && (ni->link == 0))
			ni = ni->next;
		for (i = dirh->index++;  i > 0;  i--)
		{
			if (!ni)
				break;
			ni = ni->next;
			while (ni && (ni->link == 0))
				ni = ni->next;
			if (!ni)
				break;
		}

		/* If there are indices left, find the next used one and return it's
		 * name.
		 */
		while (ni && (ni->link == 0))
			ni = ni->next;

		if (!ni)
		{
			DEBUG(("nfs_readdir(root) -> no more files"));
			return ENMFIL;
		}
		strncpy(name, ni->name, namelen-1);
		name[namelen-1] = '\0';
		if (0 == dom)   /* convert to upper case for TOS domain */
			Toupper(name);
		fc->fs = &nfs_filesys;
		fc->dev = nfs_dev;
		fc->aux = 0;
		fc->index = (long)ni;
		if (strlen(ni->name) >= namelen)
			return ERANGE;
		ni->link += 1;
		TRACE(("nfs_readdir -> '%s'", name));
		return 0;
	}

restart:
	TRACE(("trying to get entry from buffer"));
	if (stuff->curr_entry)
	{
		long res = 0;

		entp = stuff->curr_entry;
		if (giveindex)
		{
			namelen -= sizeof(long);
			if (namelen <= 0)
				return ERANGE;
			*((long *)name) = entp->fileid;
			name += sizeof(long);
		}
		strncpy(name, entp->name, namelen-1);
		name[namelen-1] = '\0';
		if (0 == dom)    /* convert to upper case for TOS domain */
			Toupper(name);
		if (strlen(entp->name) >= namelen)
		{
			DEBUG(("nfs_readdir(%s): name buffer (%ld) too short",
			                                          ni->name, namelen));
			res = ERANGE;
			goto prep_next_entry;
		}

		/* check for entries '.' and '..' which have already a local slot */
		if (!strcmp(entp->name, "."))
		{
			newi = ni;  /* '.' does always mean the read directory */
		}
		else if (!strcmp(entp->name, ".."))
		{
			newi = ni->dir;   /* '..' means the parent of the read directory */
		}
		else
		{
			TRACE(("nfs_readdir: getting new slot for '%s'", entp->name));
			newi = get_slot(ni, entp->name, (dirh->flags & TOS_SEARCH) ? 0 : 1);
			if (!newi)
			{
				DEBUG(("nfs_readdir(%s): no index for entry, -> ENHNDL", ni->name));
				res = ENHNDL;
				goto prep_next_entry;
			}
			newi->flags |= NO_HANDLE;
			newi->stamp = get_timestamp()-ni->opt->actimeo-1;  /* no attr yet */
		}
		if (newi)
		{
			newi->link += 1;
		}
#ifdef USR_CACHE
		nfs_cache_add(ni, newi);
#endif

		fc->fs = &nfs_filesys;
		fc->dev = nfs_dev;
		fc->aux = 0;
		fc->index = (long)newi;

prep_next_entry:
		for (i = 0;  i < COOKIESIZE;  i++)
			stuff->lastcookie[i] = entp->cookie[i];
		stuff->curr_entry = entp->nextentry;
		DEBUG(("nfs_readdir(%s) -> %s", ni->name, name));
		return res;
	}
	if (stuff->eof)
	{
		TRACE(("nfs_readdir(%s): end of dir reached, -> ENMFIL", ni->name));
		return ENMFIL;
	}

	/* ask the server for another chunk of directory entries */
	TRACE(("nfs_readdir: requesting new chunk"));
	read_arg.dir = ni->handle;
	for (i = 0;  i < COOKIESIZE;  i++)
		read_arg.cookie[i] = stuff->lastcookie[i];
	read_arg.count = MAX_READDIR_LEN;    /* at most this much data per chunk */
	mreq = alloc_message(&m, req_buf, READDIRBUFSIZE,
	                               xdr_size_readdirargs(&read_arg));
	if (!mreq)
	{
		DEBUG(("nfs_readdir(%s): failed to alloc msg, -> ENMFIL", ni->name));
		return ENMFIL;
	}
	xdr_init(&x, mreq->data, mreq->data_len, XDR_ENCODE, NULL);
	xdr_readdirargs(&x, &read_arg);
	TRACE(("nfs_readdir: sending request"));

	r = rpc_request(&ni->opt->server, mreq, NFSPROC_READDIR, &mrep);
	if (r != 0)
	{
		DEBUG(("nfs_readdir(%s): couldnt contact server, -> ENMFIL", ni->name));
		return ENMFIL;
	}

	TRACE(("nfs_readdir: got answer"));
	bzero(stuff->buffer, MAX_READDIR_LEN);
	entp = read_res.readdirres_u.readdirok.entries = (entry*)stuff->buffer;

	/* make sure not to write over the end of the entry buffer, so decode
	 * only the length bounded by MAX_XDR_BUF.
	 */
	xdr_init(&x, mrep->data, min(MAX_XDR_BUF, mrep->data_len), XDR_DECODE, NULL);
	if (!xdr_readdirres(&x, &read_res))
	{
		DEBUG(("nfs_readdir(%s): could not decode results, -> ENMFIL", ni->name));
		free_message(mrep);
		return ENMFIL;
	}
	free_message(mrep);
	if (NFS_OK == read_res.status)
	{
		stuff->eof = read_res.readdirres_u.readdirok.eof;
		stuff->curr_entry = read_res.readdirres_u.readdirok.entries;
		goto restart;
	}
	TRACE(("nfs_readdir(%s) -> no more files", ni->name));
	return ENMFIL;
}
