/**
 * @file
 *
 * @ingroup libfs
 *
 * @brief NFS Client Implementation for RTEMS
 *
 * Hooks Into the RTEMS NFS Filesystem
 */

/*
 * Author: Till Straumann <strauman@slac.stanford.edu>, 2002
 *
 * Hacked on by others.
 *
 * Modifications to support reference counting in the file system are
 * Copyright (c) 2012 embedded brains GmbH.
 *
 * Authorship
 * ----------
 * This software (NFS-2 client implementation for RTEMS) was created by
 *     Till Straumann <strauman@slac.stanford.edu>, 2002-2007,
 * 	   Stanford Linear Accelerator Center, Stanford University.
 *
 * Acknowledgement of sponsorship
 * ------------------------------
 * The NFS-2 client implementation for RTEMS was produced by
 *     the Stanford Linear Accelerator Center, Stanford University,
 * 	   under Contract DE-AC03-76SFO0515 with the Department of Energy.
 *
 * Government disclaimer of liability
 * ----------------------------------
 * Neither the United States nor the United States Department of Energy,
 * nor any of their employees, makes any warranty, express or implied, or
 * assumes any legal liability or responsibility for the accuracy,
 * completeness, or usefulness of any data, apparatus, product, or process
 * disclosed, or represents that its use would not infringe privately owned
 * rights.
 *
 * Stanford disclaimer of liability
 * --------------------------------
 * Stanford University makes no representations or warranties, express or
 * implied, nor assumes any liability for the use of this software.
 *
 * Stanford disclaimer of copyright
 * --------------------------------
 * Stanford University, owner of the copyright, hereby disclaims its
 * copyright and all other rights in this software.  Hence, anyone may
 * freely use it for any purpose without restriction.
 *
 * Maintenance of notices
 * ----------------------
 * In the interest of clarity regarding the origin and status of this
 * SLAC software, this and all the preceding Stanford University notices
 * are to remain affixed to any copy or derivative of this software made
 * or distributed by the recipient and are to be affixed to any copy of
 * software made or distributed by the recipient that contains a copy or
 * derivative of this software.
 *
 * ------------------ SLAC Software Notices, Set 4 OTT.002a, 2004 FEB 03
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <rtems.h>
#include <rtems/libio.h>
#include <rtems/libio_.h>
#include <rtems/seterr.h>
#include <rtems/thread.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/stat.h>
#include <dirent.h>
#include <netdb.h>
#include <ctype.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "../proto/nfs_prot.h"
#include "../proto/mount_prot.h"

#include "rpcio.h"
#include "librtemsNfs.h"

/* Configurable parameters */

/* Estimated average length of a filename (including terminating 0).
 * This was calculated by doing
 *
 * 	find <some root> -print -exec basename '{}' \; > feil
 * 	wc feil
 *
 * AVG_NAMLEN = (num_chars + num_lines)/num_lines
 */
#define CONFIG_AVG_NAMLEN				10

#define CONFIG_NFS_SMALL_XACT_SIZE		800			/* size of RPC arguments for non-write ops */
/* lifetime of NFS attributes in a NfsNode;
 * the time is in seconds and the lifetime is
 * infinite if the symbol is #undef
 */
#define CONFIG_ATTR_LIFETIME			10/*secs*/

/*
 * The 'st_blksize' (stat(2)) value this nfs
 * client should report. If set to zero then the server's fattr data
 * is passed throught which is not necessary optimal.
 * Newlib's stdio uses 'st_blksize' (if built with HAVE_BLKSIZE defined)
 * to size the default buffer.
 * Due to the overhead of NFS it is probably better to use the maximum
 * size of an NFS read request (8k) rather than the optimal block
 * size on the server.
 * This value can be overridden at run-time by setting the global
 * variable 'nfsStBlksize'.
 * Thanks to Steven Johnson <sjohnson@sakuraindustries.com> for helping
 * working on this issue.
 */
#define DEFAULT_NFS_ST_BLKSIZE			NFS_MAXDATA

/* dont change this without changing the maximal write size */
#define CONFIG_NFS_BIG_XACT_SIZE		UDPMSGSIZE	/* dont change this */

/* The real values for these are specified further down */
#define NFSCALL_TIMEOUT					(&_nfscalltimeout)
#define MNTCALL_TIMEOUT					(&_nfscalltimeout)
static struct timeval _nfscalltimeout = { 10, 0 };	/* {secs, us } */

/* More or less fixed constants; in particular, NFS3 is not supported */
#define DELIM							'/'
#define HOSTDELIM						':'
#define UPDIR							".."
#define UIDSEP							'@'
#define NFS_VERSION_2					NFS_VERSION

/* we use a dynamically assigned major number */
#define NFS_MAJOR						(nfsGlob.nfs_major)


/* NOTE: RTEMS (ss-20020301) uses a 'short st_ino' type :-( but the
 * NFS fileid is 32 bit. [Later versions of RTEMS have fixed this;
 * nfsInit() issues a warning if you run a version with 'short st_ino'.]
 *
 * As a workarount, we merge the upper 16bits of the fileid into the
 * minor device no. Hence, it is still possible to uniquely identify
 * a file by looking at its device number (major = nfs, minor = part
 * of the fileid + our 'nfs-id' identifier).
 *
 * This has an impact on performance, as e.g. getcwd() stats() all
 * directory entries when it believes it has crossed a mount point
 * (a.st_dev != b.st_dev).
 *
 * OTOH, it also might cause node comparison failure! E.g. 'getcwd()'
 * assumes that two nodes residing in the same directory must be located
 * on the same device and hence compares 'st_ino' only.
 * If two files in the same directory have the same inode number
 * modulo 2^16, they will be considered equal (although their device
 * number doesn't match - getcwd doesn't look at it).
 *
 * Other software might or might not be affected.
 *
 * The only clean solution to this problem is bumping up the size of
 * 'ino_t' at least to 'long'.
 * Note that this requires _all_ software (libraries etc.) to be
 * recompiled.
 */

#define	NFS_MAKE_DEV_T_INO_HACK(node) \
		rtems_filesystem_make_dev_t( NFS_MAJOR, \
			(((rtems_device_minor_number)((node)->nfs->id))<<16) | (((rtems_device_minor_number)SERP_ATTR((node)).fileid) >> 16) )

/* use our 'nfs id' and the server's fsid for the minor device number
 * this should be fairly unique
 */
#define	NFS_MAKE_DEV_T(node) \
		rtems_filesystem_make_dev_t( NFS_MAJOR, \
			(((rtems_device_minor_number)((node)->nfs->id))<<16) | (SERP_ATTR((node)).fsid & (((rtems_device_minor_number)1<<16)-1)) )

#define  DIRENT_HEADER_SIZE ( sizeof(struct dirent) - \
			sizeof( ((struct dirent *)0)->d_name ) )


/* debugging flags */
#define DEBUG_COUNT_NODES	(1<<0)
#define DEBUG_TRACK_NODES	(1<<1)
#define DEBUG_EVALPATH		(1<<2)
#define DEBUG_READDIR		(1<<3)
#define DEBUG_SYSCALLS		(1<<4)

/* #define DEBUG	( DEBUG_SYSCALLS | DEBUG_COUNT_NODES ) */

#ifdef DEBUG
#define STATIC
#else
#define STATIC static
#endif

#define LOCK(s)		rtems_recursive_mutex_lock(&(s))

#define UNLOCK(s)	rtems_recursive_mutex_unlock(&(s))

RTEMS_INTERRUPT_LOCK_DEFINE(static, nfs_global_lock, "NFS")

#define NFS_GLOBAL_ACQUIRE(lock_context) \
    rtems_interrupt_lock_acquire(&nfs_global_lock, lock_context)

#define NFS_GLOBAL_RELEASE(lock_context) \
    rtems_interrupt_lock_release(&nfs_global_lock, lock_context)

static inline char *
nfs_dupname(const char *name, size_t namelen)
{
	char *dupname = malloc(namelen + 1);

	if (dupname != NULL) {
		memcpy(dupname, name, namelen);
		dupname [namelen] = '\0';
	} else {
		errno = ENOMEM;
	}

	return dupname;
}

/*****************************************
	Types with Associated XDR Routines
 *****************************************/

/* a string buffer with a maximal length.
 * If the buffer pointer is NULL, it is updated
 * with an appropriately allocated area.
 */
typedef struct strbuf {
	char	*buf;
	u_int	max;
} strbuf;

/* Read 'readlink' results into a 'strbuf'.
 * This is convenient as it avoids
 * one extra step of copying / length
 * checking.
 */
typedef struct readlinkres_strbuf {
	nfsstat	status;
	strbuf	strbuf;
} readlinkres_strbuf;

static bool_t
xdr_readlinkres_strbuf(XDR *xdrs, readlinkres_strbuf *objp)
{
	if ( !xdr_nfsstat(xdrs, &objp->status) )
		return FALSE;

	if ( NFS_OK == objp->status ) {
		if ( !xdr_string(xdrs, &objp->strbuf.buf, objp->strbuf.max) )
			return FALSE;
	}
	return TRUE;
}


/* DirInfoRec is used instead of dirresargs
 * to convert recursion into iteration. The
 * 'rpcgen'erated xdr_dirresargs ends up
 * doing nested calls when unpacking the
 * 'next' pointers.
 */

typedef struct DirInfoRec_ {
	readdirargs	readdirargs;
	/* clone of the 'readdirres' fields;
	 * the cookie is put into the readdirargs above
	 */
	nfsstat		status;
	char		*buf, *ptr;
	int			len;
	bool_t		eofreached;
} DirInfoRec, *DirInfo;

/* this deals with one entry / record */
static bool_t
xdr_dir_info_entry(XDR *xdrs, DirInfo di)
{
union	{
	char			nambuf[NFS_MAXNAMLEN+1];
	nfscookie		cookie;
}				dummy;
struct dirent	*pde = (struct dirent *)di->ptr;
u_int			fileid;
char			*name;
register int	nlen = 0,len,naligned = 0;
nfscookie		*pcookie;

	len = di->len;

	if ( !xdr_u_int(xdrs, &fileid) )
		return FALSE;

	/* we must pass the address of a char* */
	name = (len > NFS_MAXNAMLEN) ? pde->d_name : dummy.nambuf;

	if ( !xdr_filename(xdrs, &name) ) {
		return FALSE;
	}

	if (len >= 0) {
		nlen      = strlen(name);
		naligned  = nlen + 1 /* string delimiter */ + 3 /* alignment */;
		naligned &= ~3;
		len      -= naligned;
	}

	/* if the cookie goes into the DirInfo, we hope this doesn't fail
	 * - the caller ends up with an invalid readdirargs cookie otherwise...
	 */
	pcookie = (len >= 0) ? &di->readdirargs.cookie : &dummy.cookie;
	if ( !xdr_nfscookie(xdrs, pcookie) ) {
		return FALSE;
	}

	di->len = len;
	/* adjust the buffer pointer */
	if (len >= 0) {
		pde->d_ino    = fileid;
		pde->d_namlen = nlen;
		pde->d_off	  = di->ptr - di->buf;
#ifdef DT_UNKNOWN
		pde->d_type = DT_UNKNOWN;
#endif
		if (name == dummy.nambuf) {
			memcpy(pde->d_name, dummy.nambuf, nlen + 1);
		}
		pde->d_reclen = DIRENT_HEADER_SIZE + naligned;
		di->ptr      += pde->d_reclen;
	}

	return TRUE;
}

/* this routine loops over all entries */
static bool_t
xdr_dir_info(XDR *xdrs, DirInfo di)
{
DirInfo	dip;

	if ( !xdr_nfsstat(xdrs, &di->status) )
		return FALSE;

	if ( NFS_OK != di->status )
		return TRUE;

	dip = di;

	while (dip) {
		/* reserve space for the dirent 'header' - we assume it's word aligned! */
#ifdef DEBUG
		assert( DIRENT_HEADER_SIZE % 4 == 0 );
#endif
		dip->len -= DIRENT_HEADER_SIZE;

		/* we pass a 0 size - size is unused since
		 * we always pass a non-NULL pointer
		 */
		if ( !xdr_pointer(xdrs, (void*)&dip, 0 /* size */, (xdrproc_t)xdr_dir_info_entry) )
			return FALSE;
	}

	if ( ! xdr_bool(xdrs, &di->eofreached) )
		return FALSE;

	/* if everything fits into the XDR buffer but not the user's buffer,
	 * they must resume reading from where xdr_dir_info_entry() started
	 * skipping and 'eofreached' needs to be adjusted
	 */
	if ( di->len < 0 && di->eofreached )
		di->eofreached = FALSE;

	return TRUE;
}


/* a type better suited for node operations
 * than diropres.
 * fattr and fhs are swapped so parts of this
 * structure may be used as a diroparg which
 * is practical when looking up paths.
 */

/* Macro for accessing serporid fields
 */
#define SERP_ARGS(node) ((node)->serporid.serporid_u.serporid.arg_u)
#define SERP_ATTR(node) ((node)->serporid.serporid_u.serporid.attributes)
#define SERP_FILE(node) ((node)->serporid.serporid_u.serporid.file)


typedef struct serporidok {
	fattr					attributes;
	nfs_fh					file;
	union	{
		struct {
			filename	name;
		}					diroparg;
		struct {
			sattr		attributes;
		}					sattrarg;
		struct {
			uint32_t	offset;
			uint32_t	count;
			uint32_t	totalcount;
		}					readarg;
		struct {
			uint32_t	beginoffset;
			uint32_t	offset;
			uint32_t	totalcount;
			struct {
				uint32_t data_len;
				char* data_val;
			}			data;
		}					writearg;
		struct {
			filename	name;
			sattr		attributes;
		}					createarg;
		struct {
			filename	name;
			diropargs	to;
		}					renamearg;
		struct {
			diropargs	to;
		}					linkarg;
		struct {
			filename	name;
			nfspath		to;
			sattr		attributes;
		}					symlinkarg;
		struct {
			nfscookie	cookie;
			uint32_t	count;
		}					readdirarg;
	}							arg_u;
} serporidok;

typedef struct serporid {
	nfsstat			status;
	union	{
		serporidok	serporid;
	}				serporid_u;
} serporid;

/* an XDR routine to encode/decode the inverted diropres
 * into an nfsnodestat;
 *
 * NOTE: this routine only acts on
 *   - 'serporid.status'
 *   - 'serporid.file'
 *   - 'serporid.attributes'
 * and leaves the 'arg_u' alone.
 *
 * The idea is that a 'diropres' is read into 'serporid'
 * which can then be used as an argument to subsequent
 * NFS-RPCs (after filling in the node's arg_u).
 */
static bool_t
xdr_serporidok(XDR *xdrs, serporidok *objp)
{
     if (!xdr_nfs_fh (xdrs, &objp->file))
         return FALSE;
     if (!xdr_fattr (xdrs, &objp->attributes))
         return FALSE;
    return TRUE;
}

static bool_t
xdr_serporid(XDR *xdrs, serporid *objp)
{
     if (!xdr_nfsstat (xdrs, &objp->status))
         return FALSE;
    switch (objp->status) {
    case NFS_OK:
         if (!xdr_serporidok(xdrs, &objp->serporid_u.serporid))
             return FALSE;
        break;
    default:
        break;
    }
    return TRUE;
}

/*****************************************
	Data Structures and Types
 *****************************************/

/* 'time()' hack with less overhead; */

/* assume reading a long word is atomic */
#define READ_LONG_IS_ATOMIC

typedef uint32_t	TimeStamp;

static inline TimeStamp
nowSeconds(void)
{
  rtems_interval rval;
  rtems_clock_get_seconds_since_epoch( &rval );
  return rval;
}


/* Per mounted FS structure */
typedef struct NfsRec_ {
		/* the NFS server we're talking to.
		 */
	RpcUdpServer						 server;
		/* statistics; how many NfsNodes are
		 * currently alive.
		 */
	volatile int						 nodesInUse;
#if DEBUG & DEBUG_COUNT_NODES
		/* statistics; how many 'NfsNode.str'
		 * strings are currently allocated.
		 */
	volatile int						 stringsInUse;
#endif
		/* A small number who uniquely
		 * identifies a mounted NFS within
		 * this driver (i.e. this NfsRec).
		 * Each time a NFS is mounted, the
		 * global ID counter is incremented
		 * and its value is assigned to the
		 * newly created NfsRec.
		 */
	u_short								 id;
		/* Our RTEMS filesystem mt_entry
		 */
	rtems_filesystem_mount_table_entry_t *mt_entry;
		/* Next NfsRec on a linked list who
		 * is anchored at nfsGlob
		 */
	struct NfsRec_						 *next;
		/* Who we pretend we are
		 */
	u_long								 uid,gid;
} NfsRec, *Nfs;

typedef struct NfsNodeRec_ {
		/* This holds this node's attributes
		 * (stats) and its nfs filehandle.
		 * It also contains room for nfs rpc
		 * arguments.
		 */
	serporid	serporid;
		/* The arguments we used when doing
		 * the 'lookup' call for this node.
		 * We need this information (especially
		 * the directory FH) for performing
		 * certain operations on this
		 * node (in particular: for unlinking
		 * it from a parent directory)
		 */
	diropargs		args;
		/* FS this node belongs to
		 */
	Nfs				nfs;
		/* A buffer for the string the
		 * args.name points to.
		 * We need this because args.name might
		 * temporarily point to strings on the
		 * stack. Duplicates are allocated from
		 * the heap and attached to 'str' so
		 * they can be released as appropriate.
		 */
	char		   *str;
		/* A timestamp for the stats
		 */
	TimeStamp		age;
} NfsNodeRec, *NfsNode;

/*****************************************
	Forward Declarations
 *****************************************/

static ssize_t nfs_readlink_with_node(
	NfsNode node,
	char *buf,
	size_t len
);

static int updateAttr(NfsNode node, int force);

/* Mask bits when setting attributes.
 * Only the 'arg' fields with their
 * corresponding bit set in the mask
 * will be used. The others are left
 * unchanged.
 * The 'TOUCH' bits instruct nfs_sattr()
 * to update the respective time
 * fields to the current time
 */
#define	SATTR_MODE		(1<<0)
#define	SATTR_UID		(1<<1)
#define	SATTR_GID		(1<<2)
#define	SATTR_SIZE		(1<<3)
#define	SATTR_ATIME		(1<<4)
#define	SATTR_TOUCHA	(1<<5)
#define	SATTR_MTIME		(1<<6)
#define	SATTR_TOUCHM	(1<<7)
#define SATTR_TOUCH		(SATTR_TOUCHM | SATTR_TOUCHA)

static int
nfs_sattr(NfsNode node, sattr *arg, u_long mask);

extern const struct _rtems_filesystem_operations_table nfs_fs_ops;
static const struct _rtems_filesystem_file_handlers_r nfs_file_file_handlers;
static const struct _rtems_filesystem_file_handlers_r nfs_dir_file_handlers;
static const struct _rtems_filesystem_file_handlers_r nfs_link_file_handlers;
static		   rtems_driver_address_table		 drvNfs;

int
nfsMountsShow(FILE*);

rtems_status_code
rtems_filesystem_resolve_location(char *buf, int len, rtems_filesystem_location_info_t *loc);

/*****************************************
	Global Variables
 *****************************************/

/* These are (except for MAXNAMLEN/MAXPATHLEN) copied from IMFS */

static const rtems_filesystem_limits_and_options_t
nfs_limits_and_options = {
   5, 				/* link_max */
   6, 				/* max_canon */
   7, 				/* max_input */
   NFS_MAXNAMLEN,	/* name_max */
   NFS_MAXPATHLEN,	/* path_max */
   2,				/* pipe_buf */
   1,				/* posix_async_io */
   2,				/* posix_chown_restrictions */
   3,				/* posix_no_trunc */
   4,				/* posix_prio_io */
   5,				/* posix_sync_io */
   6				/* posix_vdisable */
};

/* size of an encoded 'entry' object */
static int dirres_entry_size;

/* Global stuff and statistics */
static struct nfsstats {
		/* A lock for protecting the
		 * linked ist of mounted NFS
		 * and the num_mounted_fs field
		 */
	rtems_recursive_mutex			llock;
		/* A lock for protecting misc
		 * stuff  within the driver.
		 * The lock must only be held
		 * for short periods of time.
		 */
	rtems_recursive_mutex			lock;
		/* Our major number as assigned
		 * by RTEMS
		 */
	rtems_device_major_number	nfs_major;
		/* The number of currently
		 * mounted NFS
		 */
	int							num_mounted_fs;
		/* A list of the currently
		 * mounted NFS
		 */
	struct NfsRec_				*mounted_fs;
		/* A counter for allocating
		 * unique IDs to each mounted
		 * NFS.
		 * Assume we are not going to
		 * do more than 16k mounts
		 * during the system lifetime
		 */
	u_short						fs_ids;

	/* Two pools of RPC transactions;
	 * One with small send buffers
	 * the other with a big one.
	 * The actual size of the small
	 * buffer is configurable (see top).
	 *
	 * Note: The RX buffers are always
	 * big
	 */
	RpcUdpXactPool smallPool;
	RpcUdpXactPool bigPool;
} nfsGlob = {RTEMS_RECURSIVE_MUTEX_INITIALIZER("NFS List"), RTEMS_RECURSIVE_MUTEX_INITIALIZER("NFS Misc"),  0xffffffff, 0, 0, 0, NULL, NULL};

/*
 * Global variable to tune the 'st_blksize' (stat(2)) value this nfs
 * client should report.
 * size on the server.
 */
#ifndef DEFAULT_NFS_ST_BLKSIZE
#define DEFAULT_NFS_ST_BLKSIZE	NFS_MAXDATA
#endif
int nfsStBlksize = DEFAULT_NFS_ST_BLKSIZE;


/*****************************************
	Implementation
 *****************************************/

static int nfsEvaluateStatus(nfsstat nfsStatus)
{
	static const uint8_t nfsStatusToErrno [71] = {
		[NFS_OK] = 0,
		[NFSERR_PERM] = EPERM,
		[NFSERR_NOENT] = ENOENT,
		[3] = EIO,
		[4] = EIO,
		[NFSERR_IO] = EIO,
		[NFSERR_NXIO] = ENXIO,
		[7] = EIO,
		[8] = EIO,
		[9] = EIO,
		[10] = EIO,
		[11] = EIO,
		[12] = EIO,
		[NFSERR_ACCES] = EACCES,
		[14] = EIO,
		[15] = EIO,
		[16] = EIO,
		[NFSERR_EXIST] = EEXIST,
		[18] = EIO,
		[NFSERR_NODEV] = ENODEV,
		[NFSERR_NOTDIR] = ENOTDIR,
		[NFSERR_ISDIR] = EISDIR,
		[22] = EIO,
		[24] = EIO,
		[25] = EIO,
		[26] = EIO,
		[27] = EIO,
		[NFSERR_FBIG] = EFBIG,
		[NFSERR_NOSPC] = ENOSPC,
		[29] = EIO,
		[NFSERR_ROFS] = EROFS,
		[31] = EIO,
		[32] = EIO,
		[34] = EIO,
		[35] = EIO,
		[36] = EIO,
		[37] = EIO,
		[38] = EIO,
		[39] = EIO,
		[40] = EIO,
		[41] = EIO,
		[42] = EIO,
		[44] = EIO,
		[45] = EIO,
		[46] = EIO,
		[47] = EIO,
		[48] = EIO,
		[49] = EIO,
		[50] = EIO,
		[51] = EIO,
		[52] = EIO,
		[54] = EIO,
		[55] = EIO,
		[56] = EIO,
		[57] = EIO,
		[58] = EIO,
		[59] = EIO,
		[60] = EIO,
		[61] = EIO,
		[62] = EIO,
		[NFSERR_NAMETOOLONG] = ENAMETOOLONG,
		[64] = EIO,
		[65] = EIO,
		[NFSERR_NOTEMPTY] = ENOTEMPTY,
		[67] = EIO,
		[68] = EIO,
		[NFSERR_DQUOT] = EDQUOT,
		[NFSERR_STALE] = ESTALE
	};

	size_t idx = (size_t) nfsStatus;
	int eno = EIO;
	int rv = 0;

	if (idx < sizeof(nfsStatusToErrno) / sizeof(nfsStatusToErrno [0])) {
		eno = nfsStatusToErrno [idx];
	}

	if (eno != 0) {
		errno = eno;
		rv = -1;
	}

	return rv;
}

/* Create a Nfs object. This is
 * per-mounted NFS information.
 *
 * ARGS:	The Nfs server handle.
 *
 * RETURNS:	Nfs on success,
 * 			NULL on failure with
 * 			errno set
 *
 * NOTE:	The submitted server
 * 			object is 'owned' by
 * 			this Nfs and will be
 * 			destroyed by nfsDestroy()
 */
static Nfs
nfsCreate(RpcUdpServer server)
{
Nfs rval = calloc(1,sizeof(*rval));

	if (rval) {
		rval->server     = server;
		LOCK(nfsGlob.llock);
			rval->next 		   = nfsGlob.mounted_fs;
			nfsGlob.mounted_fs = rval;
		UNLOCK(nfsGlob.llock);
	} else {
		errno = ENOMEM;
	}
		return rval;
}

/* Destroy an Nfs object and
 * its associated server
 */
static void
nfsDestroy(Nfs nfs)
{
register Nfs prev;
	if (!nfs)
		return;

	LOCK(nfsGlob.llock);
		if (nfs == nfsGlob.mounted_fs)
			nfsGlob.mounted_fs = nfs->next;
		else {
			for (prev = nfsGlob.mounted_fs;
				 prev && prev->next != nfs;
				 prev = prev->next)
					/* nothing else to do */;
			assert( prev );
			prev->next = nfs->next;
		}
	UNLOCK(nfsGlob.llock);

	nfs->next = 0; /* paranoia */
	rpcUdpServerDestroy(nfs->server);
	free(nfs);
}

/*
 * Create a Node. The node will
 * be associated with a particular
 * mounted NFS identified by 'nfs'
 * Optionally, a NFS file handle
 * may be copied into this node.
 *
 * ARGS:	nfs of the NFS this node
 * 			belongs to.
 * 			NFS file handle identifying
 * 			this node.
 * RETURNS:	node on success,
 * 			NULL on failure with errno
 * 			set.
 *
 * NOTE:	The caller of this routine
 * 			is responsible for copying
 * 			a NFS file handle if she
 * 			choses to pass a NULL fh.
 *
 * 			The driver code assumes the
 * 			a node always has a valid
 * 			NFS filehandle and file
 * 			attributes (unless the latter
 * 			are aged).
 */
static NfsNode
nfsNodeCreate(Nfs nfs, fhandle *fh)
{
NfsNode	rval = malloc(sizeof(*rval));
rtems_interrupt_lock_context lock_context;

#if DEBUG & DEBUG_TRACK_NODES
	fprintf(stderr,"NFS: creating a node\n");
#endif

	if (rval) {
		if (fh)
			memcpy( &SERP_FILE(rval), fh, sizeof(*fh) );
		NFS_GLOBAL_ACQUIRE(&lock_context);
			nfs->nodesInUse++;
		NFS_GLOBAL_RELEASE(&lock_context);
		rval->nfs       = nfs;
		rval->str		= 0;
	} else {
		errno = ENOMEM;
	}

	return rval;
}

/* destroy a node */
static void
nfsNodeDestroy(NfsNode node)
{
rtems_interrupt_lock_context lock_context;

#if DEBUG & DEBUG_TRACK_NODES
	fprintf(stderr,"NFS: destroying a node\n");
#endif
#if 0
	if (!node)
		return;
	/* this probably does nothing... */
  	xdr_free(xdr_serporid, &node->serporid);
#endif

	NFS_GLOBAL_ACQUIRE(&lock_context);
		node->nfs->nodesInUse--;
#if DEBUG & DEBUG_COUNT_NODES
		if (node->str)
			node->nfs->stringsInUse--;
#endif
	NFS_GLOBAL_RELEASE(&lock_context);

	if (node->str)
		free(node->str);

	free(node);
}

/* Clone a given node (AKA copy constructor),
 * i.e. create an exact copy.
 *
 * ARGS:	node to clone
 * RETURNS:	new node on success
 * 			NULL on failure with errno set.
 *
 * NOTE:	a string attached to 'str'
 * 			is cloned as well. Outdated
 * 			attributes (of the new copy
 * 			only) will be refreshed
 * 			(if unsuccessful, this could
 * 			be a reason for failure to
 * 			clone a node).
 */
static NfsNode
nfsNodeClone(NfsNode node)
{
NfsNode rval = nfsNodeCreate(node->nfs, 0);

	if (rval) {
		*rval = *node;

		/* must clone the string also */
		if (node->str) {
			rval->args.name = rval->str = strdup(node->str);
			if (!rval->str) {
				errno = ENOMEM;
				nfsNodeDestroy(rval);
				return 0;
			}
#if DEBUG & DEBUG_COUNT_NODES
			{ rtems_interrupt_lock_context lock_context;
			NFS_GLOBAL_ACQUIRE(&lock_context);
				node->nfs->stringsInUse++;
			NFS_GLOBAL_RELEASE(&lock_context);
			}
#endif
		}

		/* possibly update the stats */
		if (updateAttr(rval, 0 /* only if necessary */)) {
			nfsNodeDestroy(rval);
			return 0;
		}
	}
	return rval;
}

/* Initialize the driver.
 *
 * ARGS:	depth of the small and big
 * 			transaction pools, i.e. how
 * 			many transactions (buffers)
 * 			should always be kept around.
 *
 * 			(If more transactions are needed,
 * 			they are created and destroyed
 * 			on the fly).
 */
int
nfsInit(int smallPoolDepth, int bigPoolDepth)
{
static int initialised = 0;
entry	dummy;

	if (initialised)
		return 0;

	initialised = 1;

	fprintf(stderr,
          "RTEMS-NFS, " \
          "Till Straumann, Stanford/SLAC/SSRL 2002, " \
          "See LICENSE file for licensing info.\n");

	/* Get a major number */

	if (RTEMS_SUCCESSFUL != rtems_io_register_driver(0, &drvNfs, &nfsGlob.nfs_major)) {
		fprintf(stderr,"Registering NFS driver failed - %s\n", strerror(errno));
		errno = ENOMEM;
		return -1;
	}

	if (0==smallPoolDepth)
		smallPoolDepth = 20;
	if (0==bigPoolDepth)
		bigPoolDepth   = 10;

	rtems_recursive_mutex_init(&nfsGlob.llock, "NFSl");
	rtems_recursive_mutex_init(&nfsGlob.lock, "NFSm");

	/* it's crucial to zero out the 'next' pointer
	 * because it terminates the xdr_entry recursion
	 *
	 * we also must make the filename some non-zero
	 * char pointer!
	 */

	memset(&dummy, 0, sizeof(dummy));

	dummy.nextentry   = 0;
	dummy.name        = "somename"; /* guess average length of a filename */
	dirres_entry_size = xdr_sizeof((xdrproc_t)xdr_entry, &dummy);

	nfsGlob.smallPool = rpcUdpXactPoolCreate(
		NFS_PROGRAM,
		NFS_VERSION_2,
		CONFIG_NFS_SMALL_XACT_SIZE,
		smallPoolDepth);
	if (nfsGlob.smallPool == NULL) {
		goto cleanup;
	}

	nfsGlob.bigPool = rpcUdpXactPoolCreate(
		NFS_PROGRAM,
		NFS_VERSION_2,
		CONFIG_NFS_BIG_XACT_SIZE,
		bigPoolDepth);
	if (nfsGlob.bigPool == NULL) {
		goto cleanup;
	}

	if (sizeof(ino_t) < sizeof(u_int)) {
		fprintf(stderr,
			"WARNING: Using 'short st_ino' hits performance and may fail to access/find correct files\n");
		fprintf(stderr,
			"you should fix newlib's sys/stat.h - for now I'll enable a hack...\n");

	}

	return 0;

cleanup:

	nfsCleanup();
	initialised = 0;

	return -1;
}

/* Driver cleanup code
 */
int
nfsCleanup(void)
{
int			refuse;

	LOCK(nfsGlob.llock);
	if ( (refuse = nfsGlob.num_mounted_fs) ) {
		fprintf(stderr,"Refuse to unload NFS; %i filesystems still mounted.\n",
						refuse);
		nfsMountsShow(stderr);
		/* yes, printing is slow - but since you try to unload the driver,
		 * you assume nobody is using NFS, so what if they have to wait?
		 */
		UNLOCK(nfsGlob.llock);
		return -1;
	}

	if (nfsGlob.smallPool != NULL) {
		rpcUdpXactPoolDestroy(nfsGlob.smallPool);
		nfsGlob.smallPool = NULL;
	}

	if (nfsGlob.bigPool != NULL) {
		rpcUdpXactPoolDestroy(nfsGlob.bigPool);
		nfsGlob.bigPool = NULL;
	}

	if (nfsGlob.nfs_major != 0xffffffff) {
		rtems_io_unregister_driver(nfsGlob.nfs_major);
		nfsGlob.nfs_major = 0xffffffff;
	}

	UNLOCK(nfsGlob.llock);

	rtems_recursive_mutex_destroy(&nfsGlob.lock);
	rtems_recursive_mutex_destroy(&nfsGlob.llock);

	return 0;
}

/* NFS RPC wrapper.
 *
 * ARGS:	srvr	the NFS server we want to call
 * 			proc	the NFSPROC_xx we want to invoke
 * 			xargs   xdr routine to wrap the arguments
 * 			pargs   pointer to the argument object
 * 			xres	xdr routine to unwrap the results
 * 			pres	pointer to the result object
 *
 * RETURNS:	0 on success, -1 on error with errno set.
 *
 * NOTE:	the caller assumes that errno is set to
 *			a nonzero value if this routine returns
 *			an error (nonzero return value).
 *
 *			This routine prints RPC error messages to
 *			stderr.
 */
STATIC int
nfscall(
	RpcUdpServer	srvr,
	int				proc,
	xdrproc_t		xargs,
	void *			pargs,
	xdrproc_t		xres,
	void *			pres)
{
RpcUdpXact		xact;
enum clnt_stat	stat;
RpcUdpXactPool	pool;
int				rval = -1;


	switch (proc) {
		case NFSPROC_SYMLINK:
		case NFSPROC_WRITE:
					pool = nfsGlob.bigPool;		break;
		default:	pool = nfsGlob.smallPool;	break;
	}

	xact = rpcUdpXactPoolGet(pool, XactGetCreate);

	if ( !xact ) {
		errno = ENOMEM;
		return -1;
	}

	if ( RPC_SUCCESS != (stat=rpcUdpSend(
								xact,
								srvr,
								NFSCALL_TIMEOUT,
								proc,
								xres,
								pres,
								xargs,
								pargs,
								0)) ||
	     RPC_SUCCESS != (stat=rpcUdpRcv(xact)) ) {

		fprintf(stderr,
				"NFS (proc %i) - %s\n",
				proc,
				clnt_sperrno(stat));

		switch (stat) {
			/* TODO: this is probably not complete and/or fully accurate */
			case RPC_CANTENCODEARGS : errno = EINVAL;	break;
			case RPC_AUTHERROR  	: errno = EPERM;	break;

			case RPC_CANTSEND		:
			case RPC_CANTRECV		: /* hope they have errno set */
			case RPC_SYSTEMERROR	: break;

			default             	: errno = EIO;		break;
		}
	} else {
		rval = 0;
	}

	/* release the transaction back into the pool */
	rpcUdpXactPoolPut(xact);

	if (rval && !errno)
		errno = EIO;

	return rval;
}

/* Check the 'age' of a node's stats
 * and read the attributes from the server
 * if necessary.
 *
 * ARGS:	node	node to update
 * 			force	enforce updating ignoring
 * 					the timestamp/age
 *
 * RETURNS:	0 on success,
 * 			-1 on failure with errno set
 */

static int
updateAttr(NfsNode node, int force)
{
	int rv = 0;

	if (force
#ifdef CONFIG_ATTR_LIFETIME
		|| (nowSeconds() - node->age > CONFIG_ATTR_LIFETIME)
#endif
	) {
		rv = nfscall(
			node->nfs->server,
			NFSPROC_GETATTR,
			(xdrproc_t) xdr_nfs_fh, &SERP_FILE(node),
			(xdrproc_t) xdr_attrstat, &node->serporid
		);

		if (rv == 0) {
			rv = nfsEvaluateStatus(node->serporid.status);

			if (rv == 0) {
				node->age = nowSeconds();
			}
		}
	}

	return rv;
}

/*
 * IP address helper.
 *
 * initialize a sockaddr_in from a
 * [<uid>'.'<gid>'@']<host>':'<path>" string and let
 * pPath point to the <path> part; retrieve the optional
 * uid/gids
 *
 * ARGS:	see description above
 *
 * RETURNS:	0 on success,
 * 			-1 on failure with errno set
 */
static int
buildIpAddr(u_long *puid, u_long *pgid,
			char **pHost, struct sockaddr_in *psa,
			char **pPath)
{
struct hostent *h;
char	host[64];
char	*chpt = *pPath;
char	*path;
int		len;

	if ( !chpt ) {
		errno = EINVAL;
		return -1;
	}

	/* look for the optional uid/gid */
	if ( (chpt = strchr(chpt, UIDSEP)) ) {
		if ( 2 != sscanf(*pPath,"%li.%li",puid,pgid) ) {
			errno = EINVAL;
			return -1;
		}
		chpt++;
	} else {
		*puid = geteuid();
		*pgid = getegid();
		chpt  = *pPath;
	}
	if ( pHost )
		*pHost = chpt;

	/* split the device name which is in the form
	 *
	 * <host> ':' <path>
	 *
	 * into its components using a local buffer
	 */

	if ( !(path = strchr(chpt, HOSTDELIM)) ||
	      (len  = path - chpt) >= sizeof(host) - 1 ) {
		errno = EINVAL;
		return -1;
	}
	/* point to path beyond ':' */
	path++;

	strncpy(host, chpt, len);
	host[len]=0;

  /* BEGIN OF NON-THREAD SAFE REGION */

	h = gethostbyname(host);

	if ( !h ) {
		errno = EINVAL;
		return -1;
	}

	memcpy(&psa->sin_addr, h->h_addr, sizeof (struct in_addr));

  /* END OF NON-THREAD SAFE REGION */

	psa->sin_family = AF_INET;
	psa->sin_port   = 0;
	*pPath          = path;
	return 0;
}

/* wrapper similar to nfscall.
 * However, since it is not used
 * very often, the simpler and less
 * efficient rpcUdpCallRp API is used.
 *
 * ARGS:	see 'nfscall()' above
 *
 * RETURNS:	RPC status
 */
static enum clnt_stat
mntcall(
	struct sockaddr_in	*psrvr,
	int					proc,
	xdrproc_t			xargs,
	void *				pargs,
	xdrproc_t			xres,
	void *				pres,
	u_long				uid,
	u_long				gid)
{
#ifdef MOUNT_V1_PORT
int					retry;
#endif
enum clnt_stat		stat = RPC_FAILED;

#ifdef MOUNT_V1_PORT
	/* if the portmapper fails, retry a fixed port */
	for (retry = 1, psrvr->sin_port = 0, stat = RPC_FAILED;
		 retry >= 0 && stat;
		 stat && (psrvr->sin_port = htons(MOUNT_V1_PORT)), retry-- )
#endif
		stat  = rpcUdpCallRp(
						psrvr,
						MOUNTPROG,
						MOUNTVERS,
						proc,
						xargs,
						pargs,
						xres,
						pres,
						uid,
						gid,
						MNTCALL_TIMEOUT
				);
	return stat;
}

/*****************************************
	RTEMS File System Operations for NFS
 *****************************************/

static bool nfs_is_directory(
	rtems_filesystem_eval_path_context_t *ctx,
	void *arg
)
{
	bool is_dir = false;
	rtems_filesystem_location_info_t *currentloc =
		rtems_filesystem_eval_path_get_currentloc(ctx);
	NfsNode node = currentloc->node_access;
	int force_update = 0;

	if (updateAttr(node, force_update) == 0) {
		is_dir = SERP_ATTR(node).type == NFDIR;
	}

	return is_dir;
}

static int nfs_search_in_directory(
	Nfs nfs,
	const NfsNode dir,
	char *part,
	NfsNode entry
)
{
	int rv;

	entry->nfs = nfs;

	/* lookup one element */
	SERP_ATTR(entry) = SERP_ATTR(dir);
	SERP_FILE(entry) = SERP_FILE(dir);
	SERP_ARGS(entry).diroparg.name = part;

	/* remember args / directory fh */
	memcpy(&entry->args, &SERP_FILE(dir), sizeof(dir->args));

#if DEBUG & DEBUG_EVALPATH
	fprintf(stderr,"Looking up '%s'\n",part);
#endif

	rv = nfscall(
		nfs->server,
		NFSPROC_LOOKUP,
		(xdrproc_t) xdr_diropargs, &SERP_FILE(entry),
		(xdrproc_t) xdr_serporid,  &entry->serporid
	);

	if (rv == 0 && entry->serporid.status == NFS_OK) {
		int force_update = 1;

		rv = updateAttr(entry, force_update);
	} else {
		rv = -1;
	}

	return rv;
}

static void nfs_eval_follow_link(
	rtems_filesystem_eval_path_context_t *ctx,
	NfsNode link
)
{
	const size_t len = NFS_MAXPATHLEN + 1;
	char *buf = malloc(len);

	if (buf != NULL) {
		ssize_t rv = nfs_readlink_with_node(link, buf, len);

		if (rv >= 0) {
			rtems_filesystem_eval_path_recursive(ctx, buf, (size_t) rv);
		} else {
			rtems_filesystem_eval_path_error(ctx, 0);
		}

		free(buf);
	} else {
		rtems_filesystem_eval_path_error(ctx, ENOMEM);
	}
}

static void nfs_eval_set_handlers(
	rtems_filesystem_eval_path_context_t *ctx,
	ftype type
)
{
	rtems_filesystem_location_info_t *currentloc =
		rtems_filesystem_eval_path_get_currentloc(ctx);

	switch (type) {
		case NFDIR:
			currentloc->handlers = &nfs_dir_file_handlers;
			break;
		case NFREG:
			currentloc->handlers = &nfs_file_file_handlers;
			break;
		case NFLNK:
			currentloc->handlers = &nfs_link_file_handlers;
			break;
		default:
			currentloc->handlers = &rtems_filesystem_handlers_default;
			break;
	}
}

static int nfs_move_node(NfsNode dst, const NfsNode src, const char *part)
{
	int rv = 0;

	if (dst->str != NULL) {
#if DEBUG & DEBUG_COUNT_NODES
		rtems_interrupt_lock_context lock_context;
		NFS_GLOBAL_ACQUIRE(&lock_context);
			dst->nfs->stringsInUse--;
		NFS_GLOBAL_RELEASE(&lock_context);
#endif
		free(dst->str);
	}

	*dst = *src;

	dst->str = dst->args.name = strdup(part);
	if (dst->str != NULL) {
#if DEBUG & DEBUG_COUNT_NODES
		rtems_interrupt_lock_context lock_context;
		NFS_GLOBAL_ACQUIRE(&lock_context);
			dst->nfs->stringsInUse++;
		NFS_GLOBAL_RELEASE(&lock_context);
#endif
	} else {
		rv = -1;
	}

	return rv;
}

static rtems_filesystem_eval_path_generic_status nfs_eval_part(
	rtems_filesystem_eval_path_context_t *ctx,
	char *part
)
{
	rtems_filesystem_eval_path_generic_status status =
		RTEMS_FILESYSTEM_EVAL_PATH_GENERIC_DONE;
	rtems_filesystem_location_info_t *currentloc =
		rtems_filesystem_eval_path_get_currentloc(ctx);
	Nfs nfs = currentloc->mt_entry->fs_info;
	NfsNode dir = currentloc->node_access;
	NfsNodeRec entry;
	int rv = nfs_search_in_directory(nfs, dir, part, &entry);

	if (rv == 0) {
		bool terminal = !rtems_filesystem_eval_path_has_path(ctx);
		int eval_flags = rtems_filesystem_eval_path_get_flags(ctx);
		bool follow_sym_link = (eval_flags & RTEMS_FS_FOLLOW_SYM_LINK) != 0;
		ftype type = SERP_ATTR(&entry).type;

		rtems_filesystem_eval_path_clear_token(ctx);

		if (type == NFLNK && (follow_sym_link || !terminal)) {
			nfs_eval_follow_link(ctx, &entry);
		} else {
			rv = nfs_move_node(dir, &entry, part);
			if (rv == 0) {
				nfs_eval_set_handlers(ctx, type);
				if (!terminal) {
					status = RTEMS_FILESYSTEM_EVAL_PATH_GENERIC_CONTINUE;
				}
			} else {
				rtems_filesystem_eval_path_error(ctx, ENOMEM);
			}
		}
	} else {
		status = RTEMS_FILESYSTEM_EVAL_PATH_GENERIC_NO_ENTRY;
	}

	return status;
}

static rtems_filesystem_eval_path_generic_status nfs_eval_token(
	rtems_filesystem_eval_path_context_t *ctx,
	void *arg,
	const char *token,
	size_t tokenlen
)
{
	rtems_filesystem_eval_path_generic_status status =
		RTEMS_FILESYSTEM_EVAL_PATH_GENERIC_DONE;

	if (rtems_filesystem_is_current_directory(token, tokenlen)) {
		rtems_filesystem_eval_path_clear_token(ctx);
		if (rtems_filesystem_eval_path_has_path(ctx)) {
			status = RTEMS_FILESYSTEM_EVAL_PATH_GENERIC_CONTINUE;
		}
	} else {
		char *part = nfs_dupname(token, tokenlen);

		if (part != NULL) {
			status = nfs_eval_part(ctx, part);
			free(part);
		} else {
			rtems_filesystem_eval_path_error(ctx, ENOMEM);
		}
	}

	return status;
}

static const rtems_filesystem_eval_path_generic_config nfs_eval_config = {
	.is_directory = nfs_is_directory,
	.eval_token = nfs_eval_token
};

static void nfs_eval_path(rtems_filesystem_eval_path_context_t *ctx)
{
	rtems_filesystem_eval_path_generic(ctx, NULL, &nfs_eval_config);
}

/* create a hard link */

static int nfs_link(
	const rtems_filesystem_location_info_t *parentloc,
	const rtems_filesystem_location_info_t *targetloc,
	const char *name,
	size_t namelen
)
{
int rv = 0;
NfsNode pNode = parentloc->node_access;
nfsstat status;
NfsNode tNode = targetloc->node_access;
char *dupname;

	dupname = nfs_dupname(name, namelen);
	if (dupname == NULL)
		return -1;

#if DEBUG & DEBUG_SYSCALLS
	fprintf(stderr,"Creating link '%s'\n",dupname);
#endif

	memcpy(&SERP_ARGS(tNode).linkarg.to.dir,
		   &SERP_FILE(pNode),
		   sizeof(SERP_FILE(pNode)));

	SERP_ARGS(tNode).linkarg.to.name = dupname;

	rv = nfscall(
		tNode->nfs->server,
		NFSPROC_LINK,
		(xdrproc_t)xdr_linkargs, &SERP_FILE(tNode),
		(xdrproc_t)xdr_nfsstat, &status
	);

	if (rv == 0) {
		rv = nfsEvaluateStatus(status);
#if DEBUG & DEBUG_SYSCALLS
		if (rv != 0) {
			perror("nfs_link");
		}
#endif
	}

	free(dupname);

	return rv;

}

static int nfs_do_unlink(
	const rtems_filesystem_location_info_t *parentloc,
	const rtems_filesystem_location_info_t *loc,
	int								  proc
)
{
int rv = 0;
nfsstat			status;
NfsNode			node  = loc->node_access;
Nfs				nfs   = node->nfs;
#if DEBUG & DEBUG_SYSCALLS
char			*name = NFSPROC_REMOVE == proc ?
							"nfs_unlink" : "nfs_rmdir";
#endif

	/* The FS generics have determined that pathloc is _not_
	 * a directory. Hence we may assume that the parent
	 * is in our NFS.
	 */

#if DEBUG & DEBUG_SYSCALLS
	assert( node->args.name == node->str && node->str );

	fprintf(stderr,"%s '%s'\n", name, node->args.name);
#endif

	rv = nfscall(
		nfs->server,
		proc,
		(xdrproc_t)xdr_diropargs, &node->args,
		(xdrproc_t)xdr_nfsstat, &status
	);

	if (rv == 0) {
		rv = nfsEvaluateStatus(status);
#if DEBUG & DEBUG_SYSCALLS
		if (rv != 0) {
			perror(name);
		}
#endif
	}

	return rv;
}

static int nfs_chown(
	const rtems_filesystem_location_info_t  *pathloc,       /* IN */
	uid_t                                    owner,         /* IN */
	gid_t                                    group          /* IN */
)
{
sattr	arg;

	arg.uid = owner;
	arg.gid = group;

	return nfs_sattr(pathloc->node_access, &arg, SATTR_UID | SATTR_GID);

}

static int nfs_clonenode(rtems_filesystem_location_info_t *loc)
{
	NfsNode	node = loc->node_access;

	LOCK(nfsGlob.lock);
	node = nfsNodeClone(node);
	UNLOCK(nfsGlob.lock);

	loc->node_access = node;

	return node != NULL ? 0 : -1;
}

/* Cleanup the FS private info attached to pathloc->node_access */
static void nfs_freenode(
	const rtems_filesystem_location_info_t      *pathloc       /* IN */
)
{
#if DEBUG & DEBUG_COUNT_NODES
Nfs	nfs    = ((NfsNode)pathloc->node_access)->nfs;

	/* print counts at entry where they are > 0 so 'nfs' is safe from being destroyed
	 * and there's no race condition
	 */
	fprintf(stderr,
			"entering freenode, in use count is %i nodes, %i strings\n",
			nfs->nodesInUse,
			nfs->stringsInUse);
#endif

	nfsNodeDestroy(pathloc->node_access);
}

/* NOTE/TODO: mounting on top of NFS is not currently supported
 *
 * Challenge: stateless protocol. It would be possible to
 * delete mount points on the server. We would need some sort
 * of a 'garbage collector' looking for dead/unreachable
 * mount points and unmounting them.
 * Also, the path evaluation routine would have to check
 * for crossing mount points. Crossing over from one NFS
 * into another NFS could probably handled iteratively
 * rather than by recursion.
 */

int rtems_nfs_initialize(
  rtems_filesystem_mount_table_entry_t *mt_entry,
  const void                           *data
)
{
char				*host;
struct sockaddr_in	saddr;
enum clnt_stat		stat;
fhstatus			fhstat;
u_long				uid,gid;
#ifdef NFS_V2_PORT
int					retry;
#endif
Nfs					nfs       = 0;
NfsNode				rootNode  = 0;
RpcUdpServer		nfsServer = 0;
int					e         = -1;
char				*path     = mt_entry->dev;

  if (rpcUdpInit () < 0) {
    fprintf (stderr, "error: initialising RPC\n");
    return -1;
  }

	if (nfsInit(0, 0) != 0) {
		fprintf (stderr, "error: initialising NFS\n");
		return -1;
	};

#if 0
	printf("Trying to mount %s on %s\n",path,mntpoint);
#endif

	if ( buildIpAddr(&uid, &gid, &host, &saddr, &path) )
		return -1;

#ifdef NFS_V2_PORT
	/* if the portmapper fails, retry a fixed port */
	for (retry = 1, saddr.sin_port = 0, stat = RPC_FAILED;
		 retry >= 0 && stat;
		 stat && (saddr.sin_port = htons(NFS_V2_PORT)), retry-- )
#endif
		stat = rpcUdpServerCreate(
					&saddr,
					NFS_PROGRAM,
					NFS_VERSION_2,
					uid,
					gid,
					&nfsServer
					);

	if ( RPC_SUCCESS != stat ) {
		fprintf(stderr,
				"Unable to contact NFS server - invalid port? (%s)\n",
				clnt_sperrno(stat));
		e = EPROTONOSUPPORT;
		goto cleanup;
	}


	/* first, try to ping the NFS server by
	 * calling the NULL proc.
	 */
	if ( nfscall(nfsServer,
					 NFSPROC_NULL,
					 (xdrproc_t)xdr_void, 0,
					 (xdrproc_t)xdr_void, 0) ) {

		fputs("NFS Ping ",stderr);
		fwrite(host, 1, path-host-1, stderr);
		fprintf(stderr," failed: %s\n", strerror(errno));

		e = errno ? errno : EIO;
		goto cleanup;
	}

	/* that seemed to work - we now try the
	 * actual mount
	 */

	/* reuse server address but let the mntcall()
	 * search for the mountd's port
	 */
	saddr.sin_port = 0;

	stat = mntcall( &saddr,
					MOUNTPROC_MNT,
					(xdrproc_t)xdr_dirpath,
					&path,
					(xdrproc_t)xdr_fhstatus,
					&fhstat,
				 	uid,
				 	gid );

	if (stat) {
		fprintf(stderr,"MOUNT -- %s\n",clnt_sperrno(stat));
		if ( e<=0 )
			e = EIO;
		goto cleanup;
	} else if (NFS_OK != (e=fhstat.fhs_status)) {
		fprintf(stderr,"MOUNT: %s\n",strerror(e));
		goto cleanup;
	}

	nfs = nfsCreate(nfsServer);
	assert( nfs );
	nfsServer = 0;

	nfs->uid  = uid;
	nfs->gid  = gid;

	/* that seemed to work - we now create the root node
	 * and we also must obtain the root node attributes
	 */
	rootNode = nfsNodeCreate(nfs, &fhstat.fhstatus_u.fhs_fhandle);
	assert( rootNode );

	if ( updateAttr(rootNode, 1 /* force */) ) {
		e = errno;
		goto cleanup;
	}

	/* looks good so far */

	mt_entry->mt_fs_root->location.node_access = rootNode;

	rootNode = 0;

	mt_entry->ops = &nfs_fs_ops;
	mt_entry->mt_fs_root->location.handlers	 = &nfs_dir_file_handlers;
	mt_entry->pathconf_limits_and_options = &nfs_limits_and_options;

	LOCK(nfsGlob.llock);
		nfsGlob.num_mounted_fs++;
		/* allocate a new ID for this FS */
		nfs->id = nfsGlob.fs_ids++;
	UNLOCK(nfsGlob.llock);

	mt_entry->fs_info				 = nfs;
	nfs->mt_entry					 = mt_entry;
	nfs = 0;

	e = 0;

cleanup:
	if (nfs)
		nfsDestroy(nfs);
	if (nfsServer)
		rpcUdpServerDestroy(nfsServer);
	if (rootNode)
		nfsNodeDestroy(rootNode);
	if (e)
		rtems_set_errno_and_return_minus_one(e);
	else
		return 0;
}

/* This op is called when they try to unmount THIS fs */
STATIC void nfs_fsunmount_me(
	rtems_filesystem_mount_table_entry_t *mt_entry    /* in */
)
{
enum clnt_stat		stat;
struct sockaddr_in	saddr;
char			*path = mt_entry->dev;
int			nodesInUse;
u_long			uid,gid;
int			status;

LOCK(nfsGlob.llock);
	nodesInUse = ((Nfs)mt_entry->fs_info)->nodesInUse;

	if (nodesInUse > 1 /* one ref to the root node used by us */) {
		UNLOCK(nfsGlob.llock);
		fprintf(stderr,
				"Refuse to unmount; there are still %i nodes in use (1 used by us)\n",
				nodesInUse);
                rtems_fatal_error_occurred(0xdeadbeef);
                return;
	}

	status = buildIpAddr(&uid, &gid, 0, &saddr, &path);
	assert( !status );

	stat = mntcall( &saddr,
					MOUNTPROC_UMNT,
					(xdrproc_t)xdr_dirpath, &path,
					(xdrproc_t)xdr_void,	 0,
				    uid,
				    gid
				  );

	if (stat) {
		UNLOCK(nfsGlob.llock);
		fprintf(stderr,"NFS UMOUNT -- %s\n", clnt_sperrno(stat));
		return;
	}

	nfsNodeDestroy(mt_entry->mt_fs_root->location.node_access);
	mt_entry->mt_fs_root->location.node_access = 0;

	nfsDestroy(mt_entry->fs_info);
	mt_entry->fs_info = 0;

	nfsGlob.num_mounted_fs--;
UNLOCK(nfsGlob.llock);
}

static int nfs_mknod(
	const rtems_filesystem_location_info_t *parentloc,
	const char *name,
	size_t namelen,
	mode_t mode,
	dev_t dev
)
{

int					rv = 0;
struct timeval				now;
diropres				res;
NfsNode					node = parentloc->node_access;
Nfs					nfs  = node->nfs;
mode_t					type = S_IFMT & mode;
char					*dupname;

	if (type != S_IFDIR && type != S_IFREG)
		rtems_set_errno_and_return_minus_one(ENOTSUP);

	dupname = nfs_dupname(name, namelen);
	if (dupname == NULL)
		return -1;

#if DEBUG & DEBUG_SYSCALLS
	fprintf(stderr,"nfs_mknod: creating %s\n", dupname);
#endif

        rtems_clock_get_tod_timeval(&now);

	SERP_ARGS(node).createarg.name       		= dupname;
	SERP_ARGS(node).createarg.attributes.mode	= mode;
	SERP_ARGS(node).createarg.attributes.uid	= nfs->uid;
	SERP_ARGS(node).createarg.attributes.gid	= nfs->gid;
	SERP_ARGS(node).createarg.attributes.size	= 0;
	SERP_ARGS(node).createarg.attributes.atime.seconds	= now.tv_sec;
	SERP_ARGS(node).createarg.attributes.atime.useconds	= now.tv_usec;
	SERP_ARGS(node).createarg.attributes.mtime.seconds	= now.tv_sec;
	SERP_ARGS(node).createarg.attributes.mtime.useconds	= now.tv_usec;

	rv = nfscall(
		nfs->server,
		(type == S_IFDIR) ? NFSPROC_MKDIR : NFSPROC_CREATE,
		(xdrproc_t)xdr_createargs, &SERP_FILE(node),
		(xdrproc_t)xdr_diropres, &res
	);

	if (rv == 0) {
		rv = nfsEvaluateStatus(res.status);
#if DEBUG & DEBUG_SYSCALLS
		if (rv != 0) {
			perror("nfs_mknod");
		}
#endif
	}

	free(dupname);

	return rv;
}

static int nfs_rmnod(
	const rtems_filesystem_location_info_t *parentloc,
	const rtems_filesystem_location_info_t *loc
)
{
	int rv = 0;
	NfsNode	node  = loc->node_access;
	int force_update = 0;

	if (updateAttr(node, force_update) == 0) {
		int proc = SERP_ATTR(node).type == NFDIR
			? NFSPROC_RMDIR
				: NFSPROC_REMOVE;

		rv = nfs_do_unlink(parentloc, loc, proc);
	} else {
		rv = -1;
	}

	return rv;
}

static int nfs_utime(
	const rtems_filesystem_location_info_t  *pathloc, /* IN */
	time_t                                   actime,  /* IN */
	time_t                                   modtime  /* IN */
)
{
sattr	arg;

	/* TODO: add rtems EPOCH - UNIX EPOCH seconds */
	arg.atime.seconds  = actime;
	arg.atime.useconds = 0;
	arg.mtime.seconds  = modtime;
	arg.mtime.useconds = 0;

	return nfs_sattr(pathloc->node_access, &arg, SATTR_ATIME | SATTR_MTIME);
}

static int nfs_symlink(
	const rtems_filesystem_location_info_t *parentloc,
	const char *name,
	size_t namelen,
	const char *target
)
{
int					rv = 0;
struct timeval				now;
nfsstat					status;
NfsNode					node = parentloc->node_access;
Nfs					nfs  = node->nfs;
char					*dupname;

	dupname = nfs_dupname(name, namelen);
	if (dupname == NULL)
		return -1;

#if DEBUG & DEBUG_SYSCALLS
	fprintf(stderr,"nfs_symlink: creating %s -> %s\n", dupname, target);
#endif

	rtems_clock_get_tod_timeval(&now);

	SERP_ARGS(node).symlinkarg.name       		= dupname;
	SERP_ARGS(node).symlinkarg.to				= (nfspath) target;

	SERP_ARGS(node).symlinkarg.attributes.mode	= S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO;
	SERP_ARGS(node).symlinkarg.attributes.uid	= nfs->uid;
	SERP_ARGS(node).symlinkarg.attributes.gid	= nfs->gid;
	SERP_ARGS(node).symlinkarg.attributes.size	= 0;
	SERP_ARGS(node).symlinkarg.attributes.atime.seconds  = now.tv_sec;
	SERP_ARGS(node).symlinkarg.attributes.atime.useconds = now.tv_usec;
	SERP_ARGS(node).symlinkarg.attributes.mtime.seconds  = now.tv_sec;
	SERP_ARGS(node).symlinkarg.attributes.mtime.useconds = now.tv_usec;

	rv = nfscall(
		nfs->server,
		NFSPROC_SYMLINK,
		(xdrproc_t)xdr_symlinkargs, &SERP_FILE(node),
		(xdrproc_t)xdr_nfsstat, &status
	);

	if (rv == 0) {
		rv = nfsEvaluateStatus(status);
#if DEBUG & DEBUG_SYSCALLS
		perror("nfs_symlink");
#endif
	}

	free(dupname);

	return rv;
}

static ssize_t nfs_readlink_with_node(
	NfsNode node,
	char *buf,
	size_t len
)
{
	ssize_t rv;
	Nfs nfs = node->nfs;
	readlinkres_strbuf rr;

	rr.strbuf.buf = buf;
	rr.strbuf.max = len - 1;

	rv = nfscall(
		nfs->server,
		NFSPROC_READLINK,
		(xdrproc_t)xdr_nfs_fh, &SERP_FILE(node),
		(xdrproc_t)xdr_readlinkres_strbuf, &rr
	);

	if (rv == 0) {
		rv = nfsEvaluateStatus(rr.status);

		if (rv == 0) {
			rv = (ssize_t) strlen(rr.strbuf.buf);
		} else {
#if DEBUG & DEBUG_SYSCALLS
			perror("nfs_readlink_with_node");
#endif
		}
	}

	return rv;
}

static ssize_t nfs_readlink(
	const rtems_filesystem_location_info_t *loc,
	char *buf,
	size_t len
)
{
	NfsNode	node = loc->node_access;

	return nfs_readlink_with_node(node, buf, len);
}

static int nfs_rename(
	const rtems_filesystem_location_info_t *oldparentloc,
	const rtems_filesystem_location_info_t *oldloc,
	const rtems_filesystem_location_info_t *newparentloc,
	const char *name,
	size_t namelen
)
{
	int rv = 0;
	char *dupname = nfs_dupname(name, namelen);

	if (dupname != NULL) {
		NfsNode oldParentNode = oldparentloc->node_access;
		NfsNode oldNode = oldloc->node_access;
		NfsNode newParentNode = newparentloc->node_access;
		Nfs nfs = oldParentNode->nfs;
		const nfs_fh *toDirSrc = &SERP_FILE(newParentNode);
		nfs_fh *toDirDst = &SERP_ARGS(oldParentNode).renamearg.to.dir;
		nfsstat	status;

		SERP_ARGS(oldParentNode).renamearg.name = oldNode->str;
		SERP_ARGS(oldParentNode).renamearg.to.name = dupname;
		memcpy(toDirDst, toDirSrc, sizeof(*toDirDst));

		rv = nfscall(
			nfs->server,
			NFSPROC_RENAME,
			(xdrproc_t) xdr_renameargs,
			&SERP_FILE(oldParentNode),
			(xdrproc_t) xdr_nfsstat,
			&status
		);

		if (rv == 0) {
			rv = nfsEvaluateStatus(status);
		}

		free(dupname);
	} else {
		rv = -1;
	}

	return rv;
}

static void nfs_lock(const rtems_filesystem_mount_table_entry_t *mt_entry)
{
}

static void nfs_unlock(const rtems_filesystem_mount_table_entry_t *mt_entry)
{
}

static bool nfs_are_nodes_equal(
	const rtems_filesystem_location_info_t *a,
	const rtems_filesystem_location_info_t *b
)
{
	bool equal = false;
	NfsNode na = a->node_access;

	if (updateAttr(na, 0) == 0) {
		NfsNode nb = b->node_access;

		if (updateAttr(nb, 0) == 0) {
			equal = SERP_ATTR(na).fileid == SERP_ATTR(nb).fileid
				&& SERP_ATTR(na).fsid == SERP_ATTR(nb).fsid;
		}
	}

	return equal;
}

static int nfs_fchmod(
	const rtems_filesystem_location_info_t *loc,
	mode_t mode
)
{
sattr	arg;

	arg.mode = mode;
	return nfs_sattr(loc->node_access, &arg, SATTR_MODE);

}

const struct _rtems_filesystem_operations_table nfs_fs_ops = {
	.lock_h         = nfs_lock,
	.unlock_h       = nfs_unlock,
	.eval_path_h    = nfs_eval_path,
	.link_h         = nfs_link,
	.are_nodes_equal_h = nfs_are_nodes_equal,
	.mknod_h        = nfs_mknod,
	.rmnod_h        = nfs_rmnod,
	.fchmod_h       = nfs_fchmod,
	.chown_h        = nfs_chown,
	.clonenod_h     = nfs_clonenode,
	.freenod_h      = nfs_freenode,
	.mount_h        = rtems_filesystem_default_mount,
	.unmount_h      = rtems_filesystem_default_unmount,
	.fsunmount_me_h = nfs_fsunmount_me,
	.utime_h        = nfs_utime,
	.symlink_h      = nfs_symlink,
	.readlink_h     = nfs_readlink,
	.rename_h       = nfs_rename,
	.statvfs_h      = rtems_filesystem_default_statvfs
};

/*****************************************
	File Handlers

	NOTE: the FS generics expect a FS'
	      evalpath_h() to switch the
		  pathloc->handlers according
		  to the pathloc/node's file
		  type.
		  We currently have 'file' and
		  'directory' handlers and very
		  few 'symlink' handlers.

		  The handlers for each type are
		  implemented or #defined ZERO
		  in a 'nfs_file_xxx',
		  'nfs_dir_xxx', 'nfs_link_xxx'
		  sequence below this point.

		  In some cases, a common handler,
		  can be used for all file types.
		  It is then simply called
		  'nfs_xxx'.
 *****************************************/

/* stateless NFS protocol makes this trivial */
static int nfs_file_open(
	rtems_libio_t *iop,
	const char    *pathname,
	int           oflag,
	mode_t        mode
)
{
	return 0;
}

/* reading directories is not stateless; we must
 * remember the last 'read' position, i.e.
 * the server 'cookie'. We do manage this information
 * attached to the pathinfo.node_access_2.
 */
static int nfs_dir_open(
	rtems_libio_t *iop,
	const char    *pathname,
	int           oflag,
	mode_t        mode
)
{
NfsNode		node = iop->pathinfo.node_access;
DirInfo		di;

	/* create a readdirargs object and copy the file handle;
	 * attach to the pathinfo.node_access_2
	 */

	di = (DirInfo) malloc(sizeof(*di));
	iop->pathinfo.node_access_2 = di;

	if ( !di  ) {
		errno = ENOMEM;
		return -1;
	}

	memcpy( &di->readdirargs.dir,
			&SERP_FILE(node),
			sizeof(di->readdirargs.dir) );

	/* rewind cookie */
	memset( &di->readdirargs.cookie,
	        0,
	        sizeof(di->readdirargs.cookie) );

	di->eofreached = FALSE;

	return 0;
}

static int nfs_file_close(
	rtems_libio_t *iop
)
{
	return 0;
}

static int nfs_dir_close(
	rtems_libio_t *iop
)
{
	free(iop->pathinfo.node_access_2);
	iop->pathinfo.node_access_2 = 0;
	return 0;
}

static ssize_t nfs_file_read_chunk(
	NfsNode node,
	uint32_t offset,
	void *buffer,
	size_t count
)
{
ssize_t rv;
readres	rr;
Nfs		nfs  = node->nfs;

	SERP_ARGS(node).readarg.offset		= offset;
	SERP_ARGS(node).readarg.count	  	= count;
	SERP_ARGS(node).readarg.totalcount	= UINT32_C(0xdeadbeef);

	rr.readres_u.reply.data.data_val	= buffer;

	rv = nfscall(
		nfs->server,
		NFSPROC_READ,
		(xdrproc_t)xdr_readargs, &SERP_FILE(node),
		(xdrproc_t)xdr_readres, &rr
	);

	if (rv == 0) {
		rv = nfsEvaluateStatus(rr.status);

		if (rv == 0) {
			rv = rr.readres_u.reply.data.data_len;

#if DEBUG & DEBUG_SYSCALLS
			fprintf(stderr,
				"Read %i (asked for %i) bytes from offset %i to 0x%08x\n",
				rr.readres_u.reply.data.data_len,
				count,
				iop->offset,
				rr.readres_u.reply.data.data_val);
#endif
		}
	}

	return rv;
}

static ssize_t nfs_file_read(
	rtems_libio_t *iop,
	void *buffer,
	size_t count
)
{
	ssize_t rv = 0;
	NfsNode node = iop->pathinfo.node_access;
	uint32_t offset = iop->offset;
	char *in = buffer;

	if (iop->offset < 0) {
		errno = EINVAL;
		return -1;
	}

	if ((uintmax_t) iop->offset >= UINT32_MAX) {
		errno = EFBIG;
		return -1;
	}

	if (count > UINT32_MAX - offset) {
		count = UINT32_MAX - offset;
	}

	do {
		size_t chunk = count <= NFS_MAXDATA ? count : NFS_MAXDATA;
		ssize_t done = nfs_file_read_chunk(node, offset, in, chunk);

		if (done > 0) {
			offset += (uint32_t) done;
			in += done;
			count -= (size_t) done;
			rv += done;
		} else {
			count = 0;
			if (done < 0) {
				rv = -1;
			}
		}
	} while (count > 0);

	if (rv > 0) {
		iop->offset = offset;
	}

	return rv;
}

/* this is called by readdir() / getdents() */
static ssize_t nfs_dir_read(
	rtems_libio_t *iop,
	void          *buffer,
	size_t        count
)
{
ssize_t rv;
DirInfo			di     = iop->pathinfo.node_access_2;
RpcUdpServer	server = ((Nfs)iop->pathinfo.mt_entry->fs_info)->server;

	if ( di->eofreached )
		return 0;

	di->ptr = di->buf = buffer;

	/* align + round down the buffer */
	count &= ~ (DIRENT_HEADER_SIZE - 1);
	di->len = count;

#if 0
	/* now estimate the number of entries we should ask for */
	count /= DIRENT_HEADER_SIZE + CONFIG_AVG_NAMLEN;

	/* estimate the encoded size that might take up */
	count *= dirres_entry_size + CONFIG_AVG_NAMLEN;
#else
	/* integer arithmetics are better done the other way round */
	count *= dirres_entry_size + CONFIG_AVG_NAMLEN;
	count /= DIRENT_HEADER_SIZE + CONFIG_AVG_NAMLEN;
#endif

	if (count > NFS_MAXDATA)
		count = NFS_MAXDATA;

	di->readdirargs.count = count;

#if DEBUG & DEBUG_READDIR
	fprintf(stderr,
			"Readdir: asking for %i XDR bytes, buffer is %i\n",
			count, di->len);
#endif

	rv = nfscall(
		server,
		NFSPROC_READDIR,
		(xdrproc_t)xdr_readdirargs, &di->readdirargs,
		(xdrproc_t)xdr_dir_info, di
	);

	if (rv == 0) {
		rv = nfsEvaluateStatus(di->status);

		if (rv == 0) {
			rv = (char*)di->ptr - (char*)buffer;
		}
	}

	return rv;
}

static ssize_t nfs_file_write(
	rtems_libio_t *iop,
	const void    *buffer,
	size_t        count
)
{
ssize_t rv;
NfsNode 	node = iop->pathinfo.node_access;
Nfs			nfs  = node->nfs;

	if (count > NFS_MAXDATA)
		count = NFS_MAXDATA;


	SERP_ARGS(node).writearg.beginoffset = UINT32_C(0xdeadbeef);
	if (rtems_libio_iop_is_append(iop)) {
		if ( updateAttr(node, 0) ) {
			return -1;
		}
		if (SERP_ATTR(node).size >= UINT32_MAX) {
			errno = EFBIG;
			return -1;
		}
		SERP_ARGS(node).writearg.offset = SERP_ATTR(node).size;
	} else {
		if (iop->offset < 0) {
			errno = EINVAL;
			return -1;
		}
		if ((uintmax_t) iop->offset >= UINT32_MAX) {
			errno = EFBIG;
			return -1;
		}
		SERP_ARGS(node).writearg.offset = iop->offset;
	}

	if (count > UINT32_MAX - SERP_ARGS(node).writearg.offset) {
		count = UINT32_MAX - SERP_ARGS(node).writearg.offset;
	}

	SERP_ARGS(node).writearg.totalcount	   = UINT32_C(0xdeadbeef);
	SERP_ARGS(node).writearg.data.data_len = count;
	SERP_ARGS(node).writearg.data.data_val = (void*)buffer;

	/* write XDR buffer size will be chosen by nfscall based
	 * on the PROC specifier
	 */

	rv = nfscall(
		nfs->server,
		NFSPROC_WRITE,
		(xdrproc_t)xdr_writeargs, &SERP_FILE(node),
		(xdrproc_t)xdr_attrstat, &node->serporid
	);

	if (rv == 0) {
		rv = nfsEvaluateStatus(node->serporid.status);

		if (rv == 0) {
			node->age = nowSeconds();

			iop->offset += count;
			rv = count;
		} else {
			/* try at least to recover the current attributes */
			updateAttr(node, 1 /* force */);
		}
	}

	return rv;
}

static off_t nfs_dir_lseek(
	rtems_libio_t *iop,
	off_t          length,
	int            whence
)
{
	off_t rv = rtems_filesystem_default_lseek_directory(iop, length, whence);

	if (rv == 0) {
		DirInfo di = iop->pathinfo.node_access_2;
		nfscookie *cookie = &di->readdirargs.cookie;

		di->eofreached = FALSE;

		/* rewind cookie */
		memset(cookie, 0, sizeof(*cookie));
	}

	return rv;
}

#if 0	/* structure types for reference */
struct fattr {
		ftype type;
		u_int mode;
		u_int nlink;
		u_int uid;
		u_int gid;
		u_int size;
		u_int blocksize;
		u_int rdev;
		u_int blocks;
		u_int fsid;
		u_int fileid;
		nfstime atime;
		nfstime mtime;
		nfstime ctime;
};

struct  stat
{
		dev_t         st_dev;
		ino_t         st_ino;
		mode_t        st_mode;
		nlink_t       st_nlink;
		uid_t         st_uid;
		gid_t         st_gid;
		dev_t         st_rdev;
		off_t         st_size;
		/* SysV/sco doesn't have the rest... But Solaris, eabi does.  */
#if defined(__svr4__) && !defined(__PPC__) && !defined(__sun__)
		time_t        st_atime;
		time_t        st_mtime;
		time_t        st_ctime;
#else
		time_t        st_atime;
		long          st_spare1;
		time_t        st_mtime;
		long          st_spare2;
		time_t        st_ctime;
		long          st_spare3;
		long          st_blksize;
		long          st_blocks;
		long      st_spare4[2];
#endif
};
#endif

/* common for file/dir/link */
static int nfs_fstat(
	const rtems_filesystem_location_info_t *loc,
	struct stat *buf
)
{
NfsNode	node = loc->node_access;
fattr	*fa  = &SERP_ATTR(node);

	if (updateAttr(node, 0 /* only if old */)) {
		return -1;
	}

/* done by caller
	memset(buf, 0, sizeof(*buf));
 */

	/* translate */

	/* one of the branches hopefully is optimized away */
	if (sizeof(ino_t) < sizeof(u_int)) {
	buf->st_dev		= NFS_MAKE_DEV_T_INO_HACK((NfsNode)loc->node_access);
	} else {
	buf->st_dev		= NFS_MAKE_DEV_T((NfsNode)loc->node_access);
	}
	buf->st_mode	= fa->mode;
	buf->st_nlink	= fa->nlink;
	buf->st_uid		= fa->uid;
	buf->st_gid		= fa->gid;
	buf->st_size	= fa->size;
	/* Set to "preferred size" of this NFS client implementation */
	buf->st_blksize	= nfsStBlksize ? nfsStBlksize : fa->blocksize;
	buf->st_rdev	= fa->rdev;
	buf->st_blocks	= fa->blocks;
	buf->st_ino     = fa->fileid;
	buf->st_atime	= fa->atime.seconds;
	buf->st_mtime	= fa->mtime.seconds;
	buf->st_ctime	= fa->ctime.seconds;

#if 0 /* NFS should return the modes */
	switch(fa->type) {
		default:
		case NFNON:
		case NFBAD:
				break;

		case NFSOCK: buf->st_mode |= S_IFSOCK; break;
		case NFFIFO: buf->st_mode |= S_IFIFO;  break;
		case NFREG : buf->st_mode |= S_IFREG;  break;
		case NFDIR : buf->st_mode |= S_IFDIR;  break;
		case NFBLK : buf->st_mode |= S_IFBLK;  break;
		case NFCHR : buf->st_mode |= S_IFCHR;  break;
		case NFLNK : buf->st_mode |= S_IFLNK;  break;
	}
#endif

	return 0;
}

/* a helper which does the real work for
 * a couple of handlers (such as chmod,
 * ftruncate or utime)
 */
static int
nfs_sattr(NfsNode node, sattr *arg, u_long mask)
{
int rv;
struct timeval				now;
nfstime					nfsnow, t;
u_int					mode;

	if (updateAttr(node, 0 /* only if old */))
		return -1;

        rtems_clock_get_tod_timeval(&now);

	/* TODO: add rtems EPOCH - UNIX EPOCH seconds */
	nfsnow.seconds  = now.tv_sec;
	nfsnow.useconds = now.tv_usec;

	/* merge permission bits into existing type bits */
	mode = SERP_ATTR(node).mode;
	if (mask & SATTR_MODE) {
		mode &= S_IFMT;
		mode |= arg->mode & ~S_IFMT;
	} else {
		mode = -1;
	}
	SERP_ARGS(node).sattrarg.attributes.mode  = mode;

	SERP_ARGS(node).sattrarg.attributes.uid	  =
		(mask & SATTR_UID)  ? arg->uid : -1;

	SERP_ARGS(node).sattrarg.attributes.gid	  =
		(mask & SATTR_GID)  ? arg->gid : -1;

	SERP_ARGS(node).sattrarg.attributes.size  =
		(mask & SATTR_SIZE) ? arg->size : -1;

	if (mask & SATTR_ATIME)
		t = arg->atime;
	else if (mask & SATTR_TOUCHA)
		t = nfsnow;
	else
		t.seconds = t.useconds = -1;
	SERP_ARGS(node).sattrarg.attributes.atime = t;

	if (mask & SATTR_ATIME)
		t = arg->mtime;
	else if (mask & SATTR_TOUCHA)
		t = nfsnow;
	else
		t.seconds = t.useconds = -1;
	SERP_ARGS(node).sattrarg.attributes.mtime = t;

	node->serporid.status = NFS_OK;

	rv = nfscall(
		node->nfs->server,
		NFSPROC_SETATTR,
		(xdrproc_t)xdr_sattrargs, &SERP_FILE(node),
		(xdrproc_t)xdr_attrstat, &node->serporid
	);

	if (rv == 0) {
		rv = nfsEvaluateStatus(node->serporid.status);

		if (rv == 0) {
			node->age = nowSeconds();
		} else {
#if DEBUG & DEBUG_SYSCALLS
			fprintf(stderr,"nfs_sattr: %s\n",strerror(errno));
#endif
			/* try at least to recover the current attributes */
			updateAttr(node, 1 /* force */);
		}
	} else {
#if DEBUG & DEBUG_SYSCALLS
		fprintf(stderr,
				"nfs_sattr (mask 0x%08x): %s",
				mask,
				strerror(errno));
#endif
	}

	return rv;
}

/* just set the size attribute to 'length'
 * the server will take care of the rest :-)
 */
static int nfs_file_ftruncate(
	rtems_libio_t *iop,
	off_t          length
)
{
sattr					arg;

	if (length < 0) {
		errno = EINVAL;
		return -1;
	}

	if ((uintmax_t) length > UINT32_MAX) {
		errno = EFBIG;
		return -1;
	}

	arg.size = length;
	/* must not modify any other attribute; if we are not the owner
	 * of the file or directory but only have write access changing
	 * any attribute besides 'size' will fail...
	 */
	return nfs_sattr(iop->pathinfo.node_access,
					 &arg,
					 SATTR_SIZE);
}

/* the file handlers table */
static const
struct _rtems_filesystem_file_handlers_r nfs_file_file_handlers = {
	.open_h      = nfs_file_open,
	.close_h     = nfs_file_close,
	.read_h      = nfs_file_read,
	.write_h     = nfs_file_write,
	.ioctl_h     = rtems_filesystem_default_ioctl,
	.lseek_h     = rtems_filesystem_default_lseek_file,
	.fstat_h     = nfs_fstat,
	.ftruncate_h = nfs_file_ftruncate,
	.fsync_h     = rtems_filesystem_default_fsync_or_fdatasync,
	.fdatasync_h = rtems_filesystem_default_fsync_or_fdatasync,
	.fcntl_h     = rtems_filesystem_default_fcntl,
	.kqfilter_h  = rtems_filesystem_default_kqfilter,
	.mmap_h      = rtems_filesystem_default_mmap,
	.poll_h      = rtems_filesystem_default_poll,
	.readv_h     = rtems_filesystem_default_readv,
	.writev_h    = rtems_filesystem_default_writev
};

/* the directory handlers table */
static const
struct _rtems_filesystem_file_handlers_r nfs_dir_file_handlers = {
	.open_h      = nfs_dir_open,
	.close_h     = nfs_dir_close,
	.read_h      = nfs_dir_read,
	.write_h     = rtems_filesystem_default_write,
	.ioctl_h     = rtems_filesystem_default_ioctl,
	.lseek_h     = nfs_dir_lseek,
	.fstat_h     = nfs_fstat,
	.ftruncate_h = rtems_filesystem_default_ftruncate_directory,
	.fsync_h     = rtems_filesystem_default_fsync_or_fdatasync,
	.fdatasync_h = rtems_filesystem_default_fsync_or_fdatasync,
	.fcntl_h     = rtems_filesystem_default_fcntl,
	.kqfilter_h  = rtems_filesystem_default_kqfilter,
	.mmap_h      = rtems_filesystem_default_mmap,
	.poll_h      = rtems_filesystem_default_poll,
	.readv_h     = rtems_filesystem_default_readv,
	.writev_h    = rtems_filesystem_default_writev
};

/* the link handlers table */
static const
struct _rtems_filesystem_file_handlers_r nfs_link_file_handlers = {
	.open_h      = rtems_filesystem_default_open,
	.close_h     = rtems_filesystem_default_close,
	.read_h      = rtems_filesystem_default_read,
	.write_h     = rtems_filesystem_default_write,
	.ioctl_h     = rtems_filesystem_default_ioctl,
	.lseek_h     = rtems_filesystem_default_lseek,
	.fstat_h     = nfs_fstat,
	.ftruncate_h = rtems_filesystem_default_ftruncate,
	.fsync_h     = rtems_filesystem_default_fsync_or_fdatasync,
	.fdatasync_h = rtems_filesystem_default_fsync_or_fdatasync,
	.fcntl_h     = rtems_filesystem_default_fcntl,
	.kqfilter_h  = rtems_filesystem_default_kqfilter,
	.mmap_h      = rtems_filesystem_default_mmap,
	.poll_h      = rtems_filesystem_default_poll,
	.readv_h     = rtems_filesystem_default_readv,
	.writev_h    = rtems_filesystem_default_writev
};

/* we need a dummy driver entry table to get a
 * major number from the system
 */
static
rtems_device_driver nfs_initialize(
		rtems_device_major_number	major,
		rtems_device_minor_number	minor,
		void						*arg
)
{
	/* we don't really use this routine because
     * we cannot supply an argument (contrary
     * to what the 'arg' parameter suggests - it
     * is always set to 0 by the generics :-()
     * and because we don't want the user to
     * have to deal with the major number (which
     * OTOH is something WE are interested in. The
     * only reason for using this API was getting
     * a major number, after all).
     *
	 * Something must be present, however, to
	 * reserve a slot in the driver table.
	 */
	return RTEMS_SUCCESSFUL;
}

static rtems_driver_address_table	drvNfs = {
		nfs_initialize,
		0,					/* open    */
		0,					/* close   */
		0,					/* read    */
		0,					/* write   */
		0					/* control */
};

/* Dump a list of the currently mounted NFS to a  file */
int
nfsMountsShow(FILE *f)
{
char	*mntpt = 0;
Nfs		nfs;

	if (!f)
		f = stdout;

	if ( !(mntpt=malloc(MAXPATHLEN)) ) {
		fprintf(stderr,"nfsMountsShow(): no memory\n");
		return -1;
	}

	fprintf(f,"Currently Mounted NFS:\n");

	LOCK(nfsGlob.llock);

	for (nfs = nfsGlob.mounted_fs; nfs; nfs=nfs->next) {
		fprintf(f,"%s on ", nfs->mt_entry->dev);
		if (rtems_filesystem_resolve_location(mntpt, MAXPATHLEN, &nfs->mt_entry->mt_fs_root->location))
			fprintf(f,"<UNABLE TO LOOKUP MOUNTPOINT>\n");
		else
			fprintf(f,"%s\n",mntpt);
	}

	UNLOCK(nfsGlob.llock);

	free(mntpt);
	return 0;
}

#if 0
CCJ_REMOVE_MOUNT
/* convenience wrapper
 *
 * NOTE: this routine calls NON-REENTRANT
 *       gethostbyname() if the host is
 *       not in 'dot' notation.
 */
int
nfsMount(char *uidhost, char *path, char *mntpoint)
{
struct stat								st;
int										devl;
char									*host;
int										rval = -1;
char									*dev =  0;

	if (!uidhost || !path || !mntpoint) {
		fprintf(stderr,"usage: nfsMount(""[uid.gid@]host"",""path"",""mountpoint"")\n");
		nfsMountsShow(stderr);
		return -1;
	}

	if ( !(dev = malloc((devl=strlen(uidhost) + 20 + strlen(path)+1))) ) {
		fprintf(stderr,"nfsMount: out of memory\n");
		return -1;
	}

	/* Try to create the mount point if nonexistent */
	if (stat(mntpoint, &st)) {
		if (ENOENT != errno) {
			perror("nfsMount trying to create mount point - stat failed");
			goto cleanup;
		} else if (mkdir(mntpoint,0777)) {
			perror("nfsMount trying to create mount point");
			goto cleanup;
		}
	}

	if ( !(host=strchr(uidhost,UIDSEP)) ) {
		host = uidhost;
	} else {
		host++;
	}

	if (isdigit((unsigned char)*host)) {
		/* avoid using gethostbyname */
		sprintf(dev,"%s:%s",uidhost,path);
	} else {
		struct hostent *h;

		/* copy the uid part (hostname will be
		 * overwritten)
		 */
		strcpy(dev, uidhost);

		/* NOTE NOTE NOTE: gethostbyname is NOT
		 * thread safe. This is UGLY
		 */

/* BEGIN OF NON-THREAD SAFE REGION */

		h = gethostbyname(host);

		if ( !h ||
			 !inet_ntop( AF_INET,
					     (struct in_addr*)h->h_addr_list[0],
						 dev  + (host - uidhost),
						 devl - (host - uidhost) )
			) {
			fprintf(stderr,"nfsMount: host '%s' not found\n",host);
			goto cleanup;
		}

/* END OF NON-THREAD SAFE REGION */

		/* append ':<path>' */
		strcat(dev,":");
		strcat(dev,path);
	}

	printf("Trying to mount %s on %s\n",dev,mntpoint);

	if (mount(dev,
			  mntpoint,
			  "nfs",
 			  RTEMS_FILESYSTEM_READ_WRITE,
 			  NULL)) {
		perror("nfsMount - mount");
		goto cleanup;
	}

	rval = 0;

cleanup:
	free(dev);
	return rval;
}
#endif

/* HERE COMES A REALLY UGLY HACK */

/* This is stupid; it is _very_ hard to find the path
 * leading to a rtems_filesystem_location_info_t node :-(
 * The only easy way is making the location the current
 * directory and issue a getcwd().
 * However, since we don't want to tamper with the
 * current directory, we must create a separate
 * task to do the job for us - sigh.
 */

typedef struct ResolvePathArgRec_ {
	rtems_filesystem_location_info_t	*loc;	/* IN: location to resolve	*/
	char								*buf;	/* IN/OUT: buffer where to put the path */
	int									len;	/* IN: buffer length		*/
	rtems_binary_semaphore			sync;	/* IN: synchronization		*/
	rtems_status_code					status; /* OUT: result				*/
} ResolvePathArgRec, *ResolvePathArg;

static void
resolve_path(rtems_task_argument arg)
{
ResolvePathArg						rpa = (ResolvePathArg)arg;
rtems_filesystem_location_info_t	old;

	/* IMPORTANT: let the helper task have its own libio environment (i.e. cwd) */
	if (RTEMS_SUCCESSFUL == (rpa->status = rtems_libio_set_private_env())) {

		old = rtems_filesystem_current->location;

		rtems_filesystem_current->location = *(rpa->loc);

		if ( !getcwd(rpa->buf, rpa->len) )
			rpa->status = RTEMS_UNSATISFIED;

		/* must restore the cwd because 'freenode' will be called on it */
		rtems_filesystem_current->location = old;
	}
	rtems_binary_semaphore_post(&rpa->sync);
	rtems_task_exit();
}


/* a utility routine to find the path leading to a
 * rtems_filesystem_location_info_t node
 *
 * INPUT: 'loc' and a buffer 'buf' (length 'len') to hold the
 *        path.
 * OUTPUT: path copied into 'buf'
 *
 * RETURNS: 0 on success, RTEMS error code on error.
 */
rtems_status_code
rtems_filesystem_resolve_location(char *buf, int len, rtems_filesystem_location_info_t *loc)
{
ResolvePathArgRec	arg;
rtems_id			tid = 0;
rtems_task_priority	pri;
rtems_status_code	status;

	arg.loc  = loc;
	arg.buf  = buf;
	arg.len  = len;

	rtems_binary_semaphore_init(&arg.sync, "NFSress");

	rtems_task_set_priority(RTEMS_SELF, RTEMS_CURRENT_PRIORITY, &pri);

	status = rtems_task_create(
					rtems_build_name('r','e','s','s'),
					pri,
					RTEMS_MINIMUM_STACK_SIZE + 50000,
					RTEMS_DEFAULT_MODES,
					RTEMS_DEFAULT_ATTRIBUTES,
					&tid);

	if (RTEMS_SUCCESSFUL != status)
		goto cleanup;

	status = rtems_task_start(tid, resolve_path, (rtems_task_argument)&arg);

	if (RTEMS_SUCCESSFUL != status) {
		rtems_task_delete(tid);
		goto cleanup;
	}


	/* synchronize with the helper task */
	rtems_binary_semaphore_wait(&arg.sync);

	status = arg.status;

cleanup:
	rtems_binary_semaphore_destroy(&arg.sync);

	return status;
}

int
nfsSetTimeout(uint32_t timeout_ms)
{
rtems_interrupt_lock_context lock_context;
uint32_t	          s,us;

	if ( timeout_ms > 100000 ) {
		/* out of range */
		return -1;
	}

	s  = timeout_ms/1000;
	us = (timeout_ms % 1000) * 1000;

	NFS_GLOBAL_ACQUIRE(&lock_context);
	_nfscalltimeout.tv_sec  = s;
	_nfscalltimeout.tv_usec = us;
	NFS_GLOBAL_RELEASE(&lock_context);

	return 0;
}

uint32_t
nfsGetTimeout( void )
{
rtems_interrupt_lock_context lock_context;
uint32_t              s,us;
	NFS_GLOBAL_ACQUIRE(&lock_context);
	s  = _nfscalltimeout.tv_sec;
	us = _nfscalltimeout.tv_usec;
	NFS_GLOBAL_RELEASE(&lock_context);
	return s*1000 + us/1000;
}
