//==========================================================================
//
//      jffs2.c
//
//      JFFS2 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):           dominic.ostrowski@3glab.com
// Contributors:        nickg, richard.panton@3glab.com
// Date:                2000-07-25
// Purpose:             JFFS2 file system
// Description:         This is the JFFS2 flash filesystem for eCos. 
//                      
//####DESCRIPTIONEND####
//
//==========================================================================
// 
// General Description
// ===================
// 
//
//==========================================================================

#include "jffs2port.h"
#include "jffs2.h"
#include "jffs2_fs_sb.h"
#include "jffs2_fs_i.h"
#include "nodelist.h"

#include <errno.h>
#include <string.h>
#include <cyg/io/io.h>
#include <cyg/io/config_keys.h>
#include <cyg/io/flash.h>

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

// Filesystem operations
static int jffs2_mount    ( cyg_fstab_entry *fste, cyg_mtab_entry *mte );
static int jffs2_umount   ( cyg_mtab_entry *mte );
static int jffs2_open     ( cyg_mtab_entry *mte, cyg_dir dir, const char *name,
                             int mode,  cyg_file *fte );
static int jffs2_ops_unlink   ( cyg_mtab_entry *mte, cyg_dir dir, const char *name );
static int jffs2_ops_mkdir    ( cyg_mtab_entry *mte, cyg_dir dir, const char *name );
static int jffs2_ops_rmdir    ( cyg_mtab_entry *mte, cyg_dir dir, const char *name );
static int jffs2_ops_rename   ( cyg_mtab_entry *mte, cyg_dir dir1, const char *name1,
                             cyg_dir dir2, const char *name2 );
static int jffs2_ops_link     ( cyg_mtab_entry *mte, cyg_dir dir1, const char *name1,
                             cyg_dir dir2, const char *name2, int type );
static int jffs2_opendir  ( cyg_mtab_entry *mte, cyg_dir dir, const char *name,
                             cyg_file *fte );
static int jffs2_chdir    ( cyg_mtab_entry *mte, cyg_dir dir, const char *name,
                             cyg_dir *dir_out );
static int jffs2_stat     ( cyg_mtab_entry *mte, cyg_dir dir, const char *name,
                             struct stat *buf);
static int jffs2_getinfo  ( cyg_mtab_entry *mte, cyg_dir dir, const char *name,
                             int key, void *buf, int len );
static int jffs2_setinfo  ( cyg_mtab_entry *mte, cyg_dir dir, const char *name,
                             int key, void *buf, int len );

// File operations
static int jffs2_fo_read      (struct CYG_FILE_TAG *fp, struct CYG_UIO_TAG *uio);
static int jffs2_fo_write     (struct CYG_FILE_TAG *fp, struct CYG_UIO_TAG *uio);
static int jffs2_fo_lseek     (struct CYG_FILE_TAG *fp, off_t *pos, int whence );
static int jffs2_fo_ioctl     (struct CYG_FILE_TAG *fp, CYG_ADDRWORD com,
                                CYG_ADDRWORD data);
static int jffs2_fo_fsync     (struct CYG_FILE_TAG *fp, int mode );        
static int jffs2_fo_close     (struct CYG_FILE_TAG *fp);
static int jffs2_fo_fstat     (struct CYG_FILE_TAG *fp, struct stat *buf );
static int jffs2_fo_getinfo   (struct CYG_FILE_TAG *fp, int key, void *buf, int len );
static int jffs2_fo_setinfo   (struct CYG_FILE_TAG *fp, int key, void *buf, int len );

// Directory operations
static int jffs2_fo_dirread      (struct CYG_FILE_TAG *fp, struct CYG_UIO_TAG *uio);
static int jffs2_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( jffs2_fste, "jffs2", 0,
             CYG_SYNCMODE_FILE_FILESYSTEM|CYG_SYNCMODE_IO_FILESYSTEM,
             jffs2_mount,
             jffs2_umount,
             jffs2_open,
             jffs2_ops_unlink,
             jffs2_ops_mkdir,
             jffs2_ops_rmdir,
             jffs2_ops_rename,
             jffs2_ops_link,
             jffs2_opendir,
             jffs2_chdir,
             jffs2_stat,
             jffs2_getinfo,
             jffs2_setinfo);

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

static cyg_fileops jffs2_fileops =
{
    jffs2_fo_read,
    jffs2_fo_write,
    jffs2_fo_lseek,
    jffs2_fo_ioctl,
    cyg_fileio_seltrue,
    jffs2_fo_fsync,
    jffs2_fo_close,
    jffs2_fo_fstat,
    jffs2_fo_getinfo,
    jffs2_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 jffs2_dirops =
{
    jffs2_fo_dirread,
    (cyg_fileop_write *)cyg_fileio_enosys,
    jffs2_fo_dirlseek,
    (cyg_fileop_ioctl *)cyg_fileio_enosys,
    cyg_fileio_seltrue,
    (cyg_fileop_fsync *)cyg_fileio_enosys,
    jffs2_fo_close,
    (cyg_fileop_fstat *)cyg_fileio_enosys,
    (cyg_fileop_getinfo *)cyg_fileio_enosys,
    (cyg_fileop_setinfo *)cyg_fileio_enosys
};


//==========================================================================
// STATIC VARIABLES !!!

static char read_write_buffer[PAGE_CACHE_SIZE]; //avoids malloc when user may be under memory pressure
static char gc_buffer[PAGE_CACHE_SIZE]; //avoids malloc when user may be under memory pressure

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

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

typedef struct jffs2_dirsearch jffs2_dirsearch;

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

// -------------------------------------------------------------------------
// dec_refcnt()
// Decrment the reference count on an inode. If this makes the ref count
// zero, then this inode can be freed.

static int dec_refcnt( struct inode *node )
{
    int err = ENOERR;
    node->i_count--;

    // In JFFS2 inode's are temporary in ram structures that are free'd when the usage i_count drops to 0
    // The i_nlink however is managed by JFFS2 and is unrelated to usage
    if( node->i_count == 0)
    {
        // This inode is not in use, so delete it.
        iput(node);
    }

    return err;
}

// FIXME: This seems like real cruft. Wouldn't it be better just to do the
// right thing?
static void icache_evict(struct inode *root_i, struct inode *i) {
    struct inode *cached_inode;
    struct inode *next_inode;
    
    D2(printf("icache_evict\n"));
    // If this is an absolute search path from the root,
    // remove all cached inodes with i_count of zero (these are only 
    // held where needed for dotdot filepaths)
    if(i == root_i) {
        for(cached_inode = root_i; cached_inode != NULL; cached_inode = next_inode) {
            next_inode = cached_inode->i_cache_next;
            if (cached_inode->i_count == 0) {
                cached_inode->i_cache_prev->i_cache_next = cached_inode->i_cache_next; // Prveious entry points ahead of us
                if (cached_inode->i_cache_next != NULL)
                    cached_inode->i_cache_next->i_cache_prev = cached_inode->i_cache_prev; // Next entry points behind us
                jffs2_clear_inode(cached_inode);
                D2(printf("free icache_evict inode %x $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n", cached_inode));
                free(cached_inode);
            }
        }
    }
}

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

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

static void init_dirsearch( jffs2_dirsearch *ds,
                            struct inode *dir,
                            const char *name)
{
    D2(printf("init_dirsearch name = %s\n", name));
    D2(printf("init_dirsearch dir = %x\n", dir));
    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( jffs2_dirsearch *ds )
{
    unsigned long hash;
    struct qstr this;
    unsigned int c;
    const char * hashname;

    struct inode *dir = ds->dir;
    const char *name = ds->path;
    const char *n = name;
    char namelen = 0;
    struct inode *d;
    
    D2(printf("find_entry\n"));

    // check that we really have a directory
    if( !S_ISDIR(dir->i_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;
    
    if (name[0] == '.') switch (namelen) {
        default:
            break;
        case 2:
            // Dot followed by not Dot, treat as any other name	
            if (name[1] != '.')
                break;
            // Dot Dot 
            // Move back up the search path
            D2(printf("find_entry found ..\n"));
            ds->node = ds->dir->i_parent;
            if(ds->dir->i_count == 0) {
                iput(ds->dir); // This inode may be evicted
                ds->dir = NULL;
            }
            return ENOERR;
        case 1:
            // Dot is consumed
            D2(printf("find_entry found .\n"));
            ds->node = ds->dir;
            return ENOERR;
    }

    // Here we have the name and its length set up.
    // Search the directory for a matching entry

    hashname = name;
    this.name = hashname;
    c = *(const unsigned char *)hashname;

    hash = init_name_hash();
    do {
        hashname++;
        hash = partial_name_hash(c, hash);
        c = *(const unsigned char *)hashname;
    } while (c && (c != '/'));
    this.len = hashname - (const char *) this.name;
    this.hash = end_name_hash(hash);

    D2(printf("find_entry for name = %s\n", ds->path));
    d = jffs2_lookup(dir, &this);
    D2(printf("find_entry got dir = %x\n", d));

    if( d == NULL )
        return ENOENT;

    // The back path for dotdot to follow
    d->i_parent = dir; 
    // pass back the node we have found
    ds->node = d;

    return ENOERR;

}

// -------------------------------------------------------------------------
// jffs2_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 jffs2_find( jffs2_dirsearch *d )
{
    int err;

    D2(printf("jffs2_find for path =%s\n", d->path));
    // 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;

        // every inode traversed in the find is temporary and should be free'd
        //iput(d->dir);

        // 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 jffs2_pathconf( struct inode *node, struct cyg_pathconf_info *info )
{
    int err = ENOERR;
    D2(printf("jffs2_pathconf\n"));
    
    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

// -------------------------------------------------------------------------
// jffs2_mount()
// Process a mount request. This mainly creates a root for the
// filesystem.
static int jffs2_read_super(struct super_block *sb)
{
	struct jffs2_sb_info *c;
	struct inode *root_i;
        Cyg_ErrNo err;
        cyg_uint32 len;
        cyg_io_flash_getconfig_devsize_t ds;
        cyg_io_flash_getconfig_blocksize_t bs;

	D1(printk(KERN_DEBUG "jffs2: read_super\n"));

	c = JFFS2_SB_INFO(sb);
	memset(c, 0, sizeof(*c));
        
        len = sizeof(ds);
        err = cyg_io_get_config( sb->s_dev,
                                 CYG_IO_GET_CONFIG_FLASH_DEVSIZE,
                                 &ds, &len );
        if ( err != ENOERR ) {
            D1(printf("jffs2: cyg_io_get_config failed to get dev size: %d\n",
                      err));
            return err;
        }
        len = sizeof(bs);
        bs.offset = 0;
        err = cyg_io_get_config( sb->s_dev,
                                 CYG_IO_GET_CONFIG_FLASH_BLOCKSIZE,
                                 &bs, &len );
        if ( err != ENOERR ) {
            D1(printf("jffs2: cyg_io_get_config failed to get block size: %d\n",
                      err));
            return err;
        }
        
        c->sector_size = bs.block_size;
        c->flash_size = ds.dev_size;

	err = jffs2_do_mount_fs(c);
	if (err)
		return -err;

	D1(printk(KERN_DEBUG "jffs2_read_super(): Getting root inode\n"));
	root_i = iget(sb, 1);
	if (is_bad_inode(root_i)) {
		D1(printk(KERN_WARNING "get root inode failed\n"));
		err = EIO;
		goto out_nodes;
	}

	D1(printk(KERN_DEBUG "jffs2_read_super(): d_alloc_root()\n"));
	sb->s_root = d_alloc_root(root_i);
	if (!sb->s_root) {
		err = ENOMEM;
		goto out_root_i;
	}
	sb->s_blocksize = PAGE_CACHE_SIZE;
	sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
	sb->s_magic = JFFS2_SUPER_MAGIC;

	return 0;

 out_root_i:
	iput(root_i);
 out_nodes:
	jffs2_free_ino_caches(c);
	jffs2_free_raw_node_refs(c);
	free(c->blocks);

	return err;
}

static int jffs2_mount    ( cyg_fstab_entry *fste, cyg_mtab_entry *mte )
{
    extern cyg_mtab_entry mtab[], mtab_end;
    struct super_block *jffs2_sb = NULL;
    struct jffs2_sb_info *c;
    cyg_mtab_entry *m;
    cyg_io_handle_t t;
    Cyg_ErrNo err;

    D2(printf("jffs2_mount\n"));

    err = cyg_io_lookup( mte->devname, &t );
    if( err != ENOERR )
        return -err;

    // Iterate through the mount table to see if we're mounted
    // FIXME: this should be done better - perhaps if the superblock
    // can be stored as an inode in the icache.
    for( m = &mtab[0]; m != &mtab_end; m++ )
    {
        // stop if there are more than the configured maximum
        if( m-&mtab[0] >= CYGNUM_FILEIO_MTAB_MAX )
        {
            m = &mtab_end;
            break;
        }
        if ( m->valid && strcmp( m->fsname, "jffs2" ) == 0 &&
            strcmp( m->devname, mte->devname) == 0 ) {
            jffs2_sb = (struct super_block *)m->data;
        }
    }

    if ( jffs2_sb == NULL) {
        jffs2_sb = malloc(sizeof(struct super_block));

        if(jffs2_sb == NULL)
            return ENOSPC;

	c = JFFS2_SB_INFO(jffs2_sb);
        memset(jffs2_sb, 0, sizeof(struct super_block));
        jffs2_sb->s_dev = t;

	err = jffs2_read_super(jffs2_sb);

	if (err) {
            free(jffs2_sb);
            return err;
        }

        jffs2_sb->s_root->i_parent = jffs2_sb->s_root; // points to itself, no dotdot paths above mountpoint
        jffs2_sb->s_root->i_cache_prev = NULL; // root inode, so always null
        jffs2_sb->s_root->i_cache_next = NULL;
        jffs2_sb->s_root->i_count = 1; // Ensures the root inode is always in ram until umount
    
        D2(printf("jffs2_mount erasing pending blocks\n"));
	jffs2_erase_pending_blocks(c);
	jffs2_mark_erased_blocks(c);
    }
    mte->data = (CYG_ADDRWORD)jffs2_sb;

    jffs2_sb->s_mount_count++;
    mte->root = (cyg_dir)jffs2_sb->s_root;
    D2(printf("jffs2_mounted superblock at %x\n", mte->root));

    return ENOERR;
}

// -------------------------------------------------------------------------
// jffs2_umount()
// Unmount the filesystem. 

static int jffs2_umount   ( cyg_mtab_entry *mte )
{
    struct inode *root = (struct inode *)mte->root;
    struct super_block *jffs2_sb = root->i_sb;
    struct jffs2_sb_info *c = JFFS2_SB_INFO(jffs2_sb);

    D2(printf("jffs2_umount\n"));

    // Decrement the mount count
    jffs2_sb->s_mount_count--;

    // Only really umount if this is the only mount
    if (jffs2_sb->s_mount_count == 0) {

        // Check for open/inuse root or any cached inodes
//if( root->i_count != 1 || root->i_cache_next != NULL) // root icount was set to 1 on mount
        if( root->i_cache_next != NULL) // root icount was set to 1 on mount
            return EBUSY;

        dec_refcnt(root); // Time to free the root inode

        //Clear root inode
        //root_i = NULL;

        // Clean up the super block and root inode
	jffs2_free_ino_caches(c);
	jffs2_free_raw_node_refs(c);
	free(c->blocks);

        // Clear root pointer
        mte->root = CYG_DIR_NULL;
    
        mte->fs->data = 0; // fstab entry, visible to all mounts. No current mount
        // That's all folks.
        D2(printf("jffs2_umount No current mounts\n"));
    }
        
    return ENOERR;
}

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

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

    jffs2_dirsearch ds;
    struct inode *node = NULL;
    int err;

    D2(printf("jffs2_open\n"));

    icache_evict((struct inode *)mte->root, (struct inode *)dir);

    init_dirsearch( &ds, (struct inode *)dir, name );
    
    err = jffs2_find( &ds );

    if( err == ENOENT )
    {
        if( ds.last && (mode & O_CREAT) )
        {
            unsigned long hash;
            struct qstr this;
            unsigned int c;
            const char * hashname;

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

            hashname = ds.name;
            this.name = hashname;
            c = *(const unsigned char *)hashname;

            hash = init_name_hash();
            do {
                hashname++;
                hash = partial_name_hash(c, hash);
                c = *(const unsigned char *)hashname;
            } while (c && (c != '/'));
            this.len = hashname - (const char *) this.name;
            this.hash = end_name_hash(hash);

            err = jffs2_create(ds.dir, &this, 0644, &node);

            if( err != 0 )
            {
                //Possible orphaned inode on the flash - but will be gc'd
                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.

        node->i_size = 0;

        // Update file times
        node->i_ctime =
        node->i_mtime = cyg_timestamp();
    }

    if( err != ENOERR ) return err;

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

    node->i_count++;       // 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         = &jffs2_fileops;
    file->f_offset      = (mode&O_APPEND) ? node->i_size : 0;
    file->f_data        = (CYG_ADDRWORD)node;  
    file->f_xops        = 0;

    return ENOERR;
}

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

static int jffs2_ops_unlink   ( cyg_mtab_entry *mte, cyg_dir dir, const char *name )
{
    unsigned long hash;
    struct qstr this;
    unsigned int c;
    const char * hashname;
    jffs2_dirsearch ds;
    int err;

    D2(printf("jffs2_ops_unlink\n"));

    icache_evict((struct inode *)mte->root, (struct inode *)dir);

    init_dirsearch( &ds, (struct inode *)dir, name );
    
    err = jffs2_find( &ds );

    if( err != ENOERR ) return err;

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

    // Delete it from its directory

    hashname = ds.name;
    this.name = hashname;
    c = *(const unsigned char *)hashname;

    hash = init_name_hash();
    do {
        hashname++;
        hash = partial_name_hash(c, hash);
        c = *(const unsigned char *)hashname;
    } while (c && (c != '/'));
    this.len = hashname - (const char *) this.name;
    this.hash = end_name_hash(hash);

    err = jffs2_unlink(ds.dir, ds.node, &this);
    
    return err;
}

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

static int jffs2_ops_mkdir    ( cyg_mtab_entry *mte, cyg_dir dir, const char *name )
{
    jffs2_dirsearch ds;
    struct inode *node = NULL;
    int err;

    D2(printf("jffs2_ops_mkdir\n"));

    icache_evict((struct inode *)mte->root, (struct inode *)dir);

    init_dirsearch( &ds, (struct inode *)dir, name );
    
    err = jffs2_find( &ds );

    if( err == ENOENT )
    {
        if( ds.last )
        {
            unsigned long hash;
            struct qstr this;
            unsigned int c;
            const char * hashname;
            // The entry does not exist, and it is the last element in
            // the pathname, so we can create it here.

            hashname = ds.name;
            this.name = hashname;
            c = *(const unsigned char *)hashname;

            hash = init_name_hash();
            do {
                hashname++;
                hash = partial_name_hash(c, hash);
                c = *(const unsigned char *)hashname;
            } while (c && (c != '/'));
            this.len = hashname - (const char *) this.name;
            this.hash = end_name_hash(hash);

            err = jffs2_mkdir(ds.dir, &this, 0, &node);

            if( err != 0 )
                return ENOSPC;

        }
        // 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;
}

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

static int jffs2_ops_rmdir    ( cyg_mtab_entry *mte, cyg_dir dir, const char *name )
{
    unsigned long hash;
    struct qstr this;
    unsigned int c;
    const char * hashname;
    jffs2_dirsearch ds;
    int err;

    D2(printf("jffs2_ops_rmdir\n"));

    icache_evict((struct inode *)mte->root, (struct inode *)dir);

    init_dirsearch( &ds, (struct inode *)dir, name );
    
    err = jffs2_find( &ds );

    if( err != ENOERR ) return err;

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

    // Delete the entry. 
    hashname = ds.name;
    this.name = hashname;
    c = *(const unsigned char *)hashname;

    hash = init_name_hash();
    do {
        hashname++;
        hash = partial_name_hash(c, hash);
        c = *(const unsigned char *)hashname;
    } while (c && (c != '/'));
    this.len = hashname - (const char *) this.name;
    this.hash = end_name_hash(hash);

    err = jffs2_rmdir(ds.dir, ds.node, &this);
    
    return err;

    return ENOERR;
}

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

static int jffs2_ops_rename   ( cyg_mtab_entry *mte, cyg_dir dir1, const char *name1,
                             cyg_dir dir2, const char *name2 )
{
    unsigned long hash;
    struct qstr this1,this2;
    unsigned int c;
    const char * hashname;
    jffs2_dirsearch ds1, ds2;
    int err;

    D2(printf("jffs2_ops_rename\n"));

    init_dirsearch( &ds1, (struct inode *)dir1, name1 );
    
    err = jffs2_find( &ds1 );

    if( err != ENOERR ) return err;

    init_dirsearch( &ds2, (struct inode *)dir2, name2 );
    
    err = jffs2_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;

    hashname = ds1.name;
    this1.name = hashname;
    c = *(const unsigned char *)hashname;

    hash = init_name_hash();
    do {
        hashname++;
        hash = partial_name_hash(c, hash);
        c = *(const unsigned char *)hashname;
    } while (c && (c != '/'));
    this1.len = hashname - (const char *) this1.name;
    this1.hash = end_name_hash(hash);

    hashname = ds2.name;
    this2.name = hashname;
    c = *(const unsigned char *)hashname;

    hash = init_name_hash();
    do {
        hashname++;
        hash = partial_name_hash(c, hash);
        c = *(const unsigned char *)hashname;
    } while (c && (c != '/'));
    this2.len = hashname - (const char *) this2.name;
    this2.hash = end_name_hash(hash);

    // 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->i_mode) && S_ISDIR(ds2.node->i_mode) )
            return EISDIR;

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

        // Now delete the destination directory entry
        
        err = jffs2_unlink(ds2.dir, ds2.node, &this2);
    
        if( err != 0 ) 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  = jffs2_rename(ds1.dir, ds1.node, &this1, ds2.dir, &this2);

    // Update directory times
    if( err == 0 )
        ds1.dir->i_ctime =
        ds1.dir->i_mtime =
        ds2.dir->i_ctime =
        ds2.dir->i_mtime = cyg_timestamp();
            
    return err;
}

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

static int jffs2_ops_link     ( cyg_mtab_entry *mte, cyg_dir dir1, const char *name1,
                             cyg_dir dir2, const char *name2, int type )
{
    unsigned long hash;
    struct qstr this;
    unsigned int c;
    const char * hashname;
    jffs2_dirsearch ds1, ds2;
    int err;

    D2(printf("jffs2_ops_link\n"));

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

    if( err != ENOERR ) return err;

    init_dirsearch( &ds2, (struct inode *)dir2, name2 );
    
    err = jffs2_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.

    hashname = ds2.name;
    this.name = hashname;
    c = *(const unsigned char *)hashname;

    hash = init_name_hash();
    do {
        hashname++;
        hash = partial_name_hash(c, hash);
        c = *(const unsigned char *)hashname;
    } while (c && (c != '/'));
    this.len = hashname - (const char *) this.name;
    this.hash = end_name_hash(hash);

    err = jffs2_link(ds2.dir, ds1.node, &this);

    if( err == 0 )
        ds1.node->i_ctime =
        ds2.dir->i_ctime =
        ds2.dir->i_mtime = cyg_timestamp();
            
    return err;
}

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

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

    D2(printf("jffs2_opendir\n"));

    icache_evict((struct inode *)mte->root, (struct inode *)dir);

    init_dirsearch( &ds, (struct inode *)dir, name );
    
    err = jffs2_find( &ds );

    if( err != ENOERR ) return err;

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

    ds.node->i_count++;       // 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         = &jffs2_dirops;
    file->f_offset      = 0;
    file->f_data        = (CYG_ADDRWORD)ds.node;
    file->f_xops        = 0;

        
    return ENOERR;

}

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

static int jffs2_chdir    ( cyg_mtab_entry *mte, cyg_dir dir, const char *name,
                             cyg_dir *dir_out )
{
    D2(printf("jffs2_chdir\n"));

    if( dir_out != NULL )
    {
        // This is a request to get a new directory pointer in
        // *dir_out.

        jffs2_dirsearch ds;
        int err;

        icache_evict((struct inode *)mte->root, (struct inode *)dir);

        init_dirsearch( &ds, (struct inode *)dir, name );
    
        err = jffs2_find( &ds );

        if( err != ENOERR ) return err;

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

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

        struct inode *node = (struct inode *)dir;

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

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

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

    D2(printf("jffs2_stat\n"));

    icache_evict((struct inode *)mte->root, (struct inode *)dir);

    init_dirsearch( &ds, (struct inode *)dir, name );
    
    err = jffs2_find( &ds );

    if( err != ENOERR ) return err;

    // Fill in the status
    buf->st_mode        = ds.node->i_mode;
    buf->st_ino         = (ino_t)ds.node;
    buf->st_dev         = 0;
    buf->st_nlink       = ds.node->i_nlink;
    buf->st_uid         = 0;
    buf->st_gid         = 0;
    buf->st_size        = ds.node->i_size;
    buf->st_atime       = ds.node->i_atime;
    buf->st_mtime       = ds.node->i_mtime;
    buf->st_ctime       = ds.node->i_ctime;
    
    return err;

    return ENOERR;
}

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

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

    D2(printf("jffs2_getinfo\n"));

    icache_evict((struct inode *)mte->root, (struct inode *)dir);

    init_dirsearch( &ds, (struct inode *)dir, name );
    
    err = jffs2_find( &ds );

    if( err != ENOERR ) return err;

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

    return ENOERR;
}

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

static int jffs2_setinfo  ( cyg_mtab_entry *mte, cyg_dir dir, const char *name,
                             int key, void *buf, int len )
{
    // No setinfo keys supported at present
    
    D2(printf("jffs2_setinfo\n"));

    return EINVAL;
}


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

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

static int jffs2_fo_read      (struct CYG_FILE_TAG *fp, struct CYG_UIO_TAG *uio)
{
    struct inode *inode = (struct inode *)fp->f_data;
    struct jffs2_inode_info *f = JFFS2_INODE_INFO(inode);
    struct jffs2_sb_info *c = JFFS2_SB_INFO(inode->i_sb);
    int i;
    ssize_t resid = uio->uio_resid;
    off_t pos = fp->f_offset;

    down(&f->sem);

    // Loop over the io vectors until there are none left
    for( i = 0; i < uio->uio_iovcnt && pos < inode->i_size; i++ )
    {
	int ret;
        cyg_iovec *iov = &uio->uio_iov[i];
        off_t len = min(iov->iov_len, inode->i_size - pos);

        D2(printf("jffs2_fo_read inode size %d\n", inode->i_size));

	ret = jffs2_read_inode_range(c, f, (unsigned char *)iov->iov_base, 
				     pos, len);
	if (ret) {
		D1(printf("jffs2_fo_read(): read_inode_range failed %d\n",
			  ret));
		uio->uio_resid = resid;
		up(&f->sem);
		return -ret;
	}
	resid -= len;
	pos += len;
    }

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

    uio->uio_resid = resid;
    fp->f_offset = pos;

    up(&f->sem);
    
    return ENOERR;
}

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

static int jffs2_fo_write     (struct CYG_FILE_TAG *fp, struct CYG_UIO_TAG *uio)
{
    struct page write_page;
    off_t page_start_pos;
    struct inode *node = (struct inode *)fp->f_data;
    off_t pos = fp->f_offset;
    ssize_t resid = uio->uio_resid;    
    int i;

    memset(&read_write_buffer, 0, PAGE_CACHE_SIZE);
    write_page.virtual = &read_write_buffer;

    // 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->i_size;
    
    // Check that pos is within current file size, or at the very end.
    if( pos < 0 || pos > node->i_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;
            size_t writtenlen;
            off_t l = len;
            int err;
            
            write_page.index = 0;

            page_start_pos = pos;
            while(page_start_pos >= (PAGE_CACHE_SIZE)) {
                write_page.index++;
                page_start_pos -= PAGE_CACHE_SIZE;
            }

            if( l > PAGE_CACHE_SIZE - page_start_pos)
                l = PAGE_CACHE_SIZE - page_start_pos;

            D2(printf("jffs2_fo_write write_page.index %d\n", write_page.index));
            D2(printf("jffs2_fo_write page_start_pos %d\n", page_start_pos));
            D2(printf("jffs2_fo_write transfer size %d\n", l));

            err = jffs2_prepare_write(node, &write_page, page_start_pos, page_start_pos + l);

            if( err != 0 )
                return err;
            
            // copy data in
            memcpy(&read_write_buffer[page_start_pos], buf, l);

            writtenlen = jffs2_commit_write(node, &write_page, page_start_pos, page_start_pos + l);
           
            if( writtenlen != l )
                return ENOSPC;
            
            // 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->i_mtime =
    node->i_ctime = cyg_timestamp();
    if( pos > node->i_size )
        node->i_size = pos;    

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

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

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

    D2(printf("jffs2_fo_lseek\n"));

    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->i_size;
        break;

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

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

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

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

    D2(printf("jffs2_fo_ioctl\n"));

    return EINVAL;
}

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

static int jffs2_fo_fsync     (struct CYG_FILE_TAG *fp, int mode )
{
    // Data is always permanently where it belongs, nothing to do
    // here.
  
    D2(printf("jffs2_fo_fsync\n"));

    return ENOERR;
}

// -------------------------------------------------------------------------
// jffs2_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 jffs2_fo_close     (struct CYG_FILE_TAG *fp)
{
    struct inode *node = (struct inode *)fp->f_data;

    D2(printf("jffs2_fo_close\n"));

    dec_refcnt( node );

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

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

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

    D2(printf("jffs2_fo_fstat\n"));

    // Fill in the status
    buf->st_mode        = node->i_mode;
    buf->st_ino         = (ino_t)node;
    buf->st_dev         = 0;
    buf->st_nlink       = node->i_nlink;
    buf->st_uid         = 0;
    buf->st_gid         = 0;
    buf->st_size        = node->i_size;
    buf->st_atime       = node->i_atime;
    buf->st_mtime       = node->i_mtime;
    buf->st_ctime       = node->i_ctime;
    
    return ENOERR;
}

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

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

    D2(printf("jffs2_fo_getinfo\n"));

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

    return ENOERR;
}

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

static int jffs2_fo_setinfo   (struct CYG_FILE_TAG *fp, int key, void *buf, int len )
{
    // No setinfo key supported at present
    
    D2(printf("jffs2_fo_setinfo\n"));

    return ENOERR;
}


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

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

static __inline void
filldir(char *nbuf, int nlen, const char * name, int namlen)
{
    int len = nlen < namlen ? nlen : namlen;
    memcpy(nbuf, name, len);
    nbuf[len] = '\0';
}

static int jffs2_fo_dirread      (struct CYG_FILE_TAG *fp, struct CYG_UIO_TAG *uio)
{
    struct inode *d_inode = (struct inode *)fp->f_data;
    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;
	struct jffs2_inode_info *f;
	struct jffs2_sb_info *c;
	struct inode *inode = d_inode;
	struct jffs2_full_dirent *fd;
	unsigned long offset, curofs;
	int found = 1;

    if( len < sizeof(struct dirent) )
        return EINVAL;

	D1(printk(KERN_DEBUG "jffs2_readdir() for dir_i #%lu\n", d_inode->i_ino));

	f = JFFS2_INODE_INFO(inode);
	c = JFFS2_SB_INFO(inode->i_sb);

	offset = fp->f_offset;

	if (offset == 0) {
		D1(printk(KERN_DEBUG "Dirent 0: \".\", ino #%lu\n", inode->i_ino));
		filldir(nbuf, nlen, ".", 1);
        goto out;
	}
	if (offset == 1) {
		filldir(nbuf, nlen, "..", 2);
        goto out;
	}

	curofs=1;
	down(&f->sem);
	for (fd = f->dents; fd; fd = fd->next) {

		curofs++;
		/* First loop: curofs = 2; offset = 2 */
		if (curofs < offset) {
			D2(printk(KERN_DEBUG "Skipping dirent: \"%s\", ino #%u, type %d, because curofs %ld < offset %ld\n", 
				  fd->name, fd->ino, fd->type, curofs, offset));
			continue;
		}
		if (!fd->ino) {
			D2(printk(KERN_DEBUG "Skipping deletion dirent \"%s\"\n", fd->name));
			offset++;
			continue;
		}
		D2(printk(KERN_DEBUG "Dirent %ld: \"%s\", ino #%u, type %d\n", offset, fd->name, fd->ino, fd->type));
		filldir(nbuf, nlen, fd->name, strlen(fd->name));
        goto out_sem;
	}
	/* Reached the end of the directory */
	found = 0;
 out_sem:
	up(&f->sem);
 out:
    fp->f_offset = ++offset;
    if (found) {
        uio->uio_resid -= sizeof(struct dirent);
    }
	return ENOERR;
}

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

static int jffs2_fo_dirlseek     (struct CYG_FILE_TAG *fp, off_t *pos, int whence )
{
    // Only allow SEEK_SET to zero
    
    D2(printf("jffs2_fo_dirlseek\n"));

    if( whence != SEEK_SET || *pos != 0)
        return EINVAL;

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


//==========================================================================
// 
// Called by JFFS2
// ===============
// 
//
//==========================================================================


struct page *read_cache_page(unsigned long index,
				int (*filler)(void *,struct page*),
				void *data)
{
        // Only called in gc.c jffs2_garbage_collect_dnode
        // but gets a real page for the specified inode

	int err;
	struct page *gc_page = malloc(sizeof(struct page));

printf("read_cache_page\n");
        memset(&gc_buffer, 0, PAGE_CACHE_SIZE);

        if(gc_page != NULL) {
            gc_page->virtual = &gc_buffer;
            gc_page->index = index;

	    err = filler(data, gc_page);
	    if (err < 0) {
                free(gc_page);
                gc_page = NULL;
	    }
        }

	return gc_page;
}

void page_cache_release(struct page * pg) {

        // Only called in gc.c jffs2_garbage_collect_dnode
        // but should free the page malloc'd by read_cache_page

printf("page_cache_release\n");
        free(pg);
}

struct inode * new_inode(struct super_block *sb) {

        // Only called in write.c jffs2_new_inode
        // Always adds itself to inode cache

        struct inode * inode;
        struct inode * cached_inode;

	inode = malloc(sizeof(struct inode));
	if (inode == NULL)
                return 0;

        D2(printf("malloc new_inode %x ####################################\n", inode));

        memset(inode, 0, sizeof(struct inode));
        inode->i_sb = sb;
        inode->i_ino = 1;
	inode->i_count = 0;//1; // Let ecos manage the open count

        inode->i_nlink = 1; // Let JFFS2 manage the link count
        inode->i_size = 0;

        inode->i_cache_next = NULL; // Newest inode, about to be cached

        // Add to the icache
        for(cached_inode = sb->s_root; cached_inode != NULL; cached_inode = cached_inode->i_cache_next) {
            if (cached_inode->i_cache_next == NULL) {
                cached_inode->i_cache_next = inode; // Current last in cache points to newcomer
                inode->i_cache_prev = cached_inode; // Newcomer points back to last
                break;
            }
        }

	return inode;
}

struct inode * iget(struct super_block *sb, cyg_uint32 ino) {

        // Substitute for iget drops straight through to reading the 
        // inode from disk if it is not in the inode cache

        // Called in super.c jffs2_read_super, dir.c jffs2_lookup,
        // and gc.c jffs2_garbage_collect_pass

        // Must first check for cached inode 
        // If this fails let new_inode create one

	struct inode * inode;

        D2(printf("iget\n"));

        // Check for this inode in the cache
        for(inode = sb->s_root; inode != NULL; inode = inode->i_cache_next) {
            if (inode->i_ino == ino)
                return inode;
        }
        inode = NULL;

        // Not cached, so malloc it
        inode = new_inode(sb);
	if (inode == NULL)
                return 0;

        inode->i_ino = ino;
        jffs2_read_inode(inode);
	
	return inode;
}

void iput(struct inode * i) {

        // Called in dec_refcnt, jffs2_find 
        // (and jffs2_open and jffs2_ops_mkdir?)
        // super.c jffs2_read_super,
        // and gc.c jffs2_garbage_collect_pass

        struct inode *cached_inode;

        D2(printf("free iput inode %x $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$\n", i));
	if (i && i->i_count) {
		/* Added by dwmw2. iget/iput in Linux track the use count,
		 don't just unconditionally free it */
		printf("iput called for used inode\n");
		return;
	}
        if (i != NULL) {
            // Remove from the icache
            for(cached_inode = i->i_sb->s_root; cached_inode != NULL; cached_inode = cached_inode->i_cache_next) {
                if (cached_inode == i) {
                    cached_inode->i_cache_prev->i_cache_next = cached_inode->i_cache_next; // Prveious entry points ahead of us
                    if (cached_inode->i_cache_next != NULL)
                        cached_inode->i_cache_next->i_cache_prev = cached_inode->i_cache_prev; // Next entry points behind us
                    break;
                }
            }
            // inode has been seperated from the cache
            jffs2_clear_inode(i);
            free(i);
        }
}

static int return_EIO(void)
{
	return -EIO;
}

#define EIO_ERROR ((void *) (return_EIO))

void make_bad_inode(struct inode * inode) {

        // In readinode.c JFFS2 checks whether the inode has appropriate
        // content for its marked type

        D2(printf("make_bad_inode\n"));

	inode->i_mode = S_IFREG;
	inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
	inode->i_op = EIO_ERROR;	
	inode->i_fop = EIO_ERROR;	
}

int is_bad_inode(struct inode * inode) {

        // Called in super.c jffs2_read_super,
        // and gc.c jffs2_garbage_collect_pass

        D2(printf("is_bad_inode\n"));

	return (inode->i_op == EIO_ERROR);	
        /*if(i == NULL)
                return 1;
        return 0;*/
}

cyg_bool jffs2_flash_read(struct jffs2_sb_info *c, cyg_uint32 read_buffer_offset, const size_t size, size_t * return_size, char * write_buffer) {
    Cyg_ErrNo err;
    cyg_uint32 len = size;
    struct super_block *sb = OFNI_BS_2SFFJ( c );

        //D2(printf("FLASH READ\n"));
        //D2(printf("read address = %x\n", CYGNUM_FS_JFFS2_BASE_ADDRESS + read_buffer_offset));
        //D2(printf("write address = %x\n", write_buffer));
        //D2(printf("size = %x\n", size));
    err = cyg_io_bread( sb->s_dev, write_buffer, &len, read_buffer_offset );
    
	*return_size = (size_t)len;
    return ( err != ENOERR );
}

cyg_bool jffs2_flash_write(struct jffs2_sb_info *c, cyg_uint32 write_buffer_offset, const size_t size, size_t * return_size, char * read_buffer) {

    Cyg_ErrNo err;
    cyg_uint32 len = size;
    struct super_block *sb = OFNI_BS_2SFFJ( c );
    
    //    D2(printf("FLASH WRITE ENABLED!!!\n"));
    //    D2(printf("write address = %x\n", CYGNUM_FS_JFFS2_BASE_ADDRESS + write_buffer_offset));
    //    D2(printf("read address = %x\n", read_buffer));
    //    D2(printf("size = %x\n", size));

    err = cyg_io_bwrite( sb->s_dev, read_buffer, &len, write_buffer_offset );
    *return_size = (size_t)len;

    return ( err != ENOERR );
}

int
jffs2_flash_writev(struct jffs2_sb_info *c, const struct iovec *vecs,
                   unsigned long count, loff_t to, size_t *retlen)
{
	unsigned long i;
	size_t totlen = 0, thislen;
	int ret = 0;
    
	for (i=0; i<count; i++) {
        // writes need to be aligned but the data we're passed may not be
        // Observation suggests most unaligned writes are small, so we
        // optimize for that case.

        if ( ((vecs[i].iov_len & (sizeof(int)-1))) ||
             (((unsigned long)vecs[i].iov_base & (sizeof(unsigned long)-1))) )
        {
            // are there iov's after this one? Or is it so much we'd need
            // to do multiple writes anyway?
            if ( (i+1)<count || vecs[i].iov_len > 256 )
            {
                // cop out and malloc
                unsigned long j;
                ssize_t sizetomalloc=0, totvecsize=0;
                char *cbuf, *cbufptr;

                for (j=i;j<count; j++)
                    totvecsize += vecs[j].iov_len;
                
                // pad up in case unaligned
                sizetomalloc = totvecsize + sizeof(int)-1;
                sizetomalloc &= ~(sizeof(int)-1);
                cbuf = (char *)malloc( sizetomalloc );
                // malloc returns aligned memory
                if (!cbuf)
                {
                    ret = -ENOMEM;
                    goto writev_out;
                }
                cbufptr = cbuf;
                for (j=i;j<count; j++)
                {
                    memcpy( cbufptr, vecs[j].iov_base, vecs[j].iov_len );
                    cbufptr += vecs[j].iov_len;
                }
                ret = jffs2_flash_write(c, to, sizetomalloc, &thislen, cbuf );
                if ( thislen > totvecsize ) // in case it was aligned up
                    thislen = totvecsize;
                totlen += thislen;
                free(cbuf);
                goto writev_out;
            }
            else
            {
                // otherwise optimize for the common case
                int buf[256/sizeof(int)]; // int, so int aligned
                size_t lentowrite;
                    
                lentowrite = vecs[i].iov_len;
                // pad up in case its unaligned
                lentowrite += sizeof(int)-1;
                lentowrite &= ~(sizeof(int)-1);
                memcpy( buf, vecs[i].iov_base, lentowrite );
                    
                ret = jffs2_flash_write(c, to, lentowrite, &thislen,
                                        (char *)&buf );
                if ( thislen > vecs[i].iov_len )
                    thislen = vecs[i].iov_len;
            } // else
        }
        else
            ret = jffs2_flash_write(c, to, vecs[i].iov_len, &thislen,
                                    vecs[i].iov_base);
		totlen += thislen;
		if (ret || thislen != vecs[i].iov_len)
			break;
		to += vecs[i].iov_len;
	}
writev_out:
	if (retlen)
		*retlen = totlen;

	return ret;
}

cyg_bool jffs2_flash_erase(struct jffs2_sb_info *c, struct jffs2_eraseblock *jeb) {
    cyg_io_flash_getconfig_erase_t e;
    void *err_addr;
    Cyg_ErrNo err;
    cyg_uint32 len = sizeof(e);
    struct super_block *sb = OFNI_BS_2SFFJ( c );

    e.offset = jeb->offset;
    e.len = c->sector_size;
    e.err_address = &err_addr;

    //        D2(printf("FLASH ERASE ENABLED!!!\n"));
    //        D2(printf("erase address = %x\n", CYGNUM_FS_JFFS2_BASE_ADDRESS + jeb->offset));
    //        D2(printf("size = %x\n", c->sector_size));

    err = cyg_io_get_config( sb->s_dev, CYG_IO_GET_CONFIG_FLASH_ERASE,
                             &e, &len );

    return ( err != ENOERR || e.flasherr != 0 );
}

// -------------------------------------------------------------------------
// EOF jffs2.c
