Ryan Long 34862dd273 libbsd nfs.c: Change filesystem utime_h handler to utimens_h
Changed nfs_utime() to nfs_utimens(), changed the arguments to use
a timespec array instead of individual variables for access and
modified time.

Updates #4400
2021-05-28 16:32:42 -05:00

3219 lines
72 KiB
C

/**
* @file
*
* @brief NFS Client Implementation for RTEMS
* @ingroup libfs
*
* 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 <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 "nfs_prot.h"
#include "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 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)
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 / 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)
#define SERP_ATTR(node) ((node)->serporid.serporid.attributes)
#define SERP_FILE(node) ((node)->serporid.serporid.file)
typedef struct serporidok {
fattr attributes;
union {
nfs_fh file;
diropargs diroparg;
sattrargs sattrarg;
readargs readarg;
writeargs writearg;
createargs createarg;
renameargs renamearg;
linkargs linkarg;
symlinkargs symlinkarg;
readdirargs readdirarg;
};
} serporidok;
/*
* The nfsstat is an enum, so has an integer alignment. The serporid contains
* pointers, so has at least a pointer alignment. The packed attribute ensures
* that there is no gap between the status and serporid members on 64-bit
* targets.
*/
typedef struct serporid {
nfsstat status;
serporidok serporid;
} RTEMS_PACKED 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))
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_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;
/* 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 = {0, 0, 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, bool verbose)
{
static int initialised = 0;
entry dummy;
rtems_status_code status;
if (initialised)
return 0;
initialised = 1;
if (verbose)
fprintf(stderr,
"RTEMS-NFS, " \
"Till Straumann, Stanford/SLAC/SSRL 2002, " \
"See LICENSE 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;
/* 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;
}
status = rtems_semaphore_create(
rtems_build_name('N','F','S','l'),
1,
MUTEX_ATTRIBUTES,
0,
&nfsGlob.llock);
if (status != RTEMS_SUCCESSFUL) {
goto cleanup;
}
status = rtems_semaphore_create(
rtems_build_name('N','F','S','m'),
1,
MUTEX_ATTRIBUTES,
0,
&nfsGlob.lock);
if (status != RTEMS_SUCCESSFUL) {
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;
if (nfsGlob.llock != 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;
}
}
if (nfsGlob.lock != 0) {
rtems_semaphore_delete(nfsGlob.lock);
nfsGlob.lock = 0;
}
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;
}
if (nfsGlob.llock != 0) {
rtems_semaphore_delete(nfsGlob.llock);
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 = 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;
const char *options = (const char*) data;
bool verbose = false;
if (options != NULL)
verbose = strstr(options, "-v") != NULL;
if (rpcUdpInit (verbose) < 0) {
fprintf (stderr, "error: initialising RPC\n");
return -1;
}
if (nfsInit(0, 0, verbose) != 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.where.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_utimens(
const rtems_filesystem_location_info_t *pathloc, /* IN */
struct timespec times[2] /* IN */
)
{
sattr arg;
/* TODO: add rtems EPOCH - UNIX EPOCH seconds */
arg.atime.seconds = times[0].tv_sec;
arg.atime.useconds = times[0].tv_nsec / 1000;
arg.mtime.seconds = times[1].tv_sec;
arg.mtime.useconds = times[1].tv_nsec / 1000;
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.from.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.from.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,
.utimens_h = nfs_utimens,
.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 ( LIBIO_FLAGS_APPEND & iop->flags ) {
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,
.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,
.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,
.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_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->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_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_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;
}