diff options
Diffstat (limited to 'c/src/nfsclient/src/nfs.c')
-rw-r--r-- | c/src/nfsclient/src/nfs.c | 3442 |
1 files changed, 3442 insertions, 0 deletions
diff --git a/c/src/nfsclient/src/nfs.c b/c/src/nfsclient/src/nfs.c new file mode 100644 index 0000000000..2b5a04e36f --- /dev/null +++ b/c/src/nfsclient/src/nfs.c @@ -0,0 +1,3442 @@ +/* $Id$ */ + +/* NFS client implementation for RTEMS; hooks into the RTEMS filesystem */ + +/* Author: Till Straumann <strauman@slac.stanford.edu> 2002 */ + +/* + * 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 <string.h> +#include <stdio.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 <nfs_prot.h> +#include <mount_prot.h> + +#include "rpcio.h" + +#ifdef HAVE_CEXP_H +#include <cexpHelp.h> +#endif + + +/* 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, \ + (((node)->nfs->id)<<16) | (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, \ + (((node)->nfs->id)<<16) | (SERP_ATTR((node)).fsid & ((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 MUTEX_ATTRIBUTES (RTEMS_LOCAL | \ + RTEMS_PRIORITY | \ + RTEMS_INHERIT_PRIORITY | \ + RTEMS_BINARY_SEMAPHORE) + +#define LOCK(s) do { \ + rtems_semaphore_obtain((s), \ + RTEMS_WAIT, \ + RTEMS_NO_TIMEOUT); \ + } while (0) + +#define UNLOCK(s) do { rtems_semaphore_release((s)); \ + } while (0) + +/***************************************** + 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; + +static bool_t +xdr_strbuf(XDR *xdrs, strbuf *obj) +{ + return xdr_string(xdrs, &obj->buf, obj->max); +} + +/* Read 'readlink' results into a 'strbuf'. + * This is convenient as it avoids + * one extra step of copying / lenght + * 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; + 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 { + u_int offset; + u_int count; + u_int totalcount; + } readarg; + struct { + u_int beginoffset; + u_int offset; + u_int totalcount; + struct { + u_int 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; + u_int 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) +{ +register uint32_t rval; +#ifndef READ_LONG_IS_ATOMIC +rtems_interrupt_level l; + + rtems_interrupt_disable(level); +#endif + + rval = _TOD_Seconds_since_epoch; + +#ifndef READ_LONG_IS_ATOMIC + rtems_interrupt_enable(level); +#endif + 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 int nfs_readlink( + rtems_filesystem_location_info_t *loc, /* IN */ + char *buf, /* OUT */ + 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 struct _rtems_filesystem_operations_table nfs_fs_ops; +static struct _rtems_filesystem_file_handlers_r nfs_file_file_handlers; +static struct _rtems_filesystem_file_handlers_r nfs_dir_file_handlers; +static 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); + + +/***************************************** + Inline Routines + *****************************************/ + + +/* * * * * * * * * * * * * * * * * * + Trivial Operations on a NfsNode + * * * * * * * * * * * * * * * * * */ + +/* determine if a location 'l' is an NFS root node */ +static inline int +locIsRoot(rtems_filesystem_location_info_t *l) +{ +NfsNode me = (NfsNode) l->node_access; +NfsNode r; + r = (NfsNode)l->mt_entry->mt_fs_root.node_access; + return SERP_ATTR(r).fileid == SERP_ATTR(me).fileid && + SERP_ATTR(r).fsid == SERP_ATTR(me).fsid; +} + +/* determine if a location 'l' is an NFS node */ +static inline int +locIsNfs(rtems_filesystem_location_info_t *l) +{ + return l->ops == &nfs_fs_ops; +} + +/* determine if two locations refer to the + * same entity. We know that 'nfsloc' is a + * location inside nfs. However, we needn't + * know anything about 'anyloc'. + */ +static inline int +locAreEqual( + rtems_filesystem_location_info_t *nfsloc, + rtems_filesystem_location_info_t *anyloc +) +{ +NfsNode na = (NfsNode) nfsloc->node_access; +NfsNode nb; + + if (!locIsNfs(anyloc)) + return 0; + + nb = (NfsNode) anyloc->node_access; + + if (na->nfs != nb->nfs) + return 0; + + updateAttr(nb, 0); + + return SERP_ATTR(na).fileid == SERP_ATTR(nb).fileid && + SERP_ATTR(na).fsid == SERP_ATTR(nb).fsid; +} + + + +/***************************************** + Global Variables + *****************************************/ + +/* These are (except for MAXNAMLEN/MAXPATHLEN) copied from IMFS */ + +static 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_id llock; + /* A lock for protecting misc + * stuff within the driver. + * The lock must only be held + * for short periods of time. + */ + rtems_id 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; +} nfsGlob = {0/* IMPORTANT */}; + +/* + * 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; + +/* 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 + */ +static RpcUdpXactPool smallPool = 0; +static RpcUdpXactPool bigPool = 0; + + +/***************************************** + Implementation + *****************************************/ + +/* 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)); +unsigned long flags; + +#if DEBUG & DEBUG_TRACK_NODES + fprintf(stderr,"NFS: creating a node\n"); +#endif + + if (rval) { + if (fh) + memcpy( &SERP_FILE(rval), fh, sizeof(*fh) ); + rtems_interrupt_disable(flags); + nfs->nodesInUse++; + rtems_interrupt_enable(flags); + rval->nfs = nfs; + rval->str = 0; + } else { + errno = ENOMEM; + } + + return rval; +} + +/* destroy a node */ +static void +nfsNodeDestroy(NfsNode node) +{ +unsigned long flags; + +#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 + + rtems_interrupt_disable(flags); + node->nfs->nodesInUse--; +#if DEBUG & DEBUG_COUNT_NODES + if (node->str) + node->nfs->stringsInUse--; +#endif + rtems_interrupt_enable(flags); + + 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) { + nfsNodeDestroy(rval); + return 0; + } +#if DEBUG & DEBUG_COUNT_NODES + { unsigned long flags; + rtems_interrupt_disable(flags); + node->nfs->stringsInUse++; + rtems_interrupt_enable(flags); + } +#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). + */ +void +nfsInit(int smallPoolDepth, int bigPoolDepth) +{ +entry dummy; + + fprintf(stderr,"This is RTEMS-NFS Release $Name$\n"); + fprintf(stderr,"($Id$)\n\n"); + fprintf(stderr,"Till Straumann, Stanford/SLAC/SSRL 2002\n"); + fprintf(stderr,"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)); + return; + } + + if (0==smallPoolDepth) + smallPoolDepth = 20; + if (0==bigPoolDepth) + bigPoolDepth = 10; + + /* 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); + + assert( smallPool = rpcUdpXactPoolCreate( + NFS_PROGRAM, + NFS_VERSION_2, + CONFIG_NFS_SMALL_XACT_SIZE, + smallPoolDepth) ); + + assert( bigPool = rpcUdpXactPoolCreate( + NFS_PROGRAM, + NFS_VERSION_2, + CONFIG_NFS_BIG_XACT_SIZE, + bigPoolDepth) ); + + assert( RTEMS_SUCCESSFUL == rtems_semaphore_create( + rtems_build_name('N','F','S','l'), + 1, + MUTEX_ATTRIBUTES, + 0, + &nfsGlob.llock) ); + + assert( RTEMS_SUCCESSFUL == rtems_semaphore_create( + rtems_build_name('N','F','S','m'), + 1, + MUTEX_ATTRIBUTES, + 0, + &nfsGlob.lock) ); + + 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"); + + } +} + +/* Driver cleanup code + */ +int +nfsCleanup(void) +{ +rtems_id l; +int refuse; + + if (!nfsGlob.llock) { + /* registering the driver failed - let them still cleanup */ + return 0; + } + + 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; + } + + rtems_semaphore_delete(nfsGlob.lock); + nfsGlob.lock = 0; + + /* hold the lock while cleaning up... */ + + rpcUdpXactPoolDestroy(smallPool); + rpcUdpXactPoolDestroy(bigPool); + l = nfsGlob.llock; + rtems_io_unregister_driver(nfsGlob.nfs_major); + + rtems_semaphore_delete(l); + nfsGlob.llock = 0; + 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 = bigPool; break; + default: pool = 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) +{ + + if (force +#ifdef CONFIG_ATTR_LIFETIME + || (nowSeconds() - node->age > CONFIG_ATTR_LIFETIME) +#endif + ) { + if ( nfscall(node->nfs->server, + NFSPROC_GETATTR, + (xdrproc_t)xdr_nfs_fh, &SERP_FILE(node), + (xdrproc_t)xdr_attrstat, &node->serporid) ) + return -1; + + if ( NFS_OK != node->serporid.status ) { + errno = node->serporid.status; + return -1; + } + + node->age = nowSeconds(); + } + + return 0; +} + +/* + * IP address helper. Note that we avoid + * gethostbyname() since it's not reentrant. + * + * 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) +{ +char host[30]; +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 = RPCIOD_DEFAULT_ID; + *pgid = RPCIOD_DEFAULT_ID; + chpt = *pPath; + } + if ( pHost ) + *pHost = chpt; + + /* split the device name which is in the form + * + * <host_ip> ':' <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; + + if ( ! inet_pton(AF_INET, host, &psa->sin_addr) ) { + errno = ENXIO; + return -1; + } + + 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 + *****************************************/ + +#if 0 /* for reference */ + +struct rtems_filesystem_location_info_tt +{ + void *node_access; + rtems_filesystem_file_handlers_r *handlers; + rtems_filesystem_operations_table *ops; + rtems_filesystem_mount_table_entry_t *mt_entry; +}; + +#endif + +/* + * Evaluate a path letting 'pathloc' travel along. + * + * The important semantics of this operation are: + * + * If this routine returns -1, the caller assumes + * pathloc to be _invalid_ and hence it will not + * invoke rtems_filesystem_freenode() on it. + * + * OTOH, if evalpath returns 0, + * rtems_filesystem_freenode() will eventually be + * called which results in our freeing the associated + * NfsNode attached to node_access. + * + * Therefore, this routine will _always_ allocate + * a NfsNode and pass it out to *pathloc (provided + * that the evaluation succeeds). + * + * However, if the evaluation finds that it has to + * step across FS boundaries (mount point or a symlink + * pointing outside), the NfsNode is destroyed + * before passing control to the new FS' evalpath_h() + * + */ + +STATIC int nfs_do_evalpath( + const char *pathname, /* IN */ + void *arg, + rtems_filesystem_location_info_t *pathloc, /* IN/OUT */ + int forMake +) +{ +char *del = 0, *part; +int e = 0; +NfsNode node = pathloc->node_access; +char *p = malloc(MAXPATHLEN+1); +Nfs nfs = (Nfs)pathloc->mt_entry->fs_info; +RpcUdpServer server = nfs->server; +unsigned long flags; +#if DEBUG & DEBUG_COUNT_NODES +unsigned long niu,siu; +#endif + + if ( !p ) { + e = ENOMEM; + goto cleanup; + } + strcpy(p, pathname); + + LOCK(nfsGlob.lock); + node = nfsNodeClone(node); + UNLOCK(nfsGlob.lock); + + /* from here on, the NFS is protected from being unmounted + * since the node refcount is > 1 + */ + + /* clone the node */ + if ( !node ) { + /* nodeClone sets errno */ + goto cleanup; + } + + pathloc->node_access = node; + + /* Special case: the RTEMS filesystem code + * may emit '..' on a regular file node to + * find the parent directory :-(. + * (eval.c: rtems_filesystem_evaluate_parent()) + * Try to catch this case here: + */ + if ( NFDIR != SERP_ATTR(node).type && '.'==*p && '.'==*(p+1) ) { + for ( part = p+2; '/'==*part; part++ ) + /* skip trailing '/' */; + if ( !*part ) { + /* this is it; back out dir and let them look up the dir itself... */ + memcpy( &SERP_FILE(node), + &node->args.dir, + sizeof(node->args.dir)); + *(p+1)=0; + } + } + + for (part=p; part && *part; part=del) { + + if ( NFLNK == SERP_ATTR(node).type ) { + /* follow midpath link */ + char *b = malloc(NFS_MAXPATHLEN+1); + int l; + + if (!b) { + e = ENOMEM; + goto cleanup; + } + if (nfs_readlink(pathloc, b, NFS_MAXPATHLEN+1)) { + free(b); + e = errno; + goto cleanup; + } + + /* prepend the link value to the rest of the path */ + if ( (l=strlen(b)) + strlen(part) + 1 > NFS_MAXPATHLEN ) { + free(b); + e = EINVAL; + goto cleanup; + } + /* swap string buffers and reset delimiter */ + b[l++] = DELIM; + strcpy(b+l,part); + part = b; + b = p; + p = del = part; + + free(b); + + /* back up the directory filehandle (only necessary + * if we don't back out to the root + */ + if (! (DELIM == *part) ) { + memcpy( &SERP_FILE(node), + &node->args.dir, + sizeof(node->args.dir)); + + if (updateAttr(node, 1 /* force */)) { + e = errno; + goto cleanup; + } + } + } + + /* find delimiter and eat /// sequences + * (only if we don't restart at the root) + */ + if ( DELIM != *part && (del = strchr(part, DELIM))) { + do { + *del++=0; + } while (DELIM==*del); + } + + /* refuse to backup over the root */ + if ( 0==strcmp(part,UPDIR) + && locAreEqual(pathloc, &rtems_filesystem_root) ) { + part++; + } + + /* cross mountpoint upwards */ + if ( (0==strcmp(part,UPDIR) && locIsRoot(pathloc)) /* cross mountpoint up */ + || DELIM == *part /* link starts at root */ + ) { + int rval; + +#if DEBUG & DEBUG_EVALPATH + fprintf(stderr, + "Crossing mountpoint upwards\n"); +#endif + + if (DELIM == *part) { + *pathloc = rtems_filesystem_root; + } else { + *pathloc = pathloc->mt_entry->mt_point_node; + /* re-append the rest of the path */ + if (del) + while ( 0 == *--del ) + *del = DELIM; + } + + nfsNodeDestroy(node); + +#if DEBUG & DEBUG_EVALPATH + fprintf(stderr, + "Re-evaluating '%s'\n", + part); +#endif + + if (forMake) + rval = pathloc->ops->evalformake_h(part, pathloc, (const char**)arg); + else + rval = pathloc->ops->evalpath_h(part, (int)arg, pathloc); + + free(p); + return rval; + } + + /* lookup one element */ + SERP_ARGS(node).diroparg.name = part; + + /* remember args / directory fh */ + memcpy( &node->args, &SERP_FILE(node), sizeof(node->args)); + + /* don't lookup the item we want to create */ + if ( forMake && (!del || !*del) ) + break; + +#if DEBUG & DEBUG_EVALPATH + fprintf(stderr,"Looking up '%s'\n",part); +#endif + + if ( nfscall(server, + NFSPROC_LOOKUP, + (xdrproc_t)xdr_diropargs, &SERP_FILE(node), + (xdrproc_t)xdr_serporid, &node->serporid) || + NFS_OK != (errno=node->serporid.status) ) { + e = errno; + goto cleanup; + } + node->age = nowSeconds(); + +#if DEBUG & DEBUG_EVALPATH + if (NFLNK == SERP_ATTR(node).type && del) { + fprintf(stderr, + "Following midpath link '%s'\n", + part); + } +#endif + + } + + if (forMake) { + /* remember the name - do this _before_ copying + * the name to local storage; the caller expects a + * pointer into pathloc + */ + assert( node->args.name ); + + *(const char**)arg = pathname + (node->args.name - p); + +#if 0 + /* restore the directory node */ + + memcpy( &SERP_FILE(node), + &node->args.dir, + sizeof(node->args.dir)); + + if ( (nfscall(nfs->server, + NFSPROC_GETATTR, + (xdrproc_t)xdr_nfs_fh, &SERP_FILE(node), + (xdrproc_t)xdr_attrstat, &node->serporid) && !errno && (errno = EIO)) || + (NFS_OK != (errno=node->serporid.status) ) ) { + goto cleanup; + } +#endif + } + + if (locIsRoot(pathloc)) { + + /* stupid filesystem code has no 'op' for comparing nodes + * but just compares the 'node_access' pointers. + * Luckily, this is only done for comparing the root nodes. + * Hence, we never give them a copy of the root but always + * the root itself. + */ + pathloc->node_access = pathloc->mt_entry->mt_fs_root.node_access; + /* increment the 'in use' counter since we return one more + * reference to the root node + */ + rtems_interrupt_disable(flags); + nfs->nodesInUse++; + rtems_interrupt_enable(flags); + nfsNodeDestroy(node); + + + } else { + switch (SERP_ATTR(node).type) { + case NFDIR: pathloc->handlers = &nfs_dir_file_handlers; break; + case NFREG: pathloc->handlers = &nfs_file_file_handlers; break; + case NFLNK: pathloc->handlers = &nfs_link_file_handlers; break; + default: pathloc->handlers = &rtems_filesystem_null_handlers; break; + } + pathloc->node_access = node; + + /* remember the name of this directory entry */ + + if (node->args.name) { + if (node->str) { +#if DEBUG & DEBUG_COUNT_NODES + rtems_interrupt_disable(flags); + nfs->stringsInUse--; + rtems_interrupt_enable(flags); +#endif + free(node->str); + } + node->args.name = node->str = strdup(node->args.name); + if (!node->str) { + e = ENOMEM; + goto cleanup; + } + +#if DEBUG & DEBUG_COUNT_NODES + rtems_interrupt_disable(flags); + nfs->stringsInUse++; + rtems_interrupt_enable(flags); +#endif + } + + } + node = 0; + + e = 0; + +cleanup: + free(p); +#if DEBUG & DEBUG_COUNT_NODES + /* cache counters; nfs may be unmounted by other thread after the last + * node is destroyed + */ + niu = nfs->nodesInUse; + siu = nfs->stringsInUse; +#endif + if (node) { + nfsNodeDestroy(node); + pathloc->node_access = 0; + } +#if DEBUG & DEBUG_COUNT_NODES + fprintf(stderr, + "leaving evalpath, in use count is %i nodes, %i strings\n", + niu,siu); +#endif + if (e) { +#if DEBUG & DEBUG_EVALPATH + perror("Evalpath"); +#endif + rtems_set_errno_and_return_minus_one(e); + } else { + return 0; + } +} + +/* MANDATORY; may set errno=ENOSYS and return -1 */ +static int nfs_evalformake( + const char *path, /* IN */ + rtems_filesystem_location_info_t *pathloc, /* IN/OUT */ + const char **pname /* OUT */ +) +{ + return nfs_do_evalpath(path, (void*)pname, pathloc, 1 /*forMake*/); +} + +static int nfs_evalpath( + const char *path, /* IN */ + int flags, /* IN */ + rtems_filesystem_location_info_t *pathloc /* IN/OUT */ +) +{ + return nfs_do_evalpath(path, (void*)flags, pathloc, 0 /*not forMake*/); +} + + +/* create a hard link */ + +static int nfs_link( + rtems_filesystem_location_info_t *to_loc, /* IN */ + rtems_filesystem_location_info_t *parent_loc, /* IN */ + const char *name /* IN */ +) +{ +NfsNode pNode; +nfsstat status; +NfsNode tNode = to_loc->node_access; + +#if DEBUG & DEBUG_SYSCALLS + fprintf(stderr,"Creating link '%s'\n",name); +#endif + + if ( !locIsNfs(parent_loc) ) { + errno = EXDEV; + return -1; + } + + pNode = parent_loc->node_access; + if ( tNode->nfs != pNode->nfs ) { + errno = EXDEV; + return -1; + } + memcpy(&SERP_ARGS(tNode).linkarg.to.dir, + &SERP_FILE(pNode), + sizeof(SERP_FILE(pNode))); + + SERP_ARGS(tNode).linkarg.to.name = (filename)name; + + if ( nfscall(tNode->nfs->server, + NFSPROC_LINK, + (xdrproc_t)xdr_linkargs, &SERP_FILE(tNode), + (xdrproc_t)xdr_nfsstat, &status) + || (NFS_OK != (errno = status)) + ) { +#if DEBUG & DEBUG_SYSCALLS + perror("nfs_link"); +#endif + return -1; + } + + return 0; + +} + +static int nfs_do_unlink( + rtems_filesystem_location_info_t *loc, /* IN */ + int proc +) +{ +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 + + if ( nfscall(nfs->server, + proc, + (xdrproc_t)xdr_diropargs, &node->args, + (xdrproc_t)xdr_nfsstat, &status) + || (NFS_OK != (errno = status)) + ) { +#if DEBUG & DEBUG_SYSCALLS + perror(name); +#endif + return -1; + } + + return 0; +} + +static int nfs_unlink( + rtems_filesystem_location_info_t *loc /* IN */ +) +{ + return nfs_do_unlink(loc, NFSPROC_REMOVE); +} + +static int nfs_chown( + 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); + +} + +/* Cleanup the FS private info attached to pathloc->node_access */ +static int nfs_freenode( + rtems_filesystem_location_info_t *pathloc /* IN */ +) +{ +Nfs nfs = ((NfsNode)pathloc->node_access)->nfs; + +#if DEBUG & DEBUG_COUNT_NODES + /* 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 + + /* never destroy the root node; it is released by the unmount + * code + */ + if (locIsRoot(pathloc)) { + unsigned long flags; + /* just adjust the references to the root node but + * don't really release it + */ + rtems_interrupt_disable(flags); + nfs->nodesInUse--; + rtems_interrupt_enable(flags); + } else { + nfsNodeDestroy(pathloc->node_access); + pathloc->node_access = 0; + } + return 0; +} + +/* 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. + */ + +#ifdef DECLARE_BODY +/* This routine is called when they try to mount something + * on top of THIS filesystem, i.e. if one of our directories + * is used as a mount point + */ +static int nfs_mount( + rtems_filesystem_mount_table_entry_t *mt_entry /* in */ +)DECLARE_BODY +#else +#define nfs_mount 0 +#endif + +#ifdef DECLARE_BODY +/* This op is called when they try to unmount a FS + * from a mountpoint managed by THIS FS. + */ +static int nfs_unmount( + rtems_filesystem_mount_table_entry_t *mt_entry /* in */ +)DECLARE_BODY +#else +#define nfs_unmount 0 +#endif + +#if 0 + +/* for reference (libio.h) */ + +struct rtems_filesystem_mount_table_entry_tt { + Chain_Node Node; + rtems_filesystem_location_info_t mt_point_node; + rtems_filesystem_location_info_t mt_fs_root; + int options; + void *fs_info; + + rtems_filesystem_limits_and_options_t pathconf_limits_and_options; + + /* + * When someone adds a mounted filesystem on a real device, + * this will need to be used. + * + * The best option long term for this is probably an open file descriptor. + */ + char *dev; +}; +#endif + + +/* This op is called as the last step of mounting this FS */ +STATIC int nfs_fsmount_me( + rtems_filesystem_mount_table_entry_t *mt_entry +) +{ +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 ( 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; + } + + assert( nfs = nfsCreate(nfsServer) ); + 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 + */ + assert( rootNode = nfsNodeCreate(nfs, &fhstat.fhstatus_u.fhs_fhandle) ); + + if ( updateAttr(rootNode, 1 /* force */) ) { + e = errno; + goto cleanup; + } + + /* looks good so far */ + + mt_entry->mt_fs_root.node_access = rootNode; + + rootNode = 0; + + mt_entry->mt_fs_root.ops = &nfs_fs_ops; + mt_entry->mt_fs_root.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 int 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; + +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_set_errno_and_return_minus_one(EBUSY); + } + + assert( 0 == buildIpAddr(&uid, &gid, 0, &saddr, &path) ); + + 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)); + errno = EIO; + return -1; + } + + nfsNodeDestroy(mt_entry->mt_fs_root.node_access); + mt_entry->mt_fs_root.node_access = 0; + + nfsDestroy(mt_entry->fs_info); + mt_entry->fs_info = 0; + + nfsGlob.num_mounted_fs--; +UNLOCK(nfsGlob.llock); + + return 0; +} + +/* OPTIONAL; may be NULL - BUT: CAUTION; mount() doesn't check + * for this handler to be present - a fs bug + * //NOTE: (10/25/2002) patch submitted and probably applied + */ +static rtems_filesystem_node_types_t nfs_node_type( + rtems_filesystem_location_info_t *pathloc /* in */ +) +{ +NfsNode node = pathloc->node_access; + + if (updateAttr(node, 0 /* only if old */)) + return -1; + + switch( SERP_ATTR(node).type ) { + default: + /* rtems has no value for 'unknown'; + */ + case NFNON: + case NFSOCK: + case NFBAD: + case NFFIFO: + break; + + + case NFREG: return RTEMS_FILESYSTEM_MEMORY_FILE; + case NFDIR: return RTEMS_FILESYSTEM_DIRECTORY; + + case NFBLK: + case NFCHR: return RTEMS_FILESYSTEM_DEVICE; + + case NFLNK: return RTEMS_FILESYSTEM_SYM_LINK; + } + return -1; +} + +static int nfs_mknod( + const char *path, /* IN */ + mode_t mode, /* IN */ + dev_t dev, /* IN */ + rtems_filesystem_location_info_t *pathloc /* IN/OUT */ +) +{ +rtems_clock_time_value now; +diropres res; +NfsNode node = pathloc->node_access; +mode_t type = S_IFMT & mode; + + if (type != S_IFDIR && type != S_IFREG) + rtems_set_errno_and_return_minus_one(ENOTSUP); + +#if DEBUG & DEBUG_SYSCALLS + fprintf(stderr,"nfs_mknod: creating %s\n", path); +#endif + + rtems_clock_get(RTEMS_CLOCK_GET_TIME_VALUE, &now); + + SERP_ARGS(node).createarg.name = (filename)path; + SERP_ARGS(node).createarg.attributes.mode = mode; + /* TODO: either use our uid or use the Nfs credentials */ + SERP_ARGS(node).createarg.attributes.uid = 0; + SERP_ARGS(node).createarg.attributes.gid = 0; + SERP_ARGS(node).createarg.attributes.size = 0; + SERP_ARGS(node).createarg.attributes.atime.seconds = now.seconds; + SERP_ARGS(node).createarg.attributes.atime.useconds = now.microseconds; + SERP_ARGS(node).createarg.attributes.mtime.seconds = now.seconds; + SERP_ARGS(node).createarg.attributes.mtime.useconds = now.microseconds; + + if ( nfscall( node->nfs->server, + NFSPROC_CREATE, + (xdrproc_t)xdr_createargs, &SERP_FILE(node), + (xdrproc_t)xdr_diropres, &res) + || (NFS_OK != (errno = res.status)) ) { +#if DEBUG & DEBUG_SYSCALLS + perror("nfs_mknod"); +#endif + return -1; + } + + return 0; +} + +static int nfs_utime( + 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( + rtems_filesystem_location_info_t *loc, /* IN */ + const char *link_name, /* IN */ + const char *node_name +) +{ +rtems_clock_time_value now; +nfsstat status; +NfsNode node = loc->node_access; + + +#if DEBUG & DEBUG_SYSCALLS + fprintf(stderr,"nfs_symlink: creating %s -> %s\n", link_name, node_name); +#endif + + rtems_clock_get(RTEMS_CLOCK_GET_TIME_VALUE, &now); + + SERP_ARGS(node).symlinkarg.name = (filename)link_name; + SERP_ARGS(node).symlinkarg.to = (nfspath) node_name; + + SERP_ARGS(node).symlinkarg.attributes.mode = S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO; + /* TODO */ + SERP_ARGS(node).symlinkarg.attributes.uid = 0; + SERP_ARGS(node).symlinkarg.attributes.gid = 0; + SERP_ARGS(node).symlinkarg.attributes.size = 0; + SERP_ARGS(node).symlinkarg.attributes.atime.seconds = now.seconds; + SERP_ARGS(node).symlinkarg.attributes.atime.useconds = now.microseconds; + SERP_ARGS(node).symlinkarg.attributes.mtime.seconds = now.seconds; + SERP_ARGS(node).symlinkarg.attributes.mtime.useconds = now.microseconds; + + if ( nfscall( node->nfs->server, + NFSPROC_SYMLINK, + (xdrproc_t)xdr_symlinkargs, &SERP_FILE(node), + (xdrproc_t)xdr_nfsstat, &status) + || (NFS_OK != (errno = status)) ) { +#if DEBUG & DEBUG_SYSCALLS + perror("nfs_symlink"); +#endif + return -1; + } + + return 0; +} + +static int nfs_do_readlink( + rtems_filesystem_location_info_t *loc, /* IN */ + strbuf *psbuf /* IN/OUT */ +) +{ +NfsNode node = loc->node_access; +Nfs nfs = node->nfs; +readlinkres_strbuf rr; +int wasAlloced; +int rval; + + rr.strbuf = *psbuf; + + wasAlloced = (0 == psbuf->buf); + + if ( (rval = nfscall(nfs->server, + NFSPROC_READLINK, + (xdrproc_t)xdr_nfs_fh, &SERP_FILE(node), + (xdrproc_t)xdr_readlinkres_strbuf, &rr)) ) { + if (wasAlloced) + xdr_free( (xdrproc_t)xdr_strbuf, (caddr_t)&rr.strbuf ); + } + + + if (NFS_OK != rr.status) { + if (wasAlloced) + xdr_free( (xdrproc_t)xdr_strbuf, (caddr_t)&rr.strbuf ); + rtems_set_errno_and_return_minus_one(rr.status); + } + + *psbuf = rr.strbuf; + + return 0; +} + +static int nfs_readlink( + rtems_filesystem_location_info_t *loc, /* IN */ + char *buf, /* OUT */ + size_t len +) +{ +strbuf sbuf; + sbuf.buf = buf; + sbuf.max = len; + + return nfs_do_readlink(loc, &sbuf); +} + +/* The semantics of this routine are: + * + * The caller submits a valid pathloc, i.e. it has + * an NfsNode attached to node_access. + * On return, pathloc points to the target node which + * may or may not be an NFS node. + * Hence, the original NFS node is released in either + * case: + * - link evaluation fails; pathloc points to no valid node + * - link evaluation success; pathloc points to a new valid + * node. If it's an NFS node, a new NfsNode will be attached + * to node_access... + */ + +#define LINKVAL_BUFLEN (MAXPATHLEN+1) +#define RVAL_ERR_BUT_DONT_FREENODE (-1) +#define RVAL_ERR_AND_DO_FREENODE ( 1) +#define RVAL_OK ( 0) + +static int nfs_eval_link( + rtems_filesystem_location_info_t *pathloc, /* IN/OUT */ + int flags /* IN */ +) +{ +rtems_filesystem_node_types_t type; +char *buf = malloc(LINKVAL_BUFLEN); +int rval = RVAL_ERR_AND_DO_FREENODE; + + if (!buf) { + errno = ENOMEM; + goto cleanup; + } + + /* in this loop, we must not use NFS specific ops as we might + * step out of our FS during the process... + * This algorithm should actually be performed by the + * generic's evaluate_path routine :-( + * + * Unfortunately, there is no way of finding the + * directory node who contains 'pathloc', however :-( + */ + do { + /* assume the generics have verified 'pathloc' to be + * a link... + */ + if ( !pathloc->ops->readlink_h ) { + errno = ENOTSUP; + goto cleanup; + } + + if ( pathloc->ops->readlink_h(pathloc, buf, LINKVAL_BUFLEN) ) { + goto cleanup; + } + +#if DEBUG & DEBUG_EVALPATH + fprintf(stderr, "link value is '%s'\n", buf); +#endif + + /* is the link value an absolute path ? */ + if ( DELIM != *buf ) { + /* NO; a relative path */ + + /* we must backup to the link's directory - we + * know only how to do that for NFS, however. + * In this special case, we can avoid recursion. + * Otherwise (i.e. if the link is on another FS), + * we must step into its eval_link_h(). + */ + if (locIsNfs(pathloc)) { + NfsNode node = pathloc->node_access; + int err; + + memcpy( &SERP_FILE(node), + &node->args.dir, + sizeof(node->args.dir) ); + + if (updateAttr(node, 1 /* force */)) + goto cleanup; + + if (SERP_ATTR(node).type != NFDIR) { + errno = ENOTDIR; + goto cleanup; + } + + pathloc->handlers = &nfs_dir_file_handlers; + + err = nfs_evalpath(buf, flags, pathloc); + + /* according to its semantics, + * nfs_evalpath cloned the node attached + * to pathloc. Hence we have to + * release the old one (referring to + * the link; the new clone has been + * updated and refers to the link _value_). + */ + nfsNodeDestroy(node); + + if (err) { + /* nfs_evalpath has set errno; + * pathloc->node_access has no + * valid node attached in this case + */ + rval = RVAL_ERR_BUT_DONT_FREENODE; + goto cleanup; + } + + } else { + if ( ! pathloc->ops->eval_link_h ) { + errno = ENOTSUP; + goto cleanup; + } + if (!pathloc->ops->eval_link_h(pathloc, flags)) { + /* FS is responsible for freeing its pathloc->node_access + * if necessary + */ + rval = RVAL_ERR_BUT_DONT_FREENODE; + goto cleanup; + } + } + } else { + /* link points to an absolute path '/xxx' */ + + /* release this node; filesystem_evaluate_path() will + * lookup a new one. + */ + rtems_filesystem_freenode(pathloc); + + if (rtems_filesystem_evaluate_path(buf, flags, pathloc, 1)) { + goto cleanup; + } + } + + if ( !pathloc->ops->node_type_h ) { + errno = ENOTSUP; + goto cleanup; + } + + type = pathloc->ops->node_type_h(pathloc); + + + /* I dont know what to do about hard links */ + } while ( RTEMS_FILESYSTEM_SYM_LINK == type ); + + rval = RVAL_OK; + +cleanup: + + free(buf); + + if (RVAL_ERR_AND_DO_FREENODE == rval) { + rtems_filesystem_freenode(pathloc); + return -1; + } + + return rval; +} + + +struct _rtems_filesystem_operations_table nfs_fs_ops = { + nfs_evalpath, /* MANDATORY */ + nfs_evalformake, /* MANDATORY; may set errno=ENOSYS and return -1 */ + nfs_link, /* OPTIONAL; may be NULL */ + nfs_unlink, /* OPTIONAL; may be NULL */ + nfs_node_type, /* OPTIONAL; may be NULL; BUG in mount - no test!! */ + nfs_mknod, /* OPTIONAL; may be NULL */ + nfs_chown, /* OPTIONAL; may be NULL */ + nfs_freenode, /* OPTIONAL; may be NULL; (release node_access) */ + nfs_mount, /* OPTIONAL; may be NULL */ + nfs_fsmount_me, /* OPTIONAL; may be NULL -- but this makes NO SENSE */ + nfs_unmount, /* OPTIONAL; may be NULL */ + nfs_fsunmount_me, /* OPTIONAL; may be NULL */ + nfs_utime, /* OPTIONAL; may be NULL */ + nfs_eval_link, /* OPTIONAL; may be NULL */ + nfs_symlink, /* OPTIONAL; may be NULL */ + nfs_readlink, /* OPTIONAL; may be NULL */ +}; + +/***************************************** + 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'. + *****************************************/ + + +#if 0 +/* from rtems/libio.h for convenience */ +struct rtems_libio_tt { + rtems_driver_name_t *driver; + off_t size; /* size of file */ + off_t offset; /* current offset into file */ + uint32_t flags; + rtems_filesystem_location_info_t pathinfo; + Objects_Id sem; + uint32_t data0; /* private to "driver" */ + void *data1; /* ... */ + void *file_info; /* used by file handlers */ + rtems_filesystem_file_handlers_r *handlers; /* type specific handlers */ +}; +#endif + +/* stateless NFS protocol makes this trivial */ +static int nfs_file_open( + rtems_libio_t *iop, + const char *pathname, + uint32_t flag, + uint32_t mode +) +{ + iop->file_info = 0; + 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 iop->file_info. + */ +static int nfs_dir_open( + rtems_libio_t *iop, + const char *pathname, + uint32_t flag, + uint32_t mode +) +{ +NfsNode node = iop->pathinfo.node_access; +DirInfo di; + + /* create a readdirargs object and copy the file handle; + * attach to the file_info. + */ + + di = (DirInfo) malloc(sizeof(*di)); + iop->file_info = 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; +} + +#define nfs_link_open 0 + +static int nfs_file_close( + rtems_libio_t *iop +) +{ + return 0; +} + +static int nfs_dir_close( + rtems_libio_t *iop +) +{ + free(iop->file_info); + iop->file_info = 0; + return 0; +} + +#define nfs_link_close 0 + +static ssize_t nfs_file_read( + rtems_libio_t *iop, + void *buffer, + size_t count +) +{ +readres rr; +NfsNode node = iop->pathinfo.node_access; +Nfs nfs = node->nfs; + + if (count > NFS_MAXDATA) + count = NFS_MAXDATA; + + SERP_ARGS(node).readarg.offset = iop->offset; + SERP_ARGS(node).readarg.count = count; + SERP_ARGS(node).readarg.totalcount = 0xdeadbeef; + + rr.readres_u.reply.data.data_val = buffer; + + if ( nfscall( nfs->server, + NFSPROC_READ, + (xdrproc_t)xdr_readargs, &SERP_FILE(node), + (xdrproc_t)xdr_readres, &rr) ) { + return -1; + } + + + if (NFS_OK != rr.status) { + rtems_set_errno_and_return_minus_one(rr.status); + } + +#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 rr.readres_u.reply.data.data_len; +} + +/* this is called by readdir() / getdents() */ +static ssize_t nfs_dir_read( + rtems_libio_t *iop, + void *buffer, + size_t count +) +{ +DirInfo di = iop->file_info; +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 + + if ( nfscall( + server, + NFSPROC_READDIR, + (xdrproc_t)xdr_readdirargs, &di->readdirargs, + (xdrproc_t)xdr_dir_info, di) ) { + return -1; + } + + + if (NFS_OK != di->status) { + rtems_set_errno_and_return_minus_one(di->status); + } + + return (char*)di->ptr - (char*)buffer; +} + +#define nfs_link_read 0 + +static ssize_t nfs_file_write( + rtems_libio_t *iop, + const void *buffer, + size_t count +) +{ +NfsNode node = iop->pathinfo.node_access; +Nfs nfs = node->nfs; +int e; + + if (count > NFS_MAXDATA) + count = NFS_MAXDATA; + + + SERP_ARGS(node).writearg.beginoffset = 0xdeadbeef; + if ( LIBIO_FLAGS_APPEND & iop->flags ) { + if ( updateAttr(node, 0) ) { + return -1; + } + SERP_ARGS(node).writearg.offset = SERP_ATTR(node).size; + } else { + SERP_ARGS(node).writearg.offset = iop->offset; + } + SERP_ARGS(node).writearg.totalcount = 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 + */ + + if ( nfscall( nfs->server, + NFSPROC_WRITE, + (xdrproc_t)xdr_writeargs, &SERP_FILE(node), + (xdrproc_t)xdr_attrstat, &node->serporid) ) { + return -1; + } + + + if (NFS_OK != (e=node->serporid.status) ) { + /* try at least to recover the current attributes */ + updateAttr(node, 1 /* force */); + rtems_set_errno_and_return_minus_one(e); + } + + node->age = nowSeconds(); + + return count; +} + +#define nfs_dir_write 0 +#define nfs_link_write 0 + +/* IOCTL is unneeded/unsupported */ +#ifdef DECLARE_BODY +static int nfs_file_ioctl( + rtems_libio_t *iop, + uint32_t command, + void *buffer +)DECLARE_BODY +#else +#define nfs_file_ioctl 0 +#define nfs_dir_ioctl 0 +#define nfs_link_ioctl 0 +#endif + +static int nfs_file_lseek( + rtems_libio_t *iop, + off_t length, + int whence +) +{ +#if DEBUG & DEBUG_SYSCALLS + fprintf(stderr, + "lseek to %i (length %i, whence %i)\n", + iop->offset, + length, + whence); +#endif + if ( SEEK_END == whence ) { + /* rtems (4.6.2) libcsupport code 'lseek' uses iop->size to + * compute the offset. We don't want to track the file size + * by updating 'iop->size' constantly. + * Since lseek is the only place using iop->size, we work + * around this by tweaking the offset here... + */ + NfsNode node = iop->pathinfo.node_access; + fattr *fa = &SERP_ATTR(node); + + if (updateAttr(node, 0 /* only if old */)) { + return -1; + } + iop->offset = fa->size; + } + + /* this is particularly easy :-) */ + return iop->offset; +} + +static int nfs_dir_lseek( + rtems_libio_t *iop, + off_t length, + int whence +) +{ +DirInfo di = iop->file_info; + + /* we don't support anything other than + * rewinding + */ + if (SEEK_SET != whence || 0 != length) { + errno = ENOTSUP; + return -1; + } + + /* rewind cookie */ + memset( &di->readdirargs.cookie, + 0, + sizeof(di->readdirargs.cookie) ); + + di->eofreached = FALSE; + + return iop->offset; +} + +#define nfs_link_lseek 0 + +#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( + 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) +{ + +rtems_clock_time_value now; +nfstime nfsnow, t; +int e; +u_int mode; + + if (updateAttr(node, 0 /* only if old */)) + return -1; + + rtems_clock_get(RTEMS_CLOCK_GET_TIME_VALUE, &now); + + /* TODO: add rtems EPOCH - UNIX EPOCH seconds */ + nfsnow.seconds = now.seconds; + nfsnow.useconds = now.microseconds; + + /* 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; + + if ( nfscall( node->nfs->server, + NFSPROC_SETATTR, + (xdrproc_t)xdr_sattrargs, &SERP_FILE(node), + (xdrproc_t)xdr_attrstat, &node->serporid) ) { +#if DEBUG & DEBUG_SYSCALLS + fprintf(stderr, + "nfs_sattr (mask 0x%08x): %s", + mask, + strerror(errno)); +#endif + return -1; + } + + if (NFS_OK != (e=node->serporid.status) ) { +#if DEBUG & DEBUG_SYSCALLS + fprintf(stderr,"nfs_sattr: %s\n",strerror(e)); +#endif + /* try at least to recover the current attributes */ + updateAttr(node, 1 /* force */); + rtems_set_errno_and_return_minus_one(e); + } + + node->age = nowSeconds(); + + return 0; +} + + +/* common for file/dir/link */ +static int nfs_fchmod( + rtems_filesystem_location_info_t *loc, + mode_t mode +) +{ +sattr arg; + + arg.mode = mode; + return nfs_sattr(loc->node_access, &arg, SATTR_MODE); + +} + +/* 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; + + 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); +} + +#define nfs_dir_ftruncate 0 +#define nfs_link_ftruncate 0 + +/* not implemented */ +#ifdef DECLARE_BODY +static int nfs_file_fpathconf( + rtems_libio_t *iop, + int name +)DECLARE_BODY +#else +#define nfs_file_fpathconf 0 +#define nfs_dir_fpathconf 0 +#define nfs_link_fpathconf 0 +#endif + +/* unused */ +#ifdef DECLARE_BODY +static int nfs_file_fsync( + rtems_libio_t *iop +)DECLARE_BODY +#else +#define nfs_file_fsync 0 +#define nfs_dir_fsync 0 +#define nfs_link_fsync 0 +#endif + +/* unused */ +#ifdef DECLARE_BODY +static int nfs_file_fdatasync( + rtems_libio_t *iop +)DECLARE_BODY +#else +#define nfs_file_fdatasync 0 +#define nfs_dir_fdatasync 0 +#define nfs_link_fdatasync 0 +#endif + +/* unused */ +#ifdef DECLARE_BODY +static int nfs_file_fcntl( + int cmd, + rtems_libio_t *iop +)DECLARE_BODY +#else +#define nfs_file_fcntl 0 +#define nfs_dir_fcntl 0 +#define nfs_link_fcntl 0 +#endif + +/* files and symlinks are removed + * by the common nfs_unlink() routine. + * NFS has a different NFSPROC_RMDIR + * call, though... + */ +static int nfs_dir_rmnod( + rtems_filesystem_location_info_t *pathloc /* IN */ +) +{ + return nfs_do_unlink(pathloc, NFSPROC_RMDIR); +} + +/* the file handlers table */ +static +struct _rtems_filesystem_file_handlers_r nfs_file_file_handlers = { + nfs_file_open, /* OPTIONAL; may be NULL */ + nfs_file_close, /* OPTIONAL; may be NULL */ + nfs_file_read, /* OPTIONAL; may be NULL */ + nfs_file_write, /* OPTIONAL; may be NULL */ + nfs_file_ioctl, /* OPTIONAL; may be NULL */ + nfs_file_lseek, /* OPTIONAL; may be NULL */ + nfs_fstat, /* OPTIONAL; may be NULL */ + nfs_fchmod, /* OPTIONAL; may be NULL */ + nfs_file_ftruncate, /* OPTIONAL; may be NULL */ + nfs_file_fpathconf, /* OPTIONAL; may be NULL - UNUSED */ + nfs_file_fsync, /* OPTIONAL; may be NULL */ + nfs_file_fdatasync, /* OPTIONAL; may be NULL */ + nfs_file_fcntl, /* OPTIONAL; may be NULL */ + nfs_unlink, /* OPTIONAL; may be NULL */ +}; + +/* the directory handlers table */ +static +struct _rtems_filesystem_file_handlers_r nfs_dir_file_handlers = { + nfs_dir_open, /* OPTIONAL; may be NULL */ + nfs_dir_close, /* OPTIONAL; may be NULL */ + nfs_dir_read, /* OPTIONAL; may be NULL */ + nfs_dir_write, /* OPTIONAL; may be NULL */ + nfs_dir_ioctl, /* OPTIONAL; may be NULL */ + nfs_dir_lseek, /* OPTIONAL; may be NULL */ + nfs_fstat, /* OPTIONAL; may be NULL */ + nfs_fchmod, /* OPTIONAL; may be NULL */ + nfs_dir_ftruncate, /* OPTIONAL; may be NULL */ + nfs_dir_fpathconf, /* OPTIONAL; may be NULL - UNUSED */ + nfs_dir_fsync, /* OPTIONAL; may be NULL */ + nfs_dir_fdatasync, /* OPTIONAL; may be NULL */ + nfs_dir_fcntl, /* OPTIONAL; may be NULL */ + nfs_dir_rmnod, /* OPTIONAL; may be NULL */ +}; + +/* the link handlers table */ +static +struct _rtems_filesystem_file_handlers_r nfs_link_file_handlers = { + nfs_link_open, /* OPTIONAL; may be NULL */ + nfs_link_close, /* OPTIONAL; may be NULL */ + nfs_link_read, /* OPTIONAL; may be NULL */ + nfs_link_write, /* OPTIONAL; may be NULL */ + nfs_link_ioctl, /* OPTIONAL; may be NULL */ + nfs_link_lseek, /* OPTIONAL; may be NULL */ + nfs_fstat, /* OPTIONAL; may be NULL */ + nfs_fchmod, /* OPTIONAL; may be NULL */ + nfs_link_ftruncate, /* OPTIONAL; may be NULL */ + nfs_link_fpathconf, /* OPTIONAL; may be NULL - UNUSED */ + nfs_link_fsync, /* OPTIONAL; may be NULL */ + nfs_link_fdatasync, /* OPTIONAL; may be NULL */ + nfs_link_fcntl, /* OPTIONAL; may be NULL */ + nfs_unlink, /* OPTIONAL; may be NULL */ +}; + +/* 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)) + fprintf(f,"<UNABLE TO LOOKUP MOUNTPOINT>\n"); + else + fprintf(f,"%s\n",mntpt); + } + + UNLOCK(nfsGlob.llock); + + free(mntpt); + return 0; +} + +/* 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) +{ +rtems_filesystem_mount_table_entry_t *mtab; +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(*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(&mtab, + &nfs_fs_ops, + RTEMS_FILESYSTEM_READ_WRITE, + dev, + mntpoint)) { + perror("nfsMount - mount"); + goto cleanup; + } + + rval = 0; + +cleanup: + free(dev); + return rval; +} + +/* 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_id 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; + + rtems_filesystem_current = *(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 = old; + } + rtems_semaphore_release(rpa->sync); + rtems_task_delete(RTEMS_SELF); +} + + +/* 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; + arg.sync = 0; + + status = rtems_semaphore_create( + rtems_build_name('r','e','s','s'), + 0, + RTEMS_SIMPLE_BINARY_SEMAPHORE, + 0, + &arg.sync); + + if (RTEMS_SUCCESSFUL != status) + goto cleanup; + + 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_semaphore_obtain(arg.sync, RTEMS_WAIT, RTEMS_NO_TIMEOUT); + + status = arg.status; + +cleanup: + if (arg.sync) + rtems_semaphore_delete(arg.sync); + + return status; +} + +int +nfsSetTimeout(uint32_t timeout_ms) +{ +rtems_interrupt_level k; +uint32_t s,us; + + if ( timeout_ms > 100000 ) { + /* out of range */ + return -1; + } + + s = timeout_ms/1000; + us = (timeout_ms % 1000) * 1000; + + rtems_interrupt_disable(k); + _nfscalltimeout.tv_sec = s; + _nfscalltimeout.tv_usec = us; + rtems_interrupt_enable(k); + + return 0; +} + +uint32_t +nfsGetTimeout() +{ +rtems_interrupt_level k; +uint32_t s,us; + rtems_interrupt_disable(k); + s = _nfscalltimeout.tv_sec; + us = _nfscalltimeout.tv_usec; + rtems_interrupt_enable(k); + return s*1000 + us/1000; +} |