//==========================================================================
//
//      misc.cxx
//
//      Fileio miscellaneous functions
//
//==========================================================================
// ####ECOSGPLCOPYRIGHTBEGIN####                                            
// -------------------------------------------                              
// This file is part of eCos, the Embedded Configurable Operating System.   
// Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2007 Free Software Foundation, Inc.
// Copyright (C) 2004, 2007 eCosCentric Limited                             
//
// 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.,    
// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 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 v2.                                               
//
// This exception does not invalidate any other reasons why a work based    
// on this file might be covered by the GNU General Public License.         
// -------------------------------------------                              
// ####ECOSGPLCOPYRIGHTEND####                                              
//==========================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):           nickg
// Contributors:        nickg
// Date:                2000-05-25
// Purpose:             Fileio miscellaneous functions
// Description:         This file contains various miscellaneous functions
//                      for use with the fileio system. These include startup,
//                      table management, and other service routines.
//
//####DESCRIPTIONEND####
//
//==========================================================================

#include <pkgconf/system.h>
#include <pkgconf/hal.h>
#include <pkgconf/io_fileio.h>
#ifdef CYGPKG_LIBC_TIME
#include <pkgconf/libc_time.h>
#endif


#include <cyg/infra/cyg_trac.h>        // tracing macros
#include <cyg/infra/cyg_ass.h>         // assertion macros
#include <string.h>                    // strcmp()
#include <time.h>                      // time()

#ifdef CYGPKG_IO_WALLCLOCK
# include <cyg/io/wallclock.hxx>       // Wallclock class
#endif

#ifdef CYGPKG_KERNEL
#include <pkgconf/kernel.h>
#include <cyg/kernel/ktypes.h>         // base kernel types
#include <cyg/kernel/clock.inl>         // Clock inlines
#endif

#include "fio.h"                       // Private header

//==========================================================================
// forward definitions

static void cyg_mtab_init();

__externC int chdir( const char *path );

//==========================================================================
// Filesystem tables

// -------------------------------------------------------------------------
// Filesystem table.

// This array contains entries for all filesystem that are installed in
// the system.
__externC cyg_fstab_entry cyg_fstab[];
CYG_HAL_TABLE_BEGIN( cyg_fstab, fstab );

// end of filesystem table, set in linker script.
__externC cyg_fstab_entry cyg_fstab_end;
CYG_HAL_TABLE_END( cyg_fstab_end, fstab );

#ifdef CYGPKG_KERNEL
// Array of mutexes for locking the fstab entries
static cyg_drv_mutex_t fstab_lock[CYGNUM_FILEIO_FSTAB_MAX] CYGBLD_ATTRIB_INIT_PRI(CYG_INIT_IO_FS);
#endif

// -------------------------------------------------------------------------
// Mount table.

// This array contains entries for all valid running filesystems.
__externC cyg_mtab_entry cyg_mtab[];
CYG_HAL_TABLE_BEGIN( cyg_mtab, mtab );

// Extra entries at end of mtab for dynamic mount points.
#if CYGNUM_FILEIO_MTAB_EXTRA > 0
cyg_mtab_entry cyg_mtab_extra[CYGNUM_FILEIO_MTAB_EXTRA] CYG_HAL_TABLE_EXTRA(mtab) = { { NULL } };
#endif
// End of mount table, set in the linker script.
__externC cyg_mtab_entry cyg_mtab_end;
CYG_HAL_TABLE_END( cyg_mtab_end, mtab );

#ifdef CYGPKG_KERNEL
// Array of mutexes for locking the mtab entries
static cyg_drv_mutex_t mtab_lock[CYGNUM_FILEIO_MTAB_MAX] CYGBLD_ATTRIB_INIT_PRI(CYG_INIT_IO_FS);

// FILEIO global lock. This protects allocation and freeing of mount
// table entries. It also protects access to the current directory
// variables. The current directory wait condition variable is
// signalled whenever the reference counter is zeroed and allows
// threads to wait until the current directory variables are available
// for reassignment.

cyg_drv_mutex_t fileio_lock;
cyg_drv_cond_t cyg_cdir_wait;

#endif

//==========================================================================
// Current directory

cyg_mtab_entry *cyg_cdir_mtab_entry = NULL;
cyg_dir cyg_cdir_dir = CYG_DIR_NULL;
int cyg_cdir_refcount = 0;

//==========================================================================
// Initialization object

class Cyg_Fileio_Init_Class
{
public:    
    Cyg_Fileio_Init_Class();
};

static Cyg_Fileio_Init_Class fileio_initializer CYGBLD_ATTRIB_INIT_PRI(CYG_INIT_IO_FS);

Cyg_Fileio_Init_Class::Cyg_Fileio_Init_Class()
{
    cyg_fd_init();

    cyg_mtab_init();

    chdir("/");

#ifdef CYGPKG_IO_FILEIO_AUTOMOUNT
        cyg_automount_init();
#endif
    
}

//==========================================================================
// Mount table initializer

static void cyg_mtab_init()
{
    cyg_fstab_entry *f;
    cyg_mtab_entry *m;
    
#ifdef CYGPKG_KERNEL
    for( f = &cyg_fstab[0]; f != &cyg_fstab_end; f++ )
        cyg_drv_mutex_init( &fstab_lock[f-&cyg_fstab[0]] );

    cyg_drv_mutex_init( &fileio_lock );
    cyg_drv_cond_init( &cyg_cdir_wait, &fileio_lock );
#endif
    
    for( m = &cyg_mtab[0]; m != &cyg_mtab_end; m++ )
    {
        const char *fsname = m->fsname;
        int fsnamelen;

        // Ignore empty entries
        if( m->name == NULL )
            continue;
        
        // stop if there are more than the configured maximum
        if( m-&cyg_mtab[0] >= CYGNUM_FILEIO_MTAB_MAX )
            break;
        
#ifdef CYGPKG_KERNEL
        cyg_drv_mutex_init( &mtab_lock[m-&cyg_mtab[0]] );
#endif

        fsnamelen = strlen(fsname);
        
        for( f = &cyg_fstab[0]; f != &cyg_fstab_end; f++ )
        {
            // stop if there are more than the configured maximum
            if( f-&cyg_fstab[0] >= CYGNUM_FILEIO_FSTAB_MAX )
                break;
            
            if( strncmp( fsname, f->name, fsnamelen) == 0 )
            {
                // We have a match.

                if( f->mount( f, m ) == 0 )
                {
                    m->valid    = true;
                    m->fs       = f;
                    // m->root installed by fs.
                }
                else
                {
                    m->valid = false;
                }
                
                break;
            }
        }
    }
}

//==========================================================================
// Mount table matching

// -------------------------------------------------------------------------
// matchlen() compares two strings and returns the number of bytes by which
// they match.

static int matchlen( const char *s1, const char *s2 )
{
    int len = 0;
    while( s1[len] == s2[len] && s1[len] && s2[len] ) len++;

    // Return length only if s2 is an initial substring of s1,
    // and it terminates in s1 at end-of-string or a '/'.

    // Special case for s2 == "/"
    if( len == 1 && s2[0] == '/' && s2[1] == 0 )
        return len;
    
    if( (s2[len] == 0) && (s1[len] == 0 || s1[len] == '/'))
         return len;
    else return 0;
}

// -------------------------------------------------------------------------
// Simple strlen implementation

static int my_strlen(const char *c)
{
    int l = 0;
    while (*c++) l++;
    return l;
}

// -------------------------------------------------------------------------
// Search the mtab for the entry that matches the longest substring of
// **name. 

__externC int cyg_mtab_lookup_locked( cyg_dir *dir, const char **name, cyg_mtab_entry **mte)
{
    int result = 0;
    cyg_mtab_entry *m, *best = NULL;
    int best_len = 0;

    *mte = cyg_cdir_mtab_entry;
    *dir = cyg_cdir_dir;
    cyg_cdir_refcount++;
        
    // Special case: empty name. Otherwise it matches when it shouldn't.
    if ( **name == '\0' ) {
        result = ENOENT;
        goto done;
    }

    // Unrooted file names start from current dir
    else if( **name != '/' ) {
        int cwd_len;
        if (*mte == (cyg_mtab_entry *)NULL) {
            // No known current directory
            result = ENOENT;
            goto done;
        }

        best = *mte;
	cwd_len = my_strlen((*mte)->name);

        // current dir is not the correct mte if the relative path crosses
        // mount points -  search for best matching mount point
        for( m = &cyg_mtab[0]; m != &cyg_mtab_end; m++ )
	{
            if( m->name != NULL && m->valid )
            {
                int len = matchlen(m->name, (*mte)->name);
                // mount point under cwd?
                if (len == cwd_len)
                {
                    if (m->name[len] == '/')
                        len++;

                    len = matchlen(*name, &m->name[len]);
                    if (len > best_len)
                        best = m, best_len = len;
                }
            }
        }

	// did we find a better match?
	if (best != *mte)
	  *dir = best->root;
    }
    else
    {
        // Otherwise search the mount table.
        for( m = &cyg_mtab[0]; m != &cyg_mtab_end; m++ )
        {
            if( m->name != NULL && m->valid )
            {
                int len = matchlen(*name,m->name);
                if( len > best_len )
                    best = m, best_len = len;
            }
        }

        // No match found, bad path name...
        if( best_len == 0 )
        {
            result = ENOENT;
            goto done;
        }

	*dir = best->root;
    }

    *name += best_len;
    if( **name == '/' )
        (*name)++;
    *mte = best;

done:
    return result;
}

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

__externC int cyg_mtab_lookup( cyg_dir *dir, const char **name, cyg_mtab_entry **mte)
{
    int result;
    
    FILEIO_MUTEX_LOCK( fileio_lock );

    result = cyg_mtab_lookup_locked( dir, name, mte );

    FILEIO_MUTEX_UNLOCK( fileio_lock );

    return result;
}

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

__externC void cyg_cdir_unref( void )
{
    FILEIO_MUTEX_LOCK( fileio_lock );

    CYG_ASSERT( cyg_cdir_refcount > 0, "cdir efcount < 1");

    cyg_cdir_refcount--;

    FILEIO_COND_SIGNAL( cyg_cdir_wait );
    
    FILEIO_MUTEX_UNLOCK( fileio_lock );
}

//==========================================================================
// mount filesystem

__externC int mount( const char *devname,
                     const char *dir,
                     const char *fsname)
{

    FILEIO_ENTRY();
    
    cyg_mtab_entry *m;
    cyg_fstab_entry *f;
    int result = ENOERR;
    char *p = strchr(fsname, ':');
    const char *options;
    int fsnamelen;

    // If there is a colon in the fsname, then the of the string is a
    // list of options. So set the options string to point to
    // it. Otherwise there are no options, so set the string to NULL.
    if(p != NULL)
    {
        fsnamelen = (p-fsname);
        options = fsname+fsnamelen+1;
    }
    else
    {
        fsnamelen = strlen(fsname);
        options = NULL;
    }
        
    FILEIO_MUTEX_LOCK( fileio_lock );

    // Search the mount table for an empty entry
    for( m = &cyg_mtab[0]; m != &cyg_mtab_end; m++ )
    {
        // stop if there are more than the configured maximum
        if( m-&cyg_mtab[0] >= CYGNUM_FILEIO_MTAB_MAX )
        {
            m = &cyg_mtab_end;
            break;
        }

         if( m->name == NULL ) break;
    }

    if( m == &cyg_mtab_end )
    {
        result = ENOMEM;
        goto done;
    }

    // Now search the fstab for the filesystem implementation
    for( f = &cyg_fstab[0]; f != &cyg_fstab_end; f++ )
    {
        // stop if there are more than the configured maximum
        if( f-&cyg_fstab[0] >= CYGNUM_FILEIO_FSTAB_MAX )
            break;
            
        if( strncmp( fsname, f->name, fsnamelen) == 0 )
            break;
    }

    if( f == &cyg_fstab_end )
    {
        result = ENODEV;
        goto done;
    }
            
    // We have a match.

    m->name = dir;
    m->fsname = f->name;
    m->devname = devname;
    m->options = options;
    m->fs = f;

    while( !cyg_fs_lock( m, f->syncmode ) )
        continue;

    if( (result = f->mount( f, m )) == 0 )
    {
        m->valid    = true;
        // m->root installed by fs.
    }
    else
    {
        m->valid = false;
        m->name = NULL;
    }

    cyg_fs_unlock( m, f->syncmode );    

    // Make sure that there is something to search (for open)

    if (cyg_cdir_mtab_entry == (cyg_mtab_entry *)NULL) {
        cyg_cdir_mtab_entry = m;
    }

done:
    FILEIO_MUTEX_UNLOCK( fileio_lock );
    FILEIO_RETURN(result);
}

//==========================================================================
// unmount filesystem

__externC int umount( const char *name)
{
    int err = ENOERR;
    
    FILEIO_ENTRY();
    
    cyg_mtab_entry *m;

    FILEIO_MUTEX_LOCK( fileio_lock );

    // Wait for any current directory references to be released.
    while( cyg_cdir_refcount != 0 )
        FILEIO_COND_WAIT( cyg_cdir_wait );
    
    // Search the mount table for a matching entry
    for( m = &cyg_mtab[0]; m != &cyg_mtab_end; m++ )
    {
        // stop if there are more than the configured maximum
        if( m-&cyg_mtab[0] >= CYGNUM_FILEIO_MTAB_MAX )
        {
            m = &cyg_mtab_end;
            break;
        }

        // Ignore empty or invalid entries
         if( m->name == NULL || !m->valid ) continue;

         // match names.
         if( strcmp(name,m->name) == 0 ) break;

         // Match device name too?
    }

    if( m == &cyg_mtab_end )
    {
        err = EINVAL;
        goto done;
    }

    // We have a match, call the umount function

    while( !cyg_fs_lock( m, m->fs->syncmode ) )
        continue;
    
    err = m->fs->umount( m, false );

    cyg_fs_unlock( m, m->fs->syncmode );

    if( err == ENOERR )
    {
        m->valid        = false;
        m->name         = NULL;
    }

done:
    FILEIO_MUTEX_UNLOCK( fileio_lock );    
    FILEIO_RETURN(err);
}

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

__externC int umount_force( const char *name)
{
    int err = ENOERR;
    
    FILEIO_ENTRY();
    
    cyg_mtab_entry *m;

    FILEIO_MUTEX_LOCK( fileio_lock );

    // Wait for any current directory references to be released.
    while( cyg_cdir_refcount != 0 )
        FILEIO_COND_WAIT( cyg_cdir_wait );
    
    // Search the mount table for a matching entry
    for( m = &cyg_mtab[0]; m != &cyg_mtab_end; m++ )
    {
        // stop if there are more than the configured maximum
        if( m-&cyg_mtab[0] >= CYGNUM_FILEIO_MTAB_MAX )
        {
            m = &cyg_mtab_end;
            break;
        }

        // Ignore empty or invalid entries
         if( m->name == NULL || !m->valid ) continue;

         // match names.
         if( strcmp(name,m->name) == 0 ) break;

         // Match device name too?
    }

    if( m == &cyg_mtab_end )
    {
        err = EINVAL;
        goto done;
    }

    // We have a match, dismantle all the FILEIO data structures and
    // then call the filesystem's umount() function.
    
    // Close any files open on this filesystem. This will prevent any
    // new IO operations being started, but there may be current
    // operations in the filesystem. It is the filesystem's
    // responsibility to deal with those.
    cyg_fd_filesys_close( m );

    // Move current directory if it points to this filesystem. For now
    // set it to "/".
    if( cyg_cdir_mtab_entry == m )
    {
        cyg_chdir_locked( "/" );
    }
    
    // Release filesystem locks. This causes any threads that are
    // queueing for access to the filesystem to be kicked back to
    // their caller before they enter the filesystem.
    cyg_fs_release( m, m->fs->syncmode );

    // Lock the filesystem. We need to loop here until we actually
    // lock it.
    while( !cyg_fs_lock( m, m->fs->syncmode ) )
        continue;
    
    err = m->fs->umount( m, true );

    cyg_fs_unlock( m, m->fs->syncmode );

    // Invalidate the mount table entry regardless of whether the
    // filesystem umount() succeeded.
    m->valid        = false;
    m->name         = NULL;

done:
    FILEIO_MUTEX_UNLOCK( fileio_lock );    
    FILEIO_RETURN(err);
}


//==========================================================================
// Implement filesystem locking protocol. 

cyg_bool cyg_fs_lock( cyg_mtab_entry *mte, cyg_uint32 syncmode )
{
    cyg_bool result = true;
    
    CYG_ASSERT(mte != NULL, "Bad mount table entry");

    if( syncmode & CYG_SYNCMODE_FILE_FILESYSTEM ) {
        CYG_ASSERT(mte->fs-&cyg_fstab[0] < CYGNUM_FILEIO_FSTAB_MAX, "Bad file system");
        result = FILEIO_MUTEX_LOCK( fstab_lock[mte->fs-&cyg_fstab[0]] );
    }

    if( result && (syncmode & CYG_SYNCMODE_FILE_MOUNTPOINT) ) {
        CYG_ASSERT(mte-&cyg_mtab[0] < CYGNUM_FILEIO_MTAB_MAX, "Bad mount point");
        result = FILEIO_MUTEX_LOCK( mtab_lock[mte-&cyg_mtab[0]] );
    }

    return result;
}

void cyg_fs_unlock( cyg_mtab_entry *mte, cyg_uint32 syncmode )
{
    CYG_ASSERT(mte != NULL, "Bad mount table entry");

    if( syncmode & CYG_SYNCMODE_FILE_FILESYSTEM ) {
        CYG_ASSERT(mte->fs-&cyg_fstab[0] < CYGNUM_FILEIO_FSTAB_MAX, "Bad file system");
        FILEIO_MUTEX_UNLOCK( fstab_lock[mte->fs-&cyg_fstab[0]] );
    }

    if( syncmode & CYG_SYNCMODE_FILE_MOUNTPOINT ) {
        CYG_ASSERT(mte-&cyg_mtab[0] < CYGNUM_FILEIO_MTAB_MAX, "Bad mount point");
        FILEIO_MUTEX_UNLOCK( mtab_lock[mte-&cyg_mtab[0]] );
    }
}

void cyg_fs_release( cyg_mtab_entry *mte, cyg_uint32 syncmode )
{
    CYG_ASSERT(mte != NULL, "Bad mount table entry");

    if( syncmode & CYG_SYNCMODE_FILE_FILESYSTEM ) {
        CYG_ASSERT(mte->fs-&cyg_fstab[0] < CYGNUM_FILEIO_FSTAB_MAX, "Bad file system");
        FILEIO_MUTEX_RELEASE( fstab_lock[mte->fs-&cyg_fstab[0]] );
    }

    if( syncmode & CYG_SYNCMODE_FILE_MOUNTPOINT ) {
        CYG_ASSERT(mte-&cyg_mtab[0] < CYGNUM_FILEIO_MTAB_MAX, "Bad mount point");
        FILEIO_MUTEX_RELEASE( mtab_lock[mte-&cyg_mtab[0]] );
    }
}

cyg_drv_mutex_t *cyg_fs_get_lock( cyg_fstab_entry *fste, cyg_mtab_entry *mte )
{
    CYG_ASSERT(mte != NULL, "Bad mount table entry");

#ifdef CYGPKG_KERNEL    
    if( fste->syncmode & CYG_SYNCMODE_FILE_FILESYSTEM ) {
        CYG_ASSERT(fste-&cyg_fstab[0] < CYGNUM_FILEIO_FSTAB_MAX, "Bad file system");
        return ( &fstab_lock[fste-&cyg_fstab[0]] );
    }

    if( fste->syncmode & CYG_SYNCMODE_FILE_MOUNTPOINT ) {
        CYG_ASSERT(mte-&cyg_mtab[0] < CYGNUM_FILEIO_MTAB_MAX, "Bad mount point");
        return( &mtab_lock[mte-&cyg_mtab[0]] );
    }
#endif
    
    return NULL;
}


//==========================================================================
// Search mount table for a filesystems root. 

__externC cyg_mtab_entry * cyg_fs_root_lookup( cyg_dir *root ) 
{
     cyg_mtab_entry *m, *result = NULL;

     FILEIO_MUTEX_LOCK( fileio_lock );     
     for( m = &cyg_mtab[0]; m != &cyg_mtab_end; m++ ) 
     {
          if( (cyg_dir *)m->root == root )
          {
               result = m;
               break;
          }
     }
     FILEIO_MUTEX_UNLOCK( fileio_lock );
     return result;
}

//==========================================================================
// Timestamp support
// This provides access to the current time/date, expressed as a
// time_t.  It uses a number of mechanisms to do this, selecting
// whichever is available in the current configuration.

__externC time_t cyg_timestamp()
{
#if defined(CYGPKG_IO_WALLCLOCK)

    // First, try to get the time from the wallclock device.
    
    return (time_t) Cyg_WallClock::wallclock->get_current_time();

#elif defined(CYGINT_ISO_POSIX_TIMERS)

    // If POSIX is present, use the current value of the realtime
    // clock.
    
    struct timespec tp;

    clock_gettime( CLOCK_REALTIME, &tp );

    return (time_t) tp.tv_sec;
    
#elif defined(CYGPKG_KERNEL) 

    // If all else fails, get the current realtime clock value and
    // convert it to seconds ourself.
    
    static struct Cyg_Clock::converter sec_converter;
    static cyg_bool initialized = false;
    cyg_tick_count ticks;
    
    if( !initialized )
    {
        Cyg_Clock::real_time_clock->get_clock_to_other_converter( 1000000000, &sec_converter );
        initialized = true;
    }

    ticks = Cyg_Clock::real_time_clock->current_value();
    
    return (time_t) Cyg_Clock::convert( ticks, &sec_converter );
#else    
    /* No clock support at all. */
    return (time_t) 0;
#endif    
    
}

//==========================================================================
// Default functions

__externC int cyg_fileio_enosys() { return ENOSYS; }
__externC int cyg_fileio_erofs() { return EROFS; }
__externC int cyg_fileio_enoerr() { return ENOERR; }
__externC int cyg_fileio_enotdir() { return ENOTDIR; }

__externC cyg_bool cyg_fileio_seltrue (struct CYG_FILE_TAG *fp, int which, CYG_ADDRWORD info)
{ return 1; }

// -------------------------------------------------------------------------
// EOF misc.cxx
