/*
 * vfs.c - Virtual File System implementation.
 *
 * Author  Mohamed Lamine Karaoui (2015)
 *         Alain Greiner (2016)
 *
 * Copyright (c) UPMC Sorbonne Universites
 *
 * This file is part of ALMOS-MKH.
 *
 * ALMOS-MKH 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; version 2.0 of the License.
 *
 * ALMOS-MKH 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 ALMOS-MKH; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */


#include <almos_config.h>
#include <hal_types.h>
#include <hal_atomic.h>
#include <hal_special.h>
#include <readlock.h>
#include <spinlock.h>
#include <printk.h>
#include <list.h>
#include <xlist.h>
#include <slist.h>
#include <xhtab.h>
#include <errno.h>
#include <kmem.h>
#include <mapper.h>
#include <thread.h>
#include <process.h>
#include <fatfs.h>
#include <ramfs.h>
#include <vfs.h>


//////////////////////////////////////////////////////////////////////////////////////////
//           Global variables         
//////////////////////////////////////////////////////////////////////////////////////////

// array of supported FS contexts (indexed by the FS type)
vfs_ctx_t   fs_context[FS_TYPES_NR];

//////////////////////////////////////////////////////////////////////////////////////////
//           Context related functions
//////////////////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////
error_t vfs_ctx_inum_alloc( vfs_ctx_t * ctx,
                            uint32_t  * inum )
{
    // get lock on inum allocator
    spinlock_lock( &ctx->lock );

    // get lid from local inum allocator
    uint32_t lid = bitmap_ffc( ctx->inum , CONFIG_VFS_MAX_INODES );

    if( lid == -1 )   // no more free slot => error
    {
        // release lock
        spinlock_unlock( &ctx->lock );

        // return error
        return 1;
    }
    else              // found => return inum
    {
        // set slot allocated
        bitmap_set( ctx->inum , lid );

        // release lock
        spinlock_unlock( &ctx->lock );

        // return inum
        *inum = (((uint32_t)local_cxy) << 16) | (lid & 0xFFFF);
        return 0;
    }
}

////////////////////////////////////////////
void vfs_ctx_inum_release( vfs_ctx_t * ctx,
                           uint32_t    inum )
{
    bitmap_clear( ctx->inum , inum & 0xFFFF ); 
}

//////////////////////////////////////////////////////////////////////////////////////////
//           Inode related functions
//////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////
error_t vfs_inode_create( xptr_t      dentry_xp,
                          uint32_t    type,
                          uint32_t    attr,
                          uint32_t    mode,
                          uid_t       uid,
                          gid_t       gid,
                          xptr_t    * inode_xp )
{
    mapper_t         * mapper;     // associated mapper( to be allocated)
    vfs_inode_t      * inode;      // inode descriptor (to be allocated)
    uint32_t           inum;       // inode identifier (to be allocated)
    vfs_ctx_t        * ctx;        // file system context 
	kmem_req_t         req;        // request to kernel memory allocator
    error_t            error;

    // check type and get pointer on context
    if     ( type == FS_TYPE_FATFS ) ctx = &fs_context[FS_TYPE_FATFS];
    else if( type == FS_TYPE_RAMFS ) ctx = &fs_context[FS_TYPE_RAMFS];
    else
    {
        ctx = NULL;
        printk("\n[PANIC] in %s : undefined file system type\n", __FUNCTION__ );
        hal_core_sleep();
    }

    // allocate inum
    error = vfs_ctx_inum_alloc( ctx , &inum );

    if( error )
    {
        printk("\n[ERROR] in %s : cannot allocate inum\n", __FUNCTION__ );
        return ENOMEM;
    }

    // allocate memory for mapper
    mapper = mapper_create();

    if( mapper == NULL )
    {
        printk("\n[ERROR] in %s : cannot allocate mapper\n", __FUNCTION__ );
        vfs_ctx_inum_release( ctx , inum );
        return ENOMEM;
    }

    // allocate memory for inode descriptor
	req.type  = KMEM_VFS_INODE;
	req.size  = sizeof(vfs_inode_t);
    req.flags = AF_KERNEL | AF_ZERO;
	inode     = (vfs_inode_t *)kmem_alloc( &req );

    if( inode == NULL )
    {
        printk("\n[ERROR] in %s : cannot allocate inode descriptor\n", __FUNCTION__ );
        vfs_ctx_inum_release( ctx , inum );
        mapper_destroy( mapper );
        return ENOMEM;
    }

    // initialize inode descriptor
    inode->gc         = 0;
    inode->inum       = inum;
    inode->attr       = attr;
    inode->mode       = mode;
    inode->uid        = uid;
    inode->gid        = gid;
    inode->refcount   = 0;
    inode->parent_xp  = dentry_xp;
    inode->ctx        = ctx;
    inode->mapper     = NULL;  

    // initialise threads waiting queue
    xlist_root_init( XPTR( local_cxy , &inode->wait_root ) );

    // initialize dentries hash table, if new inode is a directory
    if( attr & INODE_ATTR_DIR )  xhtab_init( &inode->children , XHTAB_DENTRY_TYPE );

    // initialize inode locks 
    remote_rwlock_init( XPTR( local_cxy , &inode->size_lock ) );
    remote_spinlock_init( XPTR( local_cxy , &inode->main_lock ) );

    // create FS specific inode
    if     ( ctx->type == FS_TYPE_FATFS )  fatfs_inode_create( inode );
    else if( ctx->type == FS_TYPE_RAMFS )  ramfs_inode_create( inode );

    // return extended pointer on inode
    *inode_xp = XPTR( local_cxy , inode );
    return 0;

}  // end vfs_inode_create()  

/////////////////////////////////////////////
void vfs_inode_destroy( vfs_inode_t * inode )
{
    if( inode->refcount )
    {
        printk("\n[PANIC] in %s : inode refcount non zero\n", __FUNCTION__ );
        hal_core_sleep(); 
    }       

    // release memory allocated for mapper
    mapper_destroy( inode->mapper );

    // release memory allocate for inode descriptor
	kmem_req_t req;
	req.ptr   = inode;
	req.type  = KMEM_VFS_INODE;
	kmem_free( &req );

}  // end vfs_inode_destroy()

////////////////////////////////////////////
void vfs_inode_remote_up( xptr_t  inode_xp )
{
    // get inode cluster and local pointer
    cxy_t         inode_cxy = GET_CXY( inode_xp );
    vfs_inode_t * inode_ptr = (vfs_inode_t *)GET_PTR( inode_xp );

    hal_remote_atomic_add( XPTR( inode_cxy , &inode_ptr->refcount ) , 1 );    
}

//////////////////////////////////////////////
void vfs_inode_remote_down( xptr_t  inode_xp )
{
    // get inode cluster and local pointer
    cxy_t         inode_cxy = GET_CXY( inode_xp );
    vfs_inode_t * inode_ptr = (vfs_inode_t *)GET_PTR( inode_xp );

    hal_remote_atomic_add( XPTR( inode_cxy , &inode_ptr->refcount ) , -1 );    
}

//////////////////////////////////////////////
uint32_t vfs_inode_get_size( xptr_t inode_xp )
{
    // get inode cluster and local pointer
    cxy_t         cxy = GET_CXY( inode_xp );
    vfs_inode_t * ptr = (vfs_inode_t *)GET_PTR( inode_xp );

    // get size
    remote_rwlock_rd_lock( XPTR( cxy , &ptr->size_lock ) );
    uint32_t size = hal_remote_lw( XPTR( cxy , &ptr->size ) );
    remote_rwlock_rd_unlock( XPTR( cxy , &ptr->size_lock ) );
    return size;
}

/////////////////////////////////////////////////
void vfs_inode_size_set_size( xptr_t    inode_xp,
                              uint32_t  size )
{
    // get inode cluster and local pointer
    cxy_t         cxy = GET_CXY( inode_xp );
    vfs_inode_t * ptr = (vfs_inode_t *)GET_PTR( inode_xp );

    // set size
    remote_rwlock_wr_unlock( XPTR( cxy , &ptr->size_lock ) );
    hal_remote_sw( XPTR( cxy , &ptr->size ) , size );
    remote_rwlock_wr_unlock( XPTR( cxy , &ptr->size_lock ) );
}

///////////////////////////////////////////////
void vfs_inode_remote_unlock( xptr_t inode_xp )
{
    // get inode cluster and local pointer
    cxy_t         cxy = GET_CXY( inode_xp );
    vfs_inode_t * ptr = (vfs_inode_t *)GET_PTR( inode_xp );

    // release the main lock
    remote_spinlock_unlock( XPTR( cxy , &ptr->main_lock ) );
}

/////////////////////////////////////////////
void vfs_inode_remote_lock( xptr_t inode_xp )
{
    // get inode cluster and local pointer
    cxy_t         cxy = GET_CXY( inode_xp );
    vfs_inode_t * ptr = (vfs_inode_t *)GET_PTR( inode_xp );

    // get the main lock
    remote_spinlock_lock( XPTR( cxy , &ptr->main_lock ) );
}

//////////////////////////////////////////////////////////////////////////////////////////
//           Dentry related functions
//////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////
error_t vfs_dentry_create( uint32_t      type,
                           char        * name,
                           vfs_inode_t * parent,
                           xptr_t      * dentry_xp )
{
    vfs_ctx_t      * ctx;        // context descriptor
    vfs_dentry_t   * dentry;     // dentry descriptor (to be allocated)
	kmem_req_t       req;        // request to kernel memory allocator
    xptr_t           xhtab_xp;   // extended pointer on xhtab_t embedded in inode
    xptr_t           xlist_xp;   // extended pointer on xlist_entry_t in dentry

    // check type and get pointer on context
    if     ( type == FS_TYPE_FATFS ) ctx = &fs_context[FS_TYPE_FATFS];
    else if( type == FS_TYPE_RAMFS ) ctx = &fs_context[FS_TYPE_RAMFS];
    else
    {
        ctx = NULL;
        printk("\n[PANIC] in %s : undefined file system type\n", __FUNCTION__ );
        hal_core_sleep();
    }

    // get name length
    uint32_t length = strlen( name );

    if( length > (CONFIG_VFS_MAX_NAME_LENGTH - 1) )
    {
        printk("\n[ERROR] in %s : name too long\n", __FUNCTION__ );
        return EINVAL;
    }

    // allocate memory for dentry descriptor
	req.type  = KMEM_VFS_DENTRY;
	req.size  = sizeof(vfs_dentry_t);
    req.flags = AF_KERNEL | AF_ZERO;
	dentry     = (vfs_dentry_t *)kmem_alloc( &req );

    if( dentry == NULL )
    {
        printk("\n[ERROR] in %s : cannot allocate dentry descriptor\n", __FUNCTION__ );
        return ENOMEM;
    }

    // initialize dentry descriptor
    dentry->ctx     = ctx;
    dentry->length  = length;
    dentry->parent  = parent;
    strcpy( dentry->name , name );

    // return extended pointer on dentry to caller
    *dentry_xp = XPTR( local_cxy , dentry );

    // register dentry in hash table rooted in parent inode
    xhtab_xp    = XPTR( local_cxy , &parent->children );
    xlist_xp    = XPTR( local_cxy , &dentry->xlist );
    xhtab_register( xhtab_xp  , name , xlist_xp );
    
    return 0;

}  // end vfs_dentry_create()

////////////////////////////////////////////////
void vfs_dentry_destroy( vfs_dentry_t * dentry )
{
    if( dentry->refcount )
    {
        printk("\n[PANIC] in %s : dentry refcount non zero\n", __FUNCTION__ );
        hal_core_sleep(); 
    }       

	kmem_req_t req;
	req.ptr   = dentry;
	req.type  = KMEM_VFS_DENTRY;
	kmem_free( &req );
}


//////////////////////////////////////////////////////////////////////////////////////////
//           File descriptor related functions
//////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////
void vfs_file_count_up( xptr_t file_xp )
{
    // get file cluster and local pointer
    cxy_t        file_cxy = GET_CXY( file_xp );
    vfs_file_t * file_ptr = (vfs_file_t *)GET_PTR( file_xp ); 

    // atomically increment count
    hal_remote_atomic_add( XPTR( file_cxy , &file_ptr->refcount ) , 1 ); 
}

//////////////////////////////////////////
void vfs_file_count_down( xptr_t file_xp )
{
    // get file cluster and local pointer
    cxy_t        file_cxy = GET_CXY( file_xp );
    vfs_file_t * file_ptr = (vfs_file_t *)GET_PTR( file_xp ); 

    // atomically decrement count
    hal_remote_atomic_add( XPTR( file_cxy , &file_ptr->refcount ) , -1 ); 
}

////////////////////////////////////////////////
error_t vfs_file_create( xptr_t        inode_xp,
                         uint32_t      type,
                         uint32_t      attr,
                         xptr_t      * file_xp )  
{
    vfs_file_t  * file_ptr;
	kmem_req_t    req;

    // get inode cluster and local pointer
    cxy_t         inode_cxy = GET_CXY( inode_xp );
    vfs_inode_t * inode_ptr = (vfs_inode_t *)GET_PTR( inode_xp );

    // check cluster identifier
    if( inode_cxy != local_cxy )
    {
        printk("\n[PANIC] in %s : local cluster is not the inode owner\n", __FUNCTION__ );
        hal_core_sleep();
    }

    // allocate memory for new file descriptor
	req.type  = KMEM_VFS_FILE;
	req.size  = sizeof(vfs_file_t);
    req.flags = AF_KERNEL | AF_ZERO;
	file_ptr  = (vfs_file_t *)kmem_alloc( &req );

    if( file_ptr == NULL ) return ENOMEM;

    // get inode local pointer
    // initializes new file descriptor
    file_ptr->gc       = 0;
    file_ptr->type     = type;
    file_ptr->attr     = attr;
    file_ptr->offset   = 0;
    file_ptr->refcount = 0;
    file_ptr->inode    = inode_ptr;
    file_ptr->ctx      = inode_ptr->ctx;
    file_ptr->mapper   = inode_ptr->mapper;

    remote_rwlock_init( XPTR( local_cxy , &file_ptr->lock ) );

    *file_xp = XPTR( local_cxy , file_ptr );
    return 0;
}

////////////////////////////////////////
void vfs_file_destroy( xptr_t  file_xp )
{
    // get file cluster and local pointer
    cxy_t        file_cxy = GET_CXY( file_xp );
    vfs_file_t * file_ptr = (vfs_file_t *)GET_PTR( file_xp );

    if( file_cxy != local_cxy )
    {
        printk("\n[PANIC] in %s : file descriptor not in local cluster\n", __FUNCTION__ );
        hal_core_sleep(); 
    }

    if( file_ptr->refcount )
    {
        printk("\n[PANIC] in %s : file refcount non zero\n", __FUNCTION__ );
        hal_core_sleep(); 
    }       

	kmem_req_t req;
	req.ptr   = file_ptr;
	req.type  = KMEM_VFS_FILE;
	kmem_free( &req );
}

//////////////////////////////////////////////////////////////////////////////////////////
//           File related functions
//////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////
error_t vfs_open( xptr_t     cwd_xp,
		          char     * path,
		          uint32_t   flags,
		          xptr_t   * file_xp )
{
    return 0;
}

///////////////////////////////////
uint32_t vfs_read( xptr_t   file_xp, 
                   void   * buffer,
                   uint32_t size )
{
    return 0;
}

////////////////////////////////////
uint32_t vfs_write( xptr_t   file_xp,
                    void   * buffer,
                    uint32_t size )
{
    return 0;
}

//////////////////////////////////////
error_t vfs_lseek( xptr_t     file_xp,
                   uint32_t   offset,
                   uint32_t   whence, 
                   uint32_t * new_offset )
{
    return 0;
}

//////////////////////////////////////
error_t vfs_close( xptr_t     file_xp, 
                   uint32_t * refcount )
{
    return 0;
}

////////////////////////////////////
error_t vfs_unlink( xptr_t   cwd_xp,
                    char   * path )
{
    return 0;
}

//////////////////////////////////////
error_t vfs_stat( xptr_t       file_xp,
                  vfs_stat_t * stat )
{
    return 0;
}

//////////////////////////////////////////////////////////////////////////////////////////
//           Directory related functions
//////////////////////////////////////////////////////////////////////////////////////////





//////////////////////////////////////////////////////////////////////////////////////////
//            Inode Tree functions
//////////////////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////////////////////
// This static function is used by the vfs_lookup() function.
// It takes an extended pointer on a remote inode (parent directory inode),
// and check access_rights violation for the calling thread.
// It can be used by any thread running in any cluster.
//////////////////////////////////////////////////////////////////////////////////////////
// @ inode_xp    : extended pointer on inode.
// @ client_uid  : client thread user ID
// @ client_gid  : client thread group ID
// @ return true if access rights are violated.
//////////////////////////////////////////////////////////////////////////////////////////
bool_t vfs_access_denied( xptr_t   inode_xp,
                          uint32_t client_uid,
                          uint32_t client_gid )
{
    // get found inode cluster and local pointer
    cxy_t         inode_cxy = GET_CXY( inode_xp );
    vfs_inode_t * inode_ptr = (vfs_inode_t *)GET_PTR( inode_xp );

    // get inode access mode, UID, and GID
    // TODO uint32_t  mode = hal_remote_lw( XPTR( inode_cxy , &inode_ptr->mode ) );
    uid_t     uid  = hal_remote_lw( XPTR( inode_cxy , &inode_ptr->uid  ) );
    gid_t     gid  = hal_remote_lw( XPTR( inode_cxy , &inode_ptr->gid  ) );

    // FIXME : me must use mode
    if( (uid == client_uid) || (gid == client_gid) ) return false;
    else                                             return true;
}

//////////////////////////////////////////////////////////////////////////////////////////
// This static function is used by the vfs_lookup() function.
// It takes an extended pointer on a remote inode (parent directory inode), a directory 
// entry name, and returns an extended pointer on the child inode. 
// It can be used by any thread running in any cluster.
//////////////////////////////////////////////////////////////////////////////////////////
// @ parent_xp   : extended pointer on parent inode in remote cluster.
// @ name        : dentry name
// @ child_xp    : [out] buffer for extended pointer on child inode.
// @ return true if success / return false if not found.
//////////////////////////////////////////////////////////////////////////////////////////
static bool_t vfs_get_child( xptr_t   parent_xp,
                             char   * name,
                             xptr_t * child_xp )
{
    xptr_t  xhtab_xp;    // extended pointer on hash table containing children dentries
    xptr_t  dentry_xp;   // extended pointer on children dentry

    // get parent inode cluster and local pointer
    cxy_t         parent_cxy = GET_CXY( parent_xp );
    vfs_inode_t * parent_ptr = (vfs_inode_t *)GET_PTR( parent_xp );

    // get extended pointer on hash table of children directory entries
    xhtab_xp = XPTR( parent_cxy , &parent_ptr->children );

    // search extended pointer on matching dentry
    dentry_xp = xhtab_lookup( xhtab_xp , name );

    if( dentry_xp == XPTR_NULL ) return false;

    // get dentry cluster and local pointer
    cxy_t          dentry_cxy = GET_CXY( dentry_xp );
    vfs_dentry_t * dentry_ptr = (vfs_dentry_t *)GET_PTR( dentry_xp );

    // return child inode
    *child_xp = (xptr_t)hal_remote_lwd( XPTR( dentry_cxy , &dentry_ptr->parent ) );
    return true;
}

//////////////////////////////////////////////////////////////////////////////////////////
// This static function is used by the vfs_lookup() function.
// It takes the <current> pointer on a buffer containing a complete pathname, and return
// in the <name> buffer, allocated by the caller, a single name in the path.
// It return also in the <next> pointer the next character to analyse in the path.
// Finally it returns a <last> boolean, that is true when the returned <name> is the
// last name in the path. The names are supposed to be separated by one or several '/'
// characters, that are not written in  the <name> buffer.
//////////////////////////////////////////////////////////////////////////////////////////
// @ current   : pointer on first character to analyse in buffer containing the path.
// @ name      : [out] pointer on buffer allocated by the caller for the returned name.
// @ next      : [out] pointer on next character to analyse in buffer containing the path.
// @ last      : [out] true if the returned name is the last (NUL character found).
// @ return 0 if success / return EINVAL if string empty (first chracter is NUL).
//////////////////////////////////////////////////////////////////////////////////////////
static error_t vfs_get_name_from_path( char     * current,
                                       char     * name,
                                       char    ** next,
                                       bool_t   * last )
{
    char * ptr = current;

    // skip leading '/' characters
    while( *ptr == '/' ) ptr++;

    // return EINVAL if string empty
    if( *ptr == 0 ) return EINVAL;

    // copy all characters in name until NUL or '/'
    while( (*ptr != 0) && (*ptr !='/') )  *(name++) = *(ptr++);

    // return last an next
    if( *ptr == 0 )             // last found character is NUL => last name in path
    {
        *last = true;
    }
    else                        // last found character is '/' => skip it 
    {
        *last = false;
        *next = ptr + 1;
    }

    return 0;
}

////////////////////////////////////////
error_t vfs_lookup( xptr_t       cwd_xp,
                    char       * pathname,
                    uint32_t     client_uid,
                    uint32_t     client_gid,
					xptr_t     * inode_xp,
                    xptr_t     * ctx_xp )
{
    char          name[CONFIG_VFS_MAX_NAME_LENGTH];   // one name in path 

    xptr_t        parent_xp;    // extended pointer on parent inode
    cxy_t         parent_cxy;   // cluster for parentc inode
    vfs_inode_t * parent_ptr;   // local pointer on parent inode  
    xptr_t        child_xp;     // extended pointer on child inode
    cxy_t         child_cxy;    // cluster for child inode
    vfs_inode_t * child_ptr;    // local pointer on child inode  
    char        * current;      // current pointer on path
    char        * next;         // next value for current pointer   
    bool_t        last;         // true when the name is the last in path
    bool_t        found;        // true when a child has been found
    thread_t    * this;         // pointer on calling thread descriptor
    process_t   * process;      // pointer on calling process descriptor
    vfs_ctx_t   * ctx;          // parent inode context
    uint32_t      type;         // file system type of parent inode
    error_t       error;

    this    = CURRENT_THREAD;
    process = this->process;

    // get extended pointer on first inode to search
    if( pathname[0] == '/' ) parent_xp = process->vfs_root_xp;
    else                     parent_xp = cwd_xp;

    // initialise loop variables
    current  = pathname;
    next     = NULL;
    last     = false;
    child_xp = XPTR_NULL;

    // take lock on parent inode
    vfs_inode_remote_lock( parent_xp );

    // break : if one intermediate name not found
    // exit  : when last name found (i.e. last == true)
    do
    {
        // get cluster and local pointer for parent inode
        parent_cxy = GET_CXY( parent_xp );
        parent_ptr = (vfs_inode_t *)GET_PTR( parent_xp );
        
        // get parent inode FS type
        ctx = (vfs_ctx_t *)(intptr_t)hal_remote_lpt( XPTR( parent_cxy , &parent_ptr->ctx ) );
        type = ctx->type;

        // get one name from path
        vfs_get_name_from_path( current , name , &next , &last );

        // search a child dentry matching name for parent inode
        found = vfs_get_child( parent_xp,
                               name,
                               &child_xp );

        if( found == false ) // child inode not found in inode tree => try to load it
        {
            // release lock on parent inode
            vfs_inode_remote_unlock( parent_xp );

            // insert a new dentry/inode in parent inode
            error = vfs_add_child_in_parent( type , parent_xp , name , &child_xp );

            if( error )
            {
                printk("\n[ERROR] in %s : inode %s not found in path %s\n",
                       __FUNCTION__ , name , pathname );
                return ENOENT;
            }

            // take lock on parent inode
            vfs_inode_remote_lock( parent_xp );
        }

        // check access rights
        error = vfs_access_denied( child_xp,
                                   client_uid,
                                   client_gid );
        if( error ) 
        {
            printk("\n[ERROR] in %s : permission denied for %s\n", __FUNCTION__ , name );
            return EACCES;
        }

        // take lock on child inode if not last
        if( last == false ) vfs_inode_remote_lock( child_xp );

        // release lock on parent inode
        vfs_inode_remote_unlock( parent_xp );

        // update loop variables
        parent_xp = child_xp;
        current   = next;
    }
    while( last == false );

    vfs_dmsg("\n[INFO] in %s : searched inode found for %s\n",
                 __FUNCTION__ , pathname );

    // get cluster and local pointer on child inode
    child_cxy = GET_CXY( child_xp );
    child_ptr = (vfs_inode_t *)GET_PTR( child_xp );

    // return searched pointers
    *inode_xp = child_xp;
    *ctx_xp   = (xptr_t)hal_remote_lwd( XPTR( child_cxy , &child_ptr->ctx ) );

    return 0;

}  // end vfs_lookup()


////////////////////////////////////////////
error_t vfs_get_path( xptr_t    searched_xp,
                      char    * buffer,
                      uint32_t  max_size )
{
	xptr_t       dentry_xp;   // extended pointer on current dentry
    char       * name;        // local pointer on current dentry name
	uint32_t     length;      // length of current dentry name
	uint32_t     count;       // number of characters written in buffer
	uint32_t     index;       // slot index in buffer
    xptr_t       inode_xp;  // extended pointer on    

    // implementation note:
    // we use two variables "index" and "count" because the buffer
    // is actually written in decreasing index order (from leaf to root) 
    // TODO : handle conflict with a concurrent rename 
    // FIXME : handle synchro in the loop ... [AG]

	// set the NUL character in buffer / initialise buffer index and count
	buffer[max_size - 1] = 0;
	count	 = 1;
    index    = max_size - 2;

    // initialize current inode
    inode_xp  = searched_xp;

    // exit when root inode found (i.e. dentry_xp == XPTR_NULL)
	do
    {
        // get inode cluster and local pointer
        cxy_t         inode_cxy = GET_CXY( inode_xp );
        vfs_inode_t * inode_ptr = (vfs_inode_t *)GET_PTR( inode_xp );

        // get extended pointer on parent dentry		
        dentry_xp = (xptr_t)hal_remote_lwd( XPTR( inode_cxy , inode_ptr->parent_xp ) );

        // get dentry cluster and local pointer
        cxy_t          dentry_cxy = GET_CXY( dentry_xp );
        vfs_dentry_t * dentry_ptr = (vfs_dentry_t *)GET_PTR( dentry_xp );

        // get dentry name length and pointer
        length =  hal_remote_lw( XPTR( dentry_cxy , &dentry_ptr->length ) );
        name   = (char *)hal_remote_lpt( XPTR( dentry_cxy , &dentry_ptr->name ) );

        // update index and count
        index -= (length + 1); 
        count += (length + 1);

        // check buffer overflow
        if( count >= max_size )
        {
            printk("\n[ERROR] in %s : kernel buffer too small\n", __FUNCTION__ );
            return EINVAL;
        }

        // update pathname
        hal_remote_memcpy( XPTR( local_cxy , &buffer[index + 1] ) ,
                           XPTR( dentry_cxy , name ) , length );
		buffer[index] = '/';

		// get extended pointer on next inode
        inode_xp = (xptr_t)hal_remote_lwd( XPTR( dentry_cxy , dentry_ptr->parent ) );
    }
    while( (dentry_xp != XPTR_NULL) );

	return 0;

}  // end vfs_get_path()

///////////////////////////////////////////////
error_t vfs_add_child_in_parent( uint32_t type,
                                 xptr_t   parent_xp,
                                 char   * name,   
                                 xptr_t * child_xp )
{
    xptr_t     dentry_xp;  // extended pointer on created dentry
    xptr_t     inode_xp;   // extended pointer on created inode
    error_t    error;

    // get parent inode cluster and local pointer
    cxy_t         parent_cxy = GET_CXY( parent_xp );
    vfs_inode_t * parent_ptr = (vfs_inode_t *)GET_PTR( parent_xp );

    // create dentry
    if( parent_cxy == local_cxy )      // parent cluster is the local cluster
    {
        error = vfs_dentry_create( type,
                                   name,
                                   parent_ptr,
                                   &dentry_xp );
    }
    else                               // parent cluster is remote
    {
        rpc_vfs_dentry_create_client( parent_cxy,
                                      type,
                                      name,
                                      parent_ptr,
                                      &dentry_xp,
                                      &error );
    }
                                      
    if( error )
    {
        printk("\n[ERROR] in %s : cannot create dentry in cluster %x\n",
               __FUNCTION__ , parent_cxy );

        return error;
    }

    // select a target cluster for child inode
    uint32_t  x_size    = LOCAL_CLUSTER->x_size;
    uint32_t  y_size    = LOCAL_CLUSTER->y_size;
    uint32_t  y_width   = LOCAL_CLUSTER->y_width;
    uint32_t  index     = ( hal_time_stamp() + hal_get_gid() ) % (x_size * y_size);
    uint32_t  x         = index / y_size;    
    uint32_t  y         = index % y_size;
    cxy_t     child_cxy = (x<<y_width) + y;
                                      
    // create child inode TODO : define attr / mode / uid / gid
    uint32_t attr = 0;
    uint32_t mode = 0;
    uint32_t uid  = 0;
    uint32_t gid  = 0;
    
    if( child_cxy == local_cxy )      // child cluster is the local cluster
    {
        error = vfs_inode_create( dentry_xp,
                                  type,
                                  attr,
                                  mode,
                                  uid,
                                  gid,
                                  &inode_xp );
    }
    else                              // child cluster is remote
    {
        rpc_vfs_inode_create_client( child_cxy,
                                     dentry_xp,
                                     type,
                                     attr,
                                     mode,
                                     uid,
                                     gid,
                                     &inode_xp,
                                     &error );
    }
                                     
    if( error )
    {
        printk("\n[ERROR] in %s : cannot create inode in cluster %x\n",
               __FUNCTION__ , child_cxy );
 
        vfs_dentry_t * dentry = (vfs_dentry_t *)GET_PTR( dentry_xp );
        if( parent_cxy == local_cxy ) vfs_dentry_destroy( dentry );
        else rpc_vfs_dentry_destroy_client( parent_cxy , dentry );
        return error;
    }

    // success : return extended pointer on child inode
    *child_xp = inode_xp;
    return 0;

}  // end vfs_add_child_in_parent()


