//==========================================================================
//
//      ramfs.c
//
//      RAM file system
//
//==========================================================================
//####ECOSGPLCOPYRIGHTBEGIN####
// -------------------------------------------
// This file is part of eCos, the Embedded Configurable Operating System.
// Copyright (C) 1998, 1999, 2000, 2001, 2002 Red Hat, Inc.
//
// eCos is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 2 or (at your option) any later version.
//
// eCos is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
// for more details.
//
// You should have received a copy of the GNU General Public License along
// with eCos; if not, write to the Free Software Foundation, Inc.,
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
//
// As a special exception, if other files instantiate templates or use macros
// or inline functions from this file, or you compile this file and link it
// with other works to produce a work based on this file, this file does not
// by itself cause the resulting work to be covered by the GNU General Public
// License. However the source code for this file must still be made available
// in accordance with section (3) of the GNU General Public License.
//
// This exception does not invalidate any other reasons why a work based on
// this file might be covered by the GNU General Public License.
//
// Alternative licenses for eCos may be arranged by contacting Red Hat, Inc.
// at http://sources.redhat.com/ecos/ecos-license/
// -------------------------------------------
//####ECOSGPLCOPYRIGHTEND####
//==========================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):           nickg
// Contributors:        nickg
// Date:                2000-07-25
// Purpose:             RAM file system
// Description:         This is a RAM filesystem for eCos. It attempts to
//                      provide full POSIX-compatible filesystem behaviour
//                      while at the same time being efficient in terms of
//                      time and space used.
//                      
//
//####DESCRIPTIONEND####
//
//==========================================================================
// 
// General Description
// ===================
// 
// This is an implementation of a RAM filesystem for eCos. Its goal is
// to provide a working example of a filesystem that provides most of
// the required POSIX functionality. And obviously it may also be
// useful in its own right.
//
//
// Nodes
// -----
//
// All files and directories are represented by node objects. Each
// ramfs_node structure contains the following fields:
//
// mode   - Node type, file or directory.
// refcnt - Number of references to this node. For files each open counts as
//          a reference and for directories a node is referenced when it is made
//          current directory, or is opened for reading.
// nlink  - Number of links to this node. Each directory entry that references
//          this node is a link. 
// size   - Size of the data in this node in bytes.
// atime  - Last time this node was accessed.
// mtime  - Last time the data in this node was modified.
// ctime  - Last time the status information in this node was changed.
//
// The data storage in a node is controlled by the configuration and
// can take two forms. These will be described later.
//          
// Directories
// -----------
//
// A directory is a node whose data is a list of directory entries. To
// simplify management of these, long directory entries are split into
// a chain of fixed size ramfs_dirent structures. These contain the
// following fields:
//
// node    - Pointer to node referenced by this entry. This is present in
//           every directory entry fragment
// inuse   - Set to 1 if this entry is in use, zero if it is free.
// first   - Set to 1 if this is the first fragment of a directory entry.
// last    - Set to 1 if this is the last fragment of a directory entry.
// namelen - The size of the whole file name.
// fraglen - The number of bytes of the file name that are stored in this
//           fragment.
// next    - The offset of the next fragment of this directory entry.
// name    - The characters of the fragment of the file name stored in this
//           entry.
//
// Small file names are stored in a single fragment. Longer names are
// stored in a chain of fragments.
//
// Data Storage
// ------------
//
// Two data storage mechanisms may be configured, the SIMPLE and the
// BLOCKS mechanisms.
//
// SIMPLE Data Storage
// ~~~~~~~~~~~~~~~~~~~
//
// This mechanism simply uses malloc() and free() to allocate the
// memory for both nodes and file data. File data is stored in a
// single malloced vector that is realloced as necessary to acquire
// more space. 
//
// The advantage of this approach is that the RAM filesystem only uses
// as much memory as it needs, the rest is available for use by other
// components. It also requires much simpler data structures and code
// in the filesystem to manage. However, if any files get to be a
// significant proportion of the size of the heap, there is the danger
// that fragmentation will prevent any further extension of some
// files, even if there is enough memory in total. It also requires an
// implementation of malloc() to be present. If this needs to be
// present for other components,then this is not a significant
// overhead, but including it just for use by this filesystem
// represents a major addition of code and data structures.
//
//
// BLOCKS Data Storage
// ~~~~~~~~~~~~~~~~~~~
//
// This mechanism divides the memory used for file storage into fixed
// sized blocks. These blocks may either be allocated using
// malloc()/free(), or may be obtained from a array of blocks reserved
// for the purpose. Configuration allows the block size to be
// selected, as well as the allocation mechanism, and in the case of a
// block array, whether it is defined here or by an external
// component.
// 
// Data storage in nodes is arranges in three arrays of pointers to
// blocks. The first array points directly to data blocks, the second
// to blocks which themselves contain pointers to data blocks, and the
// third to blocks which contain pointers to blocks which contain
// pointers to data blocks. In the default configuration These last
// two arrays have only one element each.
// 
// The following shows how the data is arranged in a fully populated
// file with a 256 byte block size using the default configuration.
// 
//      Node
// ~            ~
// |            |
// |            |
// +------------+
// |     *------+--------> data block 0
// +------------+
// |     *------+--------> data block 1
// +------------+
// |     *------+--------> data block 2
// +------------+
// |     *------+--------> data block 3
// +------------+
// |     *------+--------> data block 4
// +------------+
// |     *------+--------> data block 5
// +------------+
// |     *------+--------> data block 6
// +------------+
// |     *------+--------> data block 7
// +------------+
// |     *------+--------> +------------+
// +------------+          |     *------+--------> data block 8
// |     *------+----+     +------------+
// +------------+    |     |            |
//                   |     ~            ~
//                   |     |            |
//                   |     +------------+
//                   |     |     *------+--------> data block 71
//                   |     +------------+
//                   |     
//                   +---->+------------+         +------------+
//                         |     *------+-------->|     *------+---->data block 72
//                         +------------+         +------------+
//                         |            |         |            |
//                         ~            ~         ~            ~
//                         |            |         |            |
//                         +------------+         +------------+
//                         |     *------+---+     |     *------+----> data block 135
//                         +------------+   |     +------------+
//                                          |
//                                          |     +------------+
//                                          +---->|     *------+----> data block 4104
//                                                +------------+
//                                                |            |
//                                                ~            ~
//                                                |            |
//                                                +------------+
//                                                |     *------+----> data block 4167
//                                                +------------+
// 
// 
//
// The advantages of this approach are that, first, memory usage is
// divided into discreet fixed size blocks which are easier to
// manage. When using malloc() to allocate them, they will fit into
// any free memory of at least the right size. Using the block array
// option removes the need to have a malloc() implementation at all.
//
// The disadvantages of this mechanism are that, first, when using
// malloc() to allocate blocks, the per-block memory allocator
// overhead is paid for each block, rather than per file. This may
// result in less memory overall being available for data
// storage. When using the block array, it is permanently reserved for
// use by the ram filesystem, and is not available for use by other
// components.
//
//==========================================================================

#include <pkgconf/system.h>
#include <pkgconf/hal.h>
#include <pkgconf/kernel.h>
#include <pkgconf/io_fileio.h>
#include <pkgconf/fs_ram.h>

#include <cyg/kernel/ktypes.h>         // base kernel types
#include <cyg/infra/cyg_trac.h>        // tracing macros
#include <cyg/infra/cyg_ass.h>         // assertion macros

#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <dirent.h>

#include <stdlib.h>
#include <string.h>

#include <cyg/fileio/fileio.h>

#include <cyg/kernel/kapi.h>
#include <cyg/infra/diag.h>

//==========================================================================
// Sizes derived from configuration

// -------------------------------------------------------------------------
// Simple malloc based allocator parameters

#ifdef CYGPKG_FS_RAM_SIMPLE

#define RAMFS_FILESIZE_MAX      UINT_MAX

#else

// -------------------------------------------------------------------------
// Block allocator parameters

// The number of nodes per block
#define RAMFS_NODES_PER_BLOCK (CYGNUM_RAMFS_BLOCK_SIZE/sizeof(ramfs_node))

// The number of indirect pointers that can be stored in a single data block
#define RAMFS_INDIRECT_PER_BLOCK (CYGNUM_RAMFS_BLOCK_SIZE/sizeof(ramfs_block *))

// The number of directory entries that can be stored in a single data block
#define RAMFS_DIRENT_PER_BLOCK  (CYGNUM_RAMFS_BLOCK_SIZE/sizeof(ramfs_dirent))

// Number of bytes contained in a one level indirect block
#define RAMFS_INDIRECT1_BLOCK_EXTENT (RAMFS_INDIRECT_PER_BLOCK* \
                                      CYGNUM_RAMFS_BLOCK_SIZE)

// number of bytes contained in a two level indirect block
#define RAMFS_INDIRECT2_BLOCK_EXTENT (RAMFS_INDIRECT_PER_BLOCK* \
                                      RAMFS_INDIRECT_PER_BLOCK* \
                                      CYGNUM_RAMFS_BLOCK_SIZE)

// The maximum data offset for data directly accessed from the node
#define RAMFS_DIRECT_MAX        (CYGNUM_RAMFS_BLOCKS_DIRECT*CYGNUM_RAMFS_BLOCK_SIZE)

// The maximum data offset for data accessed from the single level indirect blocks
#define RAMFS_INDIRECT1_MAX     (RAMFS_DIRECT_MAX+                      \
                                 (CYGNUM_RAMFS_BLOCKS_INDIRECT1*        \
                                  RAMFS_INDIRECT1_BLOCK_EXTENT))

// The maximum data offset for data accessed from the two level indirect blocks
#define RAMFS_INDIRECT2_MAX     (RAMFS_INDIRECT1_MAX+                   \
                                 (CYGNUM_RAMFS_BLOCKS_INDIRECT2*        \
                                  RAMFS_INDIRECT2_BLOCK_EXTENT))

// The maximum size of a file
#define RAMFS_FILESIZE_MAX      RAMFS_INDIRECT2_MAX

#endif

//==========================================================================
// Forward definitions

// Filesystem operations
static int ramfs_mount    ( cyg_fstab_entry *fste, cyg_mtab_entry *mte );
static int ramfs_umount   ( cyg_mtab_entry *mte );
static int ramfs_open     ( cyg_mtab_entry *mte, cyg_dir dir, const char *name,
                             int mode,  cyg_file *fte );
static int ramfs_unlink   ( cyg_mtab_entry *mte, cyg_dir dir, const char *name );
static int ramfs_mkdir    ( cyg_mtab_entry *mte, cyg_dir dir, const char *name );
static int ramfs_rmdir    ( cyg_mtab_entry *mte, cyg_dir dir, const char *name );
static int ramfs_rename   ( cyg_mtab_entry *mte, cyg_dir dir1, const char *name1,
                             cyg_dir dir2, const char *name2 );
static int ramfs_link     ( cyg_mtab_entry *mte, cyg_dir dir1, const char *name1,
                             cyg_dir dir2, const char *name2, int type );
static int ramfs_opendir  ( cyg_mtab_entry *mte, cyg_dir dir, const char *name,
                             cyg_file *fte );
static int ramfs_chdir    ( cyg_mtab_entry *mte, cyg_dir dir, const char *name,
                             cyg_dir *dir_out );
static int ramfs_stat     ( cyg_mtab_entry *mte, cyg_dir dir, const char *name,
                             struct stat *buf);
static int ramfs_getinfo  ( cyg_mtab_entry *mte, cyg_dir dir, const char *name,
                             int key, void *buf, int len );
static int ramfs_setinfo  ( cyg_mtab_entry *mte, cyg_dir dir, const char *name,
                             int key, void *buf, int len );

// File operations
static int ramfs_fo_read      (struct CYG_FILE_TAG *fp, struct CYG_UIO_TAG *uio);
static int ramfs_fo_write     (struct CYG_FILE_TAG *fp, struct CYG_UIO_TAG *uio);
static int ramfs_fo_lseek     (struct CYG_FILE_TAG *fp, off_t *pos, int whence );
static int ramfs_fo_ioctl     (struct CYG_FILE_TAG *fp, CYG_ADDRWORD com,
                                CYG_ADDRWORD data);
static int ramfs_fo_fsync     (struct CYG_FILE_TAG *fp, int mode );        
static int ramfs_fo_close     (struct CYG_FILE_TAG *fp);
static int ramfs_fo_fstat     (struct CYG_FILE_TAG *fp, struct stat *buf );
static int ramfs_fo_getinfo   (struct CYG_FILE_TAG *fp, int key, void *buf, int len );
static int ramfs_fo_setinfo   (struct CYG_FILE_TAG *fp, int key, void *buf, int len );

// Directory operations
static int ramfs_fo_dirread      (struct CYG_FILE_TAG *fp, struct CYG_UIO_TAG *uio);
static int ramfs_fo_dirlseek     (struct CYG_FILE_TAG *fp, off_t *pos, int whence );


//==========================================================================
// Filesystem table entries

// -------------------------------------------------------------------------
// Fstab entry.
// This defines the entry in the filesystem table.
// For simplicity we use _FILESYSTEM synchronization for all accesses since
// we should never block in any filesystem operations.

FSTAB_ENTRY( ramfs_fste, "ramfs", 0,
             CYG_SYNCMODE_FILE_FILESYSTEM|CYG_SYNCMODE_IO_FILESYSTEM,
             ramfs_mount,
             ramfs_umount,
             ramfs_open,
             ramfs_unlink,
             ramfs_mkdir,
             ramfs_rmdir,
             ramfs_rename,
             ramfs_link,
             ramfs_opendir,
             ramfs_chdir,
             ramfs_stat,
             ramfs_getinfo,
             ramfs_setinfo);

// -------------------------------------------------------------------------
// File operations.
// This set of file operations are used for normal open files.

static cyg_fileops ramfs_fileops =
{
    ramfs_fo_read,
    ramfs_fo_write,
    ramfs_fo_lseek,
    ramfs_fo_ioctl,
    cyg_fileio_seltrue,
    ramfs_fo_fsync,
    ramfs_fo_close,
    ramfs_fo_fstat,
    ramfs_fo_getinfo,
    ramfs_fo_setinfo
};

// -------------------------------------------------------------------------
// Directory file operations.
// This set of operations are used for open directories. Most entries
// point to error-returning stub functions. Only the read, lseek and
// close entries are functional.

static cyg_fileops ramfs_dirops =
{
    ramfs_fo_dirread,
    (cyg_fileop_write *)cyg_fileio_enosys,
    ramfs_fo_dirlseek,
    (cyg_fileop_ioctl *)cyg_fileio_enosys,
    cyg_fileio_seltrue,
    (cyg_fileop_fsync *)cyg_fileio_enosys,
    ramfs_fo_close,
    (cyg_fileop_fstat *)cyg_fileio_enosys,
    (cyg_fileop_getinfo *)cyg_fileio_enosys,
    (cyg_fileop_setinfo *)cyg_fileio_enosys
};

//==========================================================================
// Data typedefs
// Some forward typedefs for the main data structures.

struct ramfs_node;
typedef struct ramfs_node ramfs_node;

struct ramfs_dirent;
typedef struct ramfs_dirent ramfs_dirent;

#ifndef CYGPKG_FS_RAM_SIMPLE

typedef cyg_uint8 ramfs_block[CYGNUM_RAMFS_BLOCK_SIZE];

#endif

//==========================================================================
// File and directory node
// This data structure represents a file or directory.

struct ramfs_node
{
    mode_t              mode;           // node type
    cyg_ucount32        refcnt;         // open file/current dir references
    nlink_t             nlink;          // number of links to this node
    size_t              size;           // size of file in bytes
    time_t              atime;          // last access time
    time_t              mtime;          // last modified time
    time_t              ctime;          // last changed status time

#ifdef CYGPKG_FS_RAM_SIMPLE

    // The data storage in this case consists of a single
    // malloced memory block, together with its size.
    
    size_t              datasize;       // size of data block
    cyg_uint8           *data;          // malloced data buffer

#else

    // The data storage in this case consists of arrays of pointers
    // to data blocks. 
    
#if CYGNUM_RAMFS_BLOCKS_DIRECT > 0
    // Directly accessible blocks from the inode.
    ramfs_block         *direct[CYGNUM_RAMFS_BLOCKS_DIRECT];
#endif
#if  CYGNUM_RAMFS_BLOCKS_INDIRECT1 > 0
    // Single level indirection
    ramfs_block         **indirect1[CYGNUM_RAMFS_BLOCKS_INDIRECT1];
#endif
#if  CYGNUM_RAMFS_BLOCKS_INDIRECT2 > 0
    // Two level indirection
    ramfs_block         ***indirect2[CYGNUM_RAMFS_BLOCKS_INDIRECT2];
#endif

#endif
    
};

//==========================================================================
// Directory entry.
// Fixed sized entry containing a fragment of the name of a file/directory.

struct ramfs_dirent
{
    ramfs_node          *node;          // pointer to node
    unsigned int        inuse:1,        // entry in use?
                        first:1,        // first directory entry fragment?
                        last:1,         // last directory entry fragment?
                        namelen:8,      // bytes in whole name
                        fraglen:8;      // bytes in name fragment
    off_t               next;           // offset of next dirent

    // Name fragment, fills rest of entry.
    char                name[CYGNUM_RAMFS_DIRENT_SIZE-
                             sizeof(ramfs_node *)-
                             sizeof( cyg_uint32)-
                             sizeof(off_t)];
};

//==========================================================================
// Directory search data
// Parameters for a directory search. The fields of this structure are
// updated as we follow a pathname through the directory tree.

struct ramfs_dirsearch
{
    ramfs_node          *dir;           // directory to search
    const char          *path;          // path to follow
    ramfs_node          *node;          // Node found
    const char          *name;          // last name fragment used
    int                 namelen;        // name fragment length
    cyg_bool            last;           // last name in path?
};

typedef struct ramfs_dirsearch ramfs_dirsearch;

//==========================================================================
// Forward defs

static int del_direntry( ramfs_node *dir, const char *name, int namelen );


//==========================================================================
// Block array
// This is used for block allocation when malloc is not being used.

#ifdef CYGPKG_FS_RAM_BLOCKS_ARRAY

# ifdef CYGPKG_FS_RAM_BLOCKS_ARRAY_EXTERN

// Array is defined externally with a user-supplied name

__externC ramfs_block CYGPKG_FS_RAM_BLOCKS_ARRAY_NAME[CYGNUM_FS_RAM_BLOCKS_ARRAY_SIZE];

// Translate into a usable common name
#define ramfs_block_array CYGPKG_FS_RAM_BLOCKS_ARRAY_NAME

# else

// Array is defined here

static ramfs_block cyg_ramfs_block_array[CYGNUM_FS_RAM_BLOCKS_ARRAY_SIZE];

#define ramfs_block_array cyg_ramfs_block_array

# endif

// Pointer to list of free blocks
static ramfs_block *block_free_list = NULL;

#endif

//==========================================================================
// Block allocation

#ifndef CYGPKG_FS_RAM_SIMPLE

// -------------------------------------------------------------------------
// block_init()
// Initialize the block allocator by chaining them all together on
// block_free_list.

#ifdef CYGPKG_FS_RAM_BLOCKS_ARRAY

static void block_init()
{
    static cyg_bool initialized = false;
    int i;

    if( !initialized )
    {
        for( i = 0; i < CYGNUM_FS_RAM_BLOCKS_ARRAY_SIZE; i++ )
        {
            ramfs_block *b = &ramfs_block_array[i];
            *(ramfs_block **)b = block_free_list;
            block_free_list = b;
        }
        initialized = true;
    }
}

#endif

// -------------------------------------------------------------------------
// block_alloc()
// Allocate a block for data storage.
// If we have a block array, just pick the first off the free list.
// If we are mallocing, call malloc() to get it.

static ramfs_block *block_alloc(void)
{
    ramfs_block *b;

#ifdef CYGPKG_FS_RAM_BLOCKS_ARRAY

    block_init();       // Check blocks are initialized

    // pick first block off free list.
    b = block_free_list;

    // and advance list
    if( b != NULL )
        block_free_list = *(ramfs_block **)b;

#else

    b = malloc(CYGNUM_RAMFS_BLOCK_SIZE);

#endif

    // Clear the block to zero if it was allocated
    if( b != NULL )
        memset( b, 0, CYGNUM_RAMFS_BLOCK_SIZE );

    return b;

}

// -------------------------------------------------------------------------
// block_free()
// Free a block. Depending on the configuration send it back to the
// heap or put it back on free list.

static void block_free( ramfs_block *b )
{
#ifdef CYGPKG_FS_RAM_BLOCKS_ARRAY

    // Put the block back on the free list
    
    *(ramfs_block **)b = block_free_list;
    block_free_list = b;
    
#else    

    // Call free() to return it to the memory pool
    
    free( b );

#endif
}

#endif

//==========================================================================
// Node buffer management
// There are two versions of this, one for the _SIMPLE variant and one for
// the _BLOCKS variant. In both cases the interface to this code is via the
// findbuffer_node() and freebuffer_node() functions.

#ifdef CYGPKG_FS_RAM_SIMPLE

//==========================================================================
// SIMPLE buffer management.
// Each node has a data buffer pointer and a size. This buffer is
// realloc()ed as needed.

// -------------------------------------------------------------------------
// findbuffer_node()
// return a pointer to the data at the indicated file position, extending
// the buffer if required.

static int findbuffer_node( ramfs_node  *node,  // node pointer
                            off_t pos,          // data position to get
                            cyg_uint8 **buffer, // returned buffer pointer
                            size_t *size,       // returned buffer size
                            cyg_bool alloc)     // extend allocation?
{
    if( alloc && (pos == node->datasize || node->datasize == 0) )
    {
        // If we are allowed to alloc new data, and we are at the end of the
        // current data allocation, or there is no data present, allocate or
        // extend the data buffer.
        
        cyg_uint8 *newdata;
        
        if( node->data == NULL )
            newdata = malloc( CYGNUM_RAMFS_REALLOC_INCREMENT );
        else
            newdata = realloc( node->data, pos+CYGNUM_RAMFS_REALLOC_INCREMENT );
        
        if( newdata == NULL ) return ENOSPC;
        else memset( newdata+pos, 0, CYGNUM_RAMFS_REALLOC_INCREMENT );
        
        node->data = newdata;
        node->datasize = pos+CYGNUM_RAMFS_REALLOC_INCREMENT;
    }
    else if( pos > node->datasize )
    {
        // Indicate end of data.
        *size = 0;
        return ENOERR;
    }

    *buffer = node->data+pos;
    *size = node->datasize-pos;

    return ENOERR;
}

// -------------------------------------------------------------------------
// freebuffer_node()
// Empty out the data storage from the node.

static int freebuffer_node( ramfs_node *node )
{
    if( node->data != NULL )
    {
        free( node->data );
    }

    node->data = NULL;
    node->datasize = 0;

    return ENOERR;
}

//==========================================================================

#else

//==========================================================================
// _BLOCKS storage management.
// Data storage in the node is by means of a set of arrays of pointers to
// blocks. The first array points directly to the data blocks. Subsequent
// arrays point to single and double indirect blocks respectively. 

// -------------------------------------------------------------------------
// findbuffer_direct()
// Indexes into an array of block pointers and extracts a pointer to the
// data at offset _pos_, allocating new blocks if required.

static int findbuffer_direct( off_t pos,
                              ramfs_block **blocks,
                              int nblocks,
                              cyg_uint8 **buffer,
                              size_t *size,
                              cyg_bool alloc)
{
    int bi = pos / CYGNUM_RAMFS_BLOCK_SIZE;
    int bpos = pos % CYGNUM_RAMFS_BLOCK_SIZE;
    ramfs_block *b;
    
    *buffer = NULL;
    *size = 0;
    
    if( bi >= nblocks )
        return ENOERR;

    b = blocks[bi];

    if( b == NULL )
    {
        // There is no block there. If _alloc_ is true we can fill the
        // slot in with a new block. If it is false, we indicate end of
        // data with a zero size result.
        if( alloc )
        {
            b = block_alloc();
            if( b == NULL )
                return ENOSPC;
            blocks[bi] = b;
        }
        else return ENOERR;
    }

    *buffer = &((*b)[bpos]);
    *size = CYGNUM_RAMFS_BLOCK_SIZE - bpos;

    return ENOERR;
}

// -------------------------------------------------------------------------
// findbuffer_indirect1()
// Indexes into an array of pointers to blocks containing pointers to
// blocks and extracts a pointer to the data at offset _pos_,
// allocating new blocks if required.

#if CYGNUM_RAMFS_BLOCKS_INDIRECT1 > 0

static int findbuffer_indirect1( off_t pos,
                                 ramfs_block ***blocks,
                                 int nblocks,
                                 cyg_uint8 **buffer,
                                 size_t *size,
                                 cyg_bool alloc)
{

    int bi = pos / RAMFS_INDIRECT1_BLOCK_EXTENT;
    int bpos = pos % RAMFS_INDIRECT1_BLOCK_EXTENT;
    int err;
    cyg_uint8 *b;
    size_t sz;

    // Use findbuffer_direct() to index and allocate
    // the first level indirect block.
    
    err = findbuffer_direct( bi*CYGNUM_RAMFS_BLOCK_SIZE,
                             (ramfs_block **)blocks,
                             nblocks,
                             &b,
                             &sz,
                             alloc);

    if( err != ENOERR )
        return err;

    if( sz == 0 )
    {
        *size = 0;
        return ENOERR;
    }

    // Use findbuffer_direct() on the first level indirect
    // block to allocate and return the data pointer.
    
    return findbuffer_direct( bpos,
                              blocks[bi],
                              RAMFS_INDIRECT_PER_BLOCK,
                              buffer,
                              size,
                              alloc);
}

#endif

// -------------------------------------------------------------------------
// findbuffer_indirect1()
// Indexes into an array of pointers to blocks containing pointers to
// blocks containing pointers to blocks (!) and extracts a pointer to
// the data at offset _pos_, allocating new blocks if required.

#if CYGNUM_RAMFS_BLOCKS_INDIRECT2 > 0

static int findbuffer_indirect2( off_t pos,
                                 ramfs_block ****blocks,
                                 int nblocks,
                                 cyg_uint8 **buffer,
                                 size_t *size,
                                 cyg_bool alloc)
{
    int bi = pos / RAMFS_INDIRECT2_BLOCK_EXTENT;
    int bpos = pos % RAMFS_INDIRECT2_BLOCK_EXTENT;
    int err;
    cyg_uint8 *b;
    size_t sz;

    // Use findbuffer_direct() to index and allocate
    // the first level indirect block.

    err = findbuffer_direct( bi*CYGNUM_RAMFS_BLOCK_SIZE,
                             (ramfs_block **)blocks,
                             nblocks,
                             &b,
                             &sz,
                             alloc);

    if( err != ENOERR )
        return err;

    if( sz == 0 )
    {
        *size = 0;
        return ENOERR;
    }

    // Use findbuffer_indirect1() on the first level indirect block to
    // index and allocate the next level indirect block and the data
    // block.
    
    return findbuffer_indirect1( bpos,
                                 blocks[bi],
                                 RAMFS_INDIRECT_PER_BLOCK,
                                 buffer,
                                 size,
                                 alloc);
}

#endif

// -------------------------------------------------------------------------
// findbuffer_node()
// Depending on the offset and configuration, call the appropriate
// function to get the buffer pointer.

static int findbuffer_node( ramfs_node  *node,
                            off_t pos,
                            cyg_uint8 **buffer,
                            size_t *size,
                            cyg_bool alloc)
{
#if CYGNUM_RAMFS_BLOCKS_DIRECT > 0    
    if( pos < RAMFS_DIRECT_MAX )
        return findbuffer_direct( pos,
                                  node->direct,
                                  CYGNUM_RAMFS_BLOCKS_DIRECT,
                                  buffer,
                                  size,
                                  alloc);
#endif        
#if CYGNUM_RAMFS_BLOCKS_INDIRECT1 > 0    
    if( pos < RAMFS_INDIRECT1_MAX )
        return findbuffer_indirect1( pos - RAMFS_DIRECT_MAX,
                                     node->indirect1,
                                     CYGNUM_RAMFS_BLOCKS_INDIRECT1,
                                     buffer,
                                     size,
                                     alloc);
#endif        
#if CYGNUM_RAMFS_BLOCKS_INDIRECT2 > 0    
    if( pos < RAMFS_INDIRECT2_MAX )
        return findbuffer_indirect2( pos - RAMFS_INDIRECT1_MAX,
                                     node->indirect2,
                                     CYGNUM_RAMFS_BLOCKS_INDIRECT2,
                                     buffer,
                                     size,
                                     alloc);
#endif

    return ENOSPC;
}

// -------------------------------------------------------------------------
// freeblock_list()
// Free a list of data blocks.

static void freeblock_list( ramfs_block *blocks[],int nblocks )
{
    int i;
    for( i = 0; i < nblocks ; i++ )
    {
        if( blocks[i] != NULL )
        {
            block_free( blocks[i] );
            blocks[i] = NULL;
        }
    }
}

// -------------------------------------------------------------------------
// freebuffer_node()
// Free all the data blocks in the node and clear the pointers.

static int freebuffer_node( ramfs_node *node )
{
#if CYGNUM_RAMFS_BLOCKS_DIRECT > 0
    freeblock_list( node->direct, CYGNUM_RAMFS_BLOCKS_DIRECT );
#endif

#if CYGNUM_RAMFS_BLOCKS_INDIRECT1 > 0
    {
        int i;
        for( i = 0; i < CYGNUM_RAMFS_BLOCKS_INDIRECT1 ; i++ )
        {
            if( node->indirect1[i] != NULL )
            {
                freeblock_list( (ramfs_block **)node->indirect1[i], RAMFS_INDIRECT_PER_BLOCK );
                block_free( (ramfs_block *)node->indirect1[i] );
                node->indirect1[i] = NULL;
            }
        }
    }
#endif    

#if CYGNUM_RAMFS_BLOCKS_INDIRECT2 > 0
    {
        int i;
        for( i = 0; i < CYGNUM_RAMFS_BLOCKS_INDIRECT2 ; i++ )
        {
            if( node->indirect2[i] != NULL )
            {
                ramfs_block ***b = node->indirect2[i];
                int j;
                for( j = 0; j < RAMFS_INDIRECT_PER_BLOCK ; j++ )
                {
                    if( b[j] != NULL )
                    {
                        freeblock_list( (ramfs_block **)b[j], RAMFS_INDIRECT_PER_BLOCK );
                        block_free( (ramfs_block *)b[j] );
                        b[j] = NULL;
                    }
                }
                block_free( (ramfs_block *)node->indirect2[i] );
                node->indirect2[i] = NULL;
            }
        }
    }
#endif

    return ENOERR;
}

//==========================================================================

#endif

//==========================================================================
// Node allocation

// -------------------------------------------------------------------------
// alloc_node()
// Allocate a node and initialize it.
// For the _SIMPLE allocation option, we just malloc it. For the
// _BLOCKS option we allocate a block and use that. In theory we could
// pack several nodes into a single block, but we don't at present due
// to sheer lazyness.

static ramfs_node *alloc_node( mode_t mode )
{
#ifdef CYGPKG_FS_RAM_SIMPLE
    ramfs_node *node = malloc( sizeof( ramfs_node ) );

    if( node == NULL )
        return NULL;
    
#else
    ramfs_block *b = block_alloc();
    ramfs_node *node;

    if( b == NULL )
        return NULL;

    node = (ramfs_node *)b;
    
#endif

    memset( node, 0, sizeof(ramfs_node) );
    
    node->mode          = mode;
    node->refcnt        = 0;
    node->nlink         = 0;
    node->size          = 0;
    node->atime         = 
    node->mtime         = 
    node->ctime         = cyg_timestamp();

#ifdef CYGPKG_FS_RAM_SIMPLE    
    node->datasize      = 0;
    node->data          = NULL;
#else

    // The node is already all zero
    
#endif    
    return node;
}

// -------------------------------------------------------------------------
// free_node()
// Release a node either back to the free pool or back into the block
// pool.

static void free_node( ramfs_node *node )
{
#ifdef CYGPKG_FS_RAM_SIMPLE    

    free( node );

#else

    block_free( (ramfs_block *)node );

#endif    
    
}


//==========================================================================
// Ref count and nlink management

// -------------------------------------------------------------------------
// dec_refcnt()
// Decrment the reference count on a node. If this makes the ref count
// zero, and the number of links is either zero for a file or one for
// a node, then this node is detached from the directory tree and can
// be freed.

static int dec_refcnt( ramfs_node *node )
{
    int err = ENOERR;
    node->refcnt--;

    if( node->refcnt == 0 &&
        ((S_ISREG(node->mode) && node->nlink == 0 ) ||
         (S_ISDIR(node->mode) && node->nlink == 1) )
        )
    {
        // This node it now totally detached from the directory tree,
        // so delete it.

        if( S_ISDIR(node->mode) )
        {
            del_direntry( node, ".", 1 );
            del_direntry( node, "..", 2 );
        }
        
        err = freebuffer_node( node );

        if( err == ENOERR )
            free_node( node );
    }

    return err;
}

// -------------------------------------------------------------------------
// dec_nlink()
// Decrement a node's link count. Since this has to do all the same
// work as dec_refcnt() we implement this using that function by
// essentially transferring the count to refcnt and then decrement
// that.

static int dec_nlink( ramfs_node *node )
{
    node->refcnt++;
    
    node->nlink--;

    return dec_refcnt( node );
}

//==========================================================================
// Directory operations

// -------------------------------------------------------------------------
// add_direntry()
// Add an entry to a directory. This is added as a chain of entry
// fragments until the name is exhausted.

static int add_direntry( ramfs_node *dir,       // dir to add to
                         const char *name,      // name to add
                         int namelen,           // length of name
                         ramfs_node *node       // node to reference
                       )
{
    off_t pos = 0;
    ramfs_dirent *d = NULL, *dp = NULL;
    cyg_bool isfirst = true;

    // Loop inserting fragments of the name into the directory until we
    // have found a home for them all.
    
    while( namelen > 0 )
    {
        int fraglen = namelen;

        if( fraglen > sizeof(d->name) )
            fraglen = sizeof(d->name);

        // Find a free fragment
        for(;;)
        {
            cyg_uint8 *buf;
            size_t size;
            int err = findbuffer_node( dir, pos, &buf, &size, true );
            if( err != ENOERR ) return err;

            d = (ramfs_dirent *)buf;

            if( size < sizeof(ramfs_dirent) || d->inuse )
            {
                pos += sizeof(ramfs_dirent);
                continue;
            }

            break;
        }

        // d now points to a free dirent structure

        d->node         = node;
        d->inuse        = 1;
        d->first        = isfirst;
        d->namelen      = namelen;
        d->fraglen      = fraglen;
        if( dp ) dp->next = pos;

        memcpy( d->name, name, fraglen );

        name            += fraglen;
        namelen         -= fraglen;
        pos             += sizeof(ramfs_dirent);
        dp              = d;
        isfirst         = false;
        
    }

    
    d->last = 1;        // Mark last fragment
    
    // Update directory times
    dir->mtime =
    dir->ctime = cyg_timestamp();
    
    // Extend dir size if necessary
    if( pos > dir->size )
        dir->size = pos;
    
    // Count the new link
    node->nlink++;
    
    return ENOERR;
}

// -------------------------------------------------------------------------
// find_direntry()
// Find a directory entry for the name and return a pointer to the first
// entry fragment.

static ramfs_dirent *find_direntry( ramfs_node *dir, const char *name, int namelen )
{
    ramfs_dirent *first = NULL;
    off_t pos = 0;
    int err;

    // Loop over all the entries until a match is found or we run out
    // of data.
    while( pos < dir->size )
    {
        const char *frag = name;
        ramfs_dirent *d;
        cyg_uint8 *buf;
        size_t size;
        
        // look for a first name fragment
        for(;;)
        {
            err = findbuffer_node( dir, pos, &buf, &size, false );
            if( err != ENOERR || size == 0)
                return NULL;

            d = (ramfs_dirent *)buf;

            if( size < sizeof(ramfs_dirent) || !d->inuse || !d->first )
            {
                pos += sizeof(ramfs_dirent);
                continue;
            }

            break;
        }

        // Here we have got a first fragment of a name, check it
        // against the name we are looking for. First check that they
        // are the same length.

        if( d->namelen == namelen )
        {
            // We have a potential candidate here...
            
            first = d;      // Save it for later

            // Now check that all the name fragments match
            for(;;)
            {
                int fraglen = namelen-(frag-name);

                if( fraglen > d->fraglen )
                    fraglen = d->fraglen;

                // compare strings, if different, look for another
                if( memcmp( frag, d->name, fraglen ) != 0 )
                    break;

                frag        += fraglen;
            
                // If we are at the last fragment, then the whole name string
                // has matched and we have a successful search.
                
                if( d->last )
                        return first;

                // Otherwise move on to next entry in chain
                err = findbuffer_node( dir, d->next, &buf, &size, false );
                if( err != ENOERR )
                    return NULL;

                d = (ramfs_dirent *)buf;

            }
        }

        pos += sizeof(ramfs_dirent);        
    }

    return NULL;
}

// -------------------------------------------------------------------------
// del_direntry()
// Delete a named directory entry. Find it and then follow the chain
// deleting the fragments as we go.

static int del_direntry( ramfs_node *dir, const char *name, int namelen )
{
    ramfs_dirent *d = find_direntry( dir, name, namelen );
    
    if( d == NULL )
        return ENOENT;

    for(;;)
    {
        int err;
        cyg_uint8 *buf;
        size_t size;

        d->inuse = 0;
        if( d->last ) break;

        err = findbuffer_node( dir, d->next, &buf, &size, false );
        if( err != ENOERR )
            return ENOENT;

        d = (ramfs_dirent *)buf;
    }

    dec_nlink( d->node );
    
    return ENOERR;
}

//==========================================================================
// Directory search

// -------------------------------------------------------------------------
// init_dirsearch()
// Initialize a dirsearch object to start a search

static void init_dirsearch( ramfs_dirsearch *ds,
                            ramfs_node *dir,
                            const char *name)
{
    ds->dir      = dir;
    ds->path     = name;
    ds->node     = dir;
    ds->name     = name;
    ds->namelen  = 0;
    ds->last     = false;    
}

// -------------------------------------------------------------------------
// find_entry()
// Search a single directory for the next name in a path and update the
// dirsearch object appropriately.

static int find_entry( ramfs_dirsearch *ds )
{
    ramfs_node *dir = ds->dir;
    const char *name = ds->path;
    const char *n = name;
    char namelen = 0;
    ramfs_dirent *d;
    
    // check that we really have a directory
    if( !S_ISDIR(dir->mode) )
        return ENOTDIR;

    // Isolate the next element of the path name. 
    while( *n != '\0' && *n != '/' )
        n++, namelen++;

    // If we terminated on a NUL, set last flag.
    if( *n == '\0' ) 
        ds->last = true;

    // update name in dirsearch object
    ds->name = name;
    ds->namelen = namelen;
    
    // Here we have the name and its length set up.
    // Search the directory for a matching entry

    d = find_direntry( dir, name, namelen );

    if( d == NULL )
        return ENOENT;

    // pass back the node we have found
    ds->node = d->node;

    return ENOERR;

}

// -------------------------------------------------------------------------
// ramfs_find()
// Main interface to directory search code. This is used in all file
// level operations to locate the object named by the pathname.

static int ramfs_find( ramfs_dirsearch *d )
{
    int err;

    // Short circuit empty paths
    if( *(d->path) == '\0' )
        return ENOERR;

    // iterate down directory tree until we find the object
    // we want.
    for(;;)
    {
        err = find_entry( d );

        if( err != ENOERR )
            return err;

        if( d->last )
            return ENOERR;

        // Update dirsearch object to search next directory.
        d->dir = d->node;
        d->path += d->namelen;
        if( *(d->path) == '/' ) d->path++; // skip dirname separators
    }
}

//==========================================================================
// Pathconf support
// This function provides support for pathconf() and fpathconf().

static int ramfs_pathconf( ramfs_node *node, struct cyg_pathconf_info *info )
{
    int err = ENOERR;
    
    switch( info->name )
    {
    case _PC_LINK_MAX:
        info->value = LINK_MAX;
        break;
        
    case _PC_MAX_CANON:
        info->value = -1;       // not supported
        err = EINVAL;
        break;
        
    case _PC_MAX_INPUT:
        info->value = -1;       // not supported
        err = EINVAL;        
        break;
        
    case _PC_NAME_MAX:
        info->value = NAME_MAX;
        break;
        
    case _PC_PATH_MAX:
        info->value = PATH_MAX;
        break;        

    case _PC_PIPE_BUF:
        info->value = -1;       // not supported
        err = EINVAL;        
        break;        

        
    case _PC_ASYNC_IO:
        info->value = -1;       // not supported
        err = EINVAL;        
        break;
        
    case _PC_CHOWN_RESTRICTED:
        info->value = -1;       // not supported
        err = EINVAL;
        break;
        
    case _PC_NO_TRUNC:
        info->value = 0;
        break;
        
    case _PC_PRIO_IO:
        info->value = 0;
        break;
        
    case _PC_SYNC_IO:
        info->value = 0;
        break;
        
    case _PC_VDISABLE:
        info->value = -1;       // not supported
        err = EINVAL;        
        break;
        
    default:
        err = EINVAL;
        break;
    }

    return err;
}

//==========================================================================
// Filesystem operations

// -------------------------------------------------------------------------
// ramfs_mount()
// Process a mount request. This mainly creates a root for the
// filesystem.

static int ramfs_mount    ( cyg_fstab_entry *fste, cyg_mtab_entry *mte )
{
    ramfs_node *root;
    int err;
    
    // Allocate a node to be the root of this filesystem and initialize it.

    root = alloc_node(__stat_mode_DIR);

    if( root == NULL )
        return ENOSPC;

    // Add . and .. entries back to self.
        
    err = add_direntry( root, ".", 1, root );
    if( err == ENOERR )
        err = add_direntry( root, "..", 2, root );

    if( err != ENOERR )
    {
        free_node( root );
        return err;
    }
    
    mte->root = (cyg_dir)root;
    
    return ENOERR;
}

// -------------------------------------------------------------------------
// ramfs_umount()
// Unmount the filesystem. This will currently only succeed if the
// filesystem is empty.

static int ramfs_umount   ( cyg_mtab_entry *mte )
{
    ramfs_node *root = (ramfs_node *)mte->root;

    // Check for open/inuse root
    if( root->refcnt != 0 )
        return EBUSY;

    // Check that root directory is clear of extra links.
    if( root->nlink != 2 )
        return EBUSY;

    // Just return it to free pool
    free_node( root );

    // Clear root pointer
    mte->root = CYG_DIR_NULL;
    
    // That's all folks.
        
    return ENOERR;
}

// -------------------------------------------------------------------------
// ramfs_open()
// Open a file for reading or writing.

static int ramfs_open     ( cyg_mtab_entry *mte, cyg_dir dir, const char *name,
                             int mode,  cyg_file *file )
{

    ramfs_dirsearch ds;
    ramfs_node *node = NULL;
    int err;

    init_dirsearch( &ds, (ramfs_node *)dir, name );
    
    err = ramfs_find( &ds );

    if( err == ENOENT )
    {
        if( ds.last && (mode & O_CREAT) )
        {
            // No node there, if the O_CREAT bit is set then we must
            // create a new one. The dir and name fields of the dirsearch
            // object will have been updated so we know where to put it.

            node = alloc_node( __stat_mode_REG );

            if( node == NULL )
                return ENOSPC;

            err = add_direntry( ds.dir, ds.name, ds.namelen, node );

            if( err != ENOERR )
            {
                free_node( node );
                return err;
            }
        
            err = ENOERR;
        }
    }
    else if( err == ENOERR )
    {
        // The node exists. If the O_CREAT and O_EXCL bits are set, we
        // must fail the open.
        
        if( (mode & (O_CREAT|O_EXCL)) == (O_CREAT|O_EXCL) )
            err = EEXIST;
        else node = ds.node;
    }
    
    if( err == ENOERR && (mode & O_TRUNC ) )
    {
        // If the O_TRUNC bit is set we must clean out the file data.

        err = freebuffer_node( node );
        node->size = 0;

        // Update file times
        node->ctime =
        node->mtime = cyg_timestamp();
    }

    if( err != ENOERR ) return err;

    // Check that we actually have a file here
    if( S_ISDIR(node->mode) ) return EISDIR;

    node->refcnt++;       // Count successful open
    
    // Initialize the file object
    
    file->f_flag        |= mode & CYG_FILE_MODE_MASK;
    file->f_type        = CYG_FILE_TYPE_FILE;
    file->f_ops         = &ramfs_fileops;
    file->f_offset      = (mode&O_APPEND) ? node->size : 0;
    file->f_data        = (CYG_ADDRWORD)node;
    file->f_xops        = 0;

    return ENOERR;
}

// -------------------------------------------------------------------------
// ramfs_unlink()
// Remove a file link from its directory.

static int ramfs_unlink   ( cyg_mtab_entry *mte, cyg_dir dir, const char *name )
{
    ramfs_dirsearch ds;
    int err;

    init_dirsearch( &ds, (ramfs_node *)dir, name );
    
    err = ramfs_find( &ds );

    if( err != ENOERR ) return err;

    // Cannot unlink directories, use rmdir() instead
    if( S_ISDIR(ds.node->mode) )
        return EPERM;

    // Delete it from its directory
    err = del_direntry( ds.dir, ds.name, ds.namelen );
    
    return err;
}

// -------------------------------------------------------------------------
// ramfs_mkdir()
// Create a new directory.

static int ramfs_mkdir    ( cyg_mtab_entry *mte, cyg_dir dir, const char *name )
{
    ramfs_dirsearch ds;
    ramfs_node *node = NULL;
    int err;

    init_dirsearch( &ds, (ramfs_node *)dir, name );
    
    err = ramfs_find( &ds );

    if( err == ENOENT )
    {
        if( ds.last )
        {
            // The entry does not exist, and it is the last element in
            // the pathname, so we can create it here.
            int doterr, dotdoterr, direrr;
        
            node = alloc_node( __stat_mode_DIR );

            if( node == NULL )
                return ENOSPC;

            // Add "." and ".." entries.
            doterr = add_direntry( node, ".", 1, node );
            dotdoterr = add_direntry( node, "..", 2, ds.dir );

            // And add to parent directory.
            direrr = add_direntry( ds.dir, ds.name, ds.namelen, node );

            // check for any errors in that...
            if( doterr+dotdoterr+direrr != ENOERR )
            {
                // For each of the add_direntry() calls that succeeded,
                // we must now undo it.
                
                if( doterr == ENOERR )
                    del_direntry( node, ".", 1 );
                else err = doterr;

                if( dotdoterr == ENOERR )
                    del_direntry( node, "..", 2 );
                else err = dotdoterr;

                if( direrr == ENOERR )
                    del_direntry( ds.dir, ds.name, ds.namelen );
                else err = direrr;

                // Free the data and the node itself.
                freebuffer_node( node );
                free_node( node );
            }
            else err = ENOERR;
        }
        // If this was not the last element, then and intermediate
        // directory does not exist.
    }
    else
    {
        // If there we no error, something already exists with that
        // name, so we cannot create another one.
        
        if( err == ENOERR )
            err = EEXIST;
    }

    return err;
}

// -------------------------------------------------------------------------
// ramfs_rmdir()
// Remove a directory.

static int ramfs_rmdir    ( cyg_mtab_entry *mte, cyg_dir dir, const char *name )
{
    ramfs_dirsearch ds;
    int err;

    init_dirsearch( &ds, (ramfs_node *)dir, name );
    
    err = ramfs_find( &ds );

    if( err != ENOERR ) return err;

    // Check that this is actually a directory.
    if( !S_ISDIR(ds.node->mode) )
        return EPERM;

    // Delete the entry. This will adjust the link values
    // accordingly and if the directory is now unreferenced,
    // will cause it to be deleted.
    
    err = del_direntry( ds.dir, ds.name, ds.namelen );
    
    return err;
}

// -------------------------------------------------------------------------
// ramfs_rename()
// Rename a file/dir.

static int ramfs_rename   ( cyg_mtab_entry *mte, cyg_dir dir1, const char *name1,
                             cyg_dir dir2, const char *name2 )
{
    ramfs_dirsearch ds1, ds2;
    int err;

    init_dirsearch( &ds1, (ramfs_node *)dir1, name1 );
    
    err = ramfs_find( &ds1 );

    if( err != ENOERR ) return err;

    init_dirsearch( &ds2, (ramfs_node *)dir2, name2 );
    
    err = ramfs_find( &ds2 );

    // Allow through renames to non-existent objects.
    if( ds2.last && err == ENOENT )
        ds2.node = NULL, err = ENOERR;
    
    if( err != ENOERR ) return err;

    // Null rename, just return
    if( ds1.node == ds2.node )
        return ENOERR;

    // First deal with any entry that is at the destination
    if( ds2.node )
    {
        // Check that we are renaming like-for-like

        if( !S_ISDIR(ds1.node->mode) && S_ISDIR(ds2.node->mode) )
            return EISDIR;

        if( S_ISDIR(ds1.node->mode) && !S_ISDIR(ds2.node->mode) )
            return ENOTDIR;

        // Now delete the destination directory entry
        
        err = del_direntry( ds2.dir, ds2.name, ds2.namelen );
        
        if( err != ENOERR ) return err;

    }

    // Now we know that there is no clashing node at the destination,
    // make a new direntry at the destination and delete the old entry
    // at the source.

    err = add_direntry( ds2.dir, ds2.name, ds2.namelen, ds1.node );

    if( err == ENOERR )
        err = del_direntry( ds1.dir, ds1.name, ds1.namelen );

    // Update directory times
    if( err == ENOERR )
        ds1.dir->ctime =
        ds1.dir->mtime =
        ds2.dir->ctime =
        ds2.dir->mtime = cyg_timestamp();
            
    return err;
}

// -------------------------------------------------------------------------
// ramfs_link()
// Make a new directory entry for a file.

static int ramfs_link     ( cyg_mtab_entry *mte, cyg_dir dir1, const char *name1,
                             cyg_dir dir2, const char *name2, int type )
{
    ramfs_dirsearch ds1, ds2;
    int err;

    // Only do hard links for now in this filesystem
    if( type != CYG_FSLINK_HARD )
        return EINVAL;
    
    init_dirsearch( &ds1, (ramfs_node *)dir1, name1 );
    
    err = ramfs_find( &ds1 );

    if( err != ENOERR ) return err;

    init_dirsearch( &ds2, (ramfs_node *)dir2, name2 );
    
    err = ramfs_find( &ds2 );

    // Don't allow links to existing objects
    if( err == ENOERR ) return EEXIST;
    
    // Allow through links to non-existing terminal objects
    if( ds2.last && err == ENOENT )
        ds2.node = NULL, err = ENOERR;

    if( err != ENOERR ) return err;
    
    // Now we know that there is no existing node at the destination,
    // make a new direntry at the destination.

    err = add_direntry( ds2.dir, ds2.name, ds2.namelen, ds1.node );

    if( err == ENOERR )
        ds1.node->ctime =
        ds2.dir->ctime =
        ds2.dir->mtime = cyg_timestamp();
            
    return err;
}

// -------------------------------------------------------------------------
// ramfs_opendir()
// Open a directory for reading.

static int ramfs_opendir  ( cyg_mtab_entry *mte, cyg_dir dir, const char *name,
                             cyg_file *file )
{
    ramfs_dirsearch ds;
    int err;

    init_dirsearch( &ds, (ramfs_node *)dir, name );
    
    err = ramfs_find( &ds );

    if( err != ENOERR ) return err;

    // check it is really a directory.
    if( !S_ISDIR(ds.node->mode) ) return ENOTDIR;

    ds.node->refcnt++;       // Count successful open
    
    // Initialize the file object, setting the f_ops field to a
    // special set of file ops.
    
    file->f_type        = CYG_FILE_TYPE_FILE;
    file->f_ops         = &ramfs_dirops;
    file->f_offset      = 0;
    file->f_data        = (CYG_ADDRWORD)ds.node;
    file->f_xops        = 0;
        
    return ENOERR;

}

// -------------------------------------------------------------------------
// ramfs_chdir()
// Change directory support.

static int ramfs_chdir    ( cyg_mtab_entry *mte, cyg_dir dir, const char *name,
                             cyg_dir *dir_out )
{
    if( dir_out != NULL )
    {
        // This is a request to get a new directory pointer in
        // *dir_out.

        ramfs_dirsearch ds;
        int err;
    
        init_dirsearch( &ds, (ramfs_node *)dir, name );
    
        err = ramfs_find( &ds );

        if( err != ENOERR ) return err;

        // check it is a directory
        if( !S_ISDIR(ds.node->mode) )
            return ENOTDIR;
        
        // Increment ref count to keep this directory in existent
        // while it is the current cdir.
        ds.node->refcnt++;

        // Pass it out
        *dir_out = (cyg_dir)ds.node;
    }
    else
    {
        // If no output dir is required, this means that the mte and
        // dir arguments are the current cdir setting and we should
        // forget this fact.

        ramfs_node *node = (ramfs_node *)dir;

        // Just decrement directory reference count.
        dec_refcnt( node );
    }
        
    return ENOERR;
}

// -------------------------------------------------------------------------
// ramfs_stat()
// Get struct stat info for named object.

static int ramfs_stat     ( cyg_mtab_entry *mte, cyg_dir dir, const char *name,
                             struct stat *buf)
{
    ramfs_dirsearch ds;
    int err;

    init_dirsearch( &ds, (ramfs_node *)dir, name );
    
    err = ramfs_find( &ds );

    if( err != ENOERR ) return err;

    // Fill in the status
    buf->st_mode        = ds.node->mode;
    buf->st_ino         = (ino_t)ds.node;
    buf->st_dev         = 0;
    buf->st_nlink       = ds.node->nlink;
    buf->st_uid         = 0;
    buf->st_gid         = 0;
    buf->st_size        = ds.node->size;
    buf->st_atime       = ds.node->atime;
    buf->st_mtime       = ds.node->mtime;
    buf->st_ctime       = ds.node->ctime;
    
    return err;
}

// -------------------------------------------------------------------------
// ramfs_getinfo()
// Getinfo. Currently only support pathconf().

static int ramfs_getinfo  ( cyg_mtab_entry *mte, cyg_dir dir, const char *name,
                             int key, void *buf, int len )
{
    ramfs_dirsearch ds;
    int err;

    init_dirsearch( &ds, (ramfs_node *)dir, name );
    
    err = ramfs_find( &ds );

    if( err != ENOERR ) return err;

    switch( key )
    {
    case FS_INFO_CONF:
        err = ramfs_pathconf( ds.node, (struct cyg_pathconf_info *)buf );
        break;
        
    default:
        err = EINVAL;
    }
    return err;
}

// -------------------------------------------------------------------------
// ramfs_setinfo()
// Setinfo. Nothing to support here at present.

static int ramfs_setinfo  ( cyg_mtab_entry *mte, cyg_dir dir, const char *name,
                             int key, void *buf, int len )
{
    // No setinfo keys supported at present
    
    return EINVAL;
}


//==========================================================================
// File operations

// -------------------------------------------------------------------------
// ramfs_fo_read()
// Read data from the file.

static int ramfs_fo_read      (struct CYG_FILE_TAG *fp, struct CYG_UIO_TAG *uio)
{
    ramfs_node *node = (ramfs_node *)fp->f_data;
    int i;
    off_t pos = fp->f_offset;
    ssize_t resid = uio->uio_resid;

    // Loop over the io vectors until there are none left
    for( i = 0; i < uio->uio_iovcnt; i++ )
    {
        cyg_iovec *iov = &uio->uio_iov[i];
        char *buf = (char *)iov->iov_base;
        off_t len = iov->iov_len;

        // Loop over each vector filling it with data from the file.
        while( len > 0 && pos < node->size )
        {
            cyg_uint8 *fbuf;
            size_t bsize;
            off_t l = len;
            int err;

            // Get a pointer to the data at offset _pos_.
            err = findbuffer_node( node, pos, &fbuf, &bsize, false );

            if( err != ENOERR )
                return err;

            // adjust size to end of file if necessary
            if( l > node->size-pos )
                l = node->size-pos;
            
            // adjust size to the amount of contiguous data we can see
            // at present.
            if( l > bsize )
                l = bsize;

            // copy data out
            memcpy( buf, fbuf, l );

            // Update working vars
            len -= l;
            buf += l;
            pos += l;
            resid -= l;
        }
    }

    // We successfully read some data, update the node's access time
    // and update the file offset and transfer residue.
    
    node->atime = cyg_timestamp();

    uio->uio_resid = resid;
    fp->f_offset = pos;
    
    return ENOERR;
}

// -------------------------------------------------------------------------
// ramfs_fo_write()
// Write data to file.

static int ramfs_fo_write     (struct CYG_FILE_TAG *fp, struct CYG_UIO_TAG *uio)
{
    ramfs_node *node = (ramfs_node *)fp->f_data;
    off_t pos = fp->f_offset;
    ssize_t resid = uio->uio_resid;    
    int err = ENOERR;
    int i;

    // If the APPEND mode bit was supplied, force all writes to
    // the end of the file.
    if( fp->f_flag & CYG_FAPPEND )
        pos = fp->f_offset = node->size;
    
    // Check that pos is within current file size, or at the very end.
    if( pos < 0 || pos > node->size )
        return EINVAL;

    // Now loop over the iovecs until they are all done, or
    // we get an error.
    for( i = 0; i < uio->uio_iovcnt; i++ )
    {
        cyg_iovec *iov = &uio->uio_iov[i];
        char *buf = (char *)iov->iov_base;
        off_t len = iov->iov_len;

        // loop over the vector writing it to the file until it has
        // all been done.
        while( len > 0 )
        {
            cyg_uint8 *fbuf;
            size_t bsize;
            off_t l = len;
            
            err = findbuffer_node( node, pos, &fbuf, &bsize, true );

            // Stop writing if there is no more space in the file and
            // indicate end of data.
            if( err == ENOSPC )
                break;
            
            if( err != ENOERR )
                return err;
            
            // adjust size to this block
            if( l > bsize )
                l = bsize;

            // copy data in
            memcpy( fbuf, buf, l );

            // Update working vars
            len -= l;
            buf += l;
            pos += l;
            resid -= l;
        }
    }

    // We wrote some data successfully, update the modified and access
    // times of the node, increase its size appropriately, and update
    // the file offset and transfer residue.
    node->mtime =
    node->ctime = cyg_timestamp();
    if( pos > node->size )
        node->size = pos;    

    uio->uio_resid = resid;
    fp->f_offset = pos;
    
    return err;
}

// -------------------------------------------------------------------------
// ramfs_fo_lseek()
// Seek to a new file position.

static int ramfs_fo_lseek     (struct CYG_FILE_TAG *fp, off_t *apos, int whence )
{
    ramfs_node *node = (ramfs_node *)fp->f_data;
    off_t pos = *apos;

    switch( whence )
    {
    case SEEK_SET:
        // Pos is already where we want to be.
        break;

    case SEEK_CUR:
        // Add pos to current offset.
        pos += fp->f_offset;
        break;

    case SEEK_END:
        // Add pos to file size.
        pos += node->size;
        break;

    default:
        return EINVAL;
    }
    
    // Check that pos is still within current file size, or at the
    // very end.
    if( pos < 0 || pos > node->size )
        return EINVAL;

    // All OK, set fp offset and return new position.
    *apos = fp->f_offset = pos;
    
    return ENOERR;
}

// -------------------------------------------------------------------------
// ramfs_fo_ioctl()
// Handle ioctls. Currently none are defined.

static int ramfs_fo_ioctl     (struct CYG_FILE_TAG *fp, CYG_ADDRWORD com,
                                CYG_ADDRWORD data)
{
    // No Ioctls currenly defined.

    return EINVAL;
}

// -------------------------------------------------------------------------
// ramfs_fo_fsync().
// Force the file out to data storage.

static int ramfs_fo_fsync     (struct CYG_FILE_TAG *fp, int mode )
{
    // Data is always permanently where it belongs, nothing to do
    // here.
  
    return ENOERR;
}

// -------------------------------------------------------------------------
// ramfs_fo_close()
// Close a file. We just decrement the refcnt and let it go away if
// that is all that is keeping it here.

static int ramfs_fo_close     (struct CYG_FILE_TAG *fp)
{
    ramfs_node *node = (ramfs_node *)fp->f_data;

    dec_refcnt( node );

    fp->f_data = 0;     // zero data pointer
    
    return ENOERR;
}

// -------------------------------------------------------------------------
//ramfs_fo_fstat()
// Get file status.

static int ramfs_fo_fstat     (struct CYG_FILE_TAG *fp, struct stat *buf )
{
    ramfs_node *node = (ramfs_node *)fp->f_data;

    // Fill in the status
    buf->st_mode        = node->mode;
    buf->st_ino         = (ino_t)node;
    buf->st_dev         = 0;
    buf->st_nlink       = node->nlink;
    buf->st_uid         = 0;
    buf->st_gid         = 0;
    buf->st_size        = node->size;
    buf->st_atime       = node->atime;
    buf->st_mtime       = node->mtime;
    buf->st_ctime       = node->ctime;
    
    return ENOERR;
}

// -------------------------------------------------------------------------
// ramfs_fo_getinfo()
// Get info. Currently only supports fpathconf().

static int ramfs_fo_getinfo   (struct CYG_FILE_TAG *fp, int key, void *buf, int len )
{
    ramfs_node *node = (ramfs_node *)fp->f_data;    
    int err;

    switch( key )
    {
    case FS_INFO_CONF:
        err = ramfs_pathconf( node, (struct cyg_pathconf_info *)buf );
        break;
        
    default:
        err = EINVAL;
    }
    return err;
}

// -------------------------------------------------------------------------
// ramfs_fo_setinfo()
// Set info. Nothing supported here.

static int ramfs_fo_setinfo   (struct CYG_FILE_TAG *fp, int key, void *buf, int len )
{
    // No setinfo key supported at present
    
    return ENOERR;
}


//==========================================================================
// Directory operations

// -------------------------------------------------------------------------
// ramfs_fo_dirread()
// Read a single directory entry from a file.

static int ramfs_fo_dirread      (struct CYG_FILE_TAG *fp, struct CYG_UIO_TAG *uio)
{
    ramfs_node *dir = (ramfs_node *)fp->f_data;
    off_t pos = fp->f_offset;
    int err = ENOERR;
    struct dirent *ent = (struct dirent *)uio->uio_iov[0].iov_base;
    char *nbuf = ent->d_name;
    int nlen = sizeof(ent->d_name)-1;
    off_t len = uio->uio_iov[0].iov_len;
    ramfs_dirent *d = NULL;
    cyg_uint8 *buf;
    size_t size;
        
    if( len < sizeof(struct dirent) )
        return EINVAL;    
    
    // look for a first name fragment

    while( pos < dir->size )
    {
        err = findbuffer_node( dir, pos, &buf, &size, false );
        if( err != ENOERR || size == 0)
            break;

        d = (ramfs_dirent *)buf;

        if( size < sizeof(ramfs_dirent) || !d->inuse || !d->first )
        {
            pos += sizeof(ramfs_dirent);
            continue;
        }

        break;
    }

    // Check we have not exceeded the size of the directory.
    if( pos == dir->size )
        return err;
    
    // Here we  have the first fragment of a directory entry.

    for(;;)
    {
        int fraglen = d->fraglen;

        // adjust to allow for remaining space in dirent struct
        if( fraglen > nlen )
            fraglen = nlen;
        
        memcpy( nbuf, d->name, fraglen);
        nbuf += fraglen;
        nlen -= fraglen;
            
        // if we hit the last entry, we have a successful transfer
        if( d->last || nlen == 0)
            break;

        // Otherwise move on to next entry in chain
        err = findbuffer_node( dir, d->next, &buf, &size, false );
        if( err != ENOERR )
            return err;

        d = (ramfs_dirent *)buf;
    }

    // A successful read. Terminate the entry name with a NUL, set the
    // residue and set the file offset to restart at the next
    // directory entry.
    
    *nbuf = '\0';
    uio->uio_resid -= sizeof(struct dirent);
    fp->f_offset = pos+sizeof(ramfs_dirent);
    
    return ENOERR;
}

// -------------------------------------------------------------------------
// ramfs_fo_dirlseek()
// Seek directory to start.

static int ramfs_fo_dirlseek     (struct CYG_FILE_TAG *fp, off_t *pos, int whence )
{
    // Only allow SEEK_SET to zero
    
    if( whence != SEEK_SET || *pos != 0)
        return EINVAL;

    *pos = fp->f_offset = 0;
    
    return ENOERR;
}

// -------------------------------------------------------------------------
// EOF ramfs.c
