/* Support files for GNU libc.  Files in the system namespace go here.
   Files in the C namespace (ie those that do not start with an
   underscore) go in .c.  */

#include <_ansi.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <stdio.h>
#include <time.h>
#include <sys/time.h>
#include <sys/times.h>
#include <errno.h>
#include <Npttypes.h>

#include "swi.h"
#include "ddi.h"
/* Treck includes */
#include "selectext.h"
/* reent.h must be after Treck includes, since Treck redefines timeval. */
#include <reent.h>


#define IS_DEVICE(sock)     (naSockGetMagic(sock) == TM_MAGIC_DEVICE)
#define IS_FILE(x)          ((x & DDI_FILE_BIT) != 0)
#define GET_FILE_HANDLE(x)  (x & ~DDI_FILE_BIT)

    int     isatty		_PARAMS ((int));
/* Forward prototypes.  */
int     _system     _PARAMS ((const char *));
int     _rename     _PARAMS ((const char *, const char *));
clock_t _times		_PARAMS ((struct tms *));
int     _gettimeofday	_PARAMS ((struct timeval *, struct timezone *));
void    _raise 		_PARAMS ((void));
#ifdef NETOS_GNU_TOOLS
int     _unlink		_PARAMS ((const char *));
#else
int     _unlink		_PARAMS ((void));
#endif

int     _link 		_PARAMS ((void));
int     _stat 		_PARAMS ((const char *, struct stat *));
int     _fstat 		_PARAMS ((int, struct stat *));
caddr_t _sbrk		_PARAMS ((int));
int     _getpid		_PARAMS ((int));
int     _kill		_PARAMS ((int, int));
void    _exit		_PARAMS ((int));
int     _close		_PARAMS ((int));
int     _swiclose	_PARAMS ((int));
int     _open		_PARAMS ((const char *, int, ...));
int     _swiopen	_PARAMS ((const char *, int));
int     _write 		_PARAMS ((int, char *, int));
int     _swiwrite	_PARAMS ((int, char *, int));
int     _lseek		_PARAMS ((int, int, int));
int     _swilseek	_PARAMS ((int, int, int));
int     _read		_PARAMS ((int, char *, int));
int     _swiread	_PARAMS ((int, char *, int));
void    initialise_monitor_handles _PARAMS ((void));


extern int setErrno (int errcode);
extern int naClib_fs_mkdir(const char *dirname, int prot);
extern int naClib_fs_open (const char *filename, int mode, int perm);
extern int naClib_fs_close(int fno);
extern int naClib_fs_write(int fno, const void *buf, int size);
extern int naClib_fs_read(int fno, const void *buf, int size);
extern int naClib_fs_rmdir(const char *dirname);
extern int naClib_fs_unlink(const char * filename);
extern int naClib_fs_lseek (int file, int ptr, int dir);
extern int naClib_fs_validInterface(const char * entry_name, int * number);



/* Register name faking - works in collusion with the linker.  */
register char * stack_ptr asm ("sp");


/* following is copied from libc/stdio/local.h to check std streams */
extern void   _EXFUN(__sinit,(struct _reent *));
#define CHECK_INIT(fp) \
  do                                    \
    {                                   \
      if ((fp)->_data == 0)             \
        (fp)->_data = _REENT;           \
      if (!(fp)->_data->__sdidinit)     \
        __sinit ((fp)->_data);          \
    }                                   \
  while (0)

/* Adjust our internal handles to stay away from std* handles.  */
#define FILE_HANDLE_OFFSET (0x20)


/* Treck support functions. */



#define SOCKPTR_UNLOCK(sock)        if (sock != NULL)           \
                                    {                           \
                                        nasockptr_unlock(sock);   \
                                        sock = NULL;            \
                                    }                           \







/*
 * tfSelectCheckDevice takes the socketEntryPtr and the checkMask.
 * The function must ask the driver about this device, and report whether
 * it can satisfy each of the set bits in the checkMask, and if so,
 * set the corresponding bits in the return value.  Any error sets errno and
 * returns -1.
 * insert_remove is either NA_SELECT_INSERT or NA_SELECT_REMOVE indicating
 * whether the checkMask and context should be inserted or removed from the
 * driver's list of active selects.  If the driver chooses to only support
 * one select at a time, this parameter tells whether it should remember
 * or forget this context.
 * The checkMask is a bitset of TM_SELECT_READ, TM_SELECT_WRITE and
 * TM_SELECT_EXCEPTION.  socketEntryPtr is used as the context variable for
 * tfDeviceSelectNotify with the set bits when the device state changes.
 */
int tfSelectCheckDevice(ttSocketEntryPtr socketEntryPtr, int insert_remove,
       	int checkMask, int socketDescriptor)
{
    int ret;
    unsigned devid;
    int mode;

    devid = naSockGetDevid(socketEntryPtr);
    mode = naSockGetMode(socketEntryPtr);
    /* TODOTRECK: bounds-check devid */

    setErrno(0);
    if (deviceTable[devid].poll == NULL)
    {
        setErrno(EBADF);
        return -1;
    }
    ret = deviceTable[devid].poll(deviceTable[devid].channel, insert_remove, &checkMask, (ulong)socketDescriptor);
    if (ret != EXIT_SUCCESS)
    {               
        setErrno(ret);
        return -1;
    }
    return checkMask;
}

/*
 * CLIB read from a file handle.  The file handle may be for a file, a device, or a
 * socket.  This function determines which type of file handle we are dealing with
 * and calls the appropriate internal routine to perform the actual read.  
 */
int
_read (int file,
       char * ptr,
       int len)
{
    ttSocketEntryPtr sock = NULL;
    int ret = -1;
    int actual_read, idx;
    int fno = file;
    char *buf = ptr;
    int size = len;

    NA_ASSERT(buf != NULL);
    NA_ASSERT(size > 0 );               /* range check parameters*/
    NA_ASSERT(size < 32767 );
    
    setErrno(0);                        /* assume no error */
    if (IS_FILE(fno))                   /* if this is a file number */
    {                                   /* then call CLIB file function */
        ret = naClib_fs_read(GET_FILE_HANDLE(fno), buf, len);
        goto cleanup;
    }
    else                                /* else must be socket number */
    {
        sock = nasockptr_lock(fno);       /* get socket pointer */
		
        if (sock == NULL)               /* terminate if invalid socket */
        {
            setErrno(EINVAL);
            goto cleanup;
        }
        if (IS_DEVICE(sock))            /* if this is a device */
        {
    	    idx = naSockGetDevid(sock);       /* device driver table index is stored in socDevid */
            if (idx >= MAX_DEV || idx < 0) 
            {
                setErrno(EINVAL);
                goto cleanup;
            }

            SOCKPTR_UNLOCK(sock);       /* unlock socket to allow concurrent read/write to I/O device */
	    
            /* call device read function */
            ret = (deviceTable[idx].read) (deviceTable[idx].channel, (void*)buf, size, &actual_read);

            sock = nasockptr_lock(fno);     /* get socket pointer */	
            if (sock == NULL)               /* terminate if invalid socket */
            {
                setErrno(EINVAL);
                goto cleanup;
            }

	    if (ret != EXIT_SUCCESS)    /* if error */
            {                           /* then put error code into errno */
                setErrno(ret);
                ret = -1;
                goto cleanup;
            }
            ret = actual_read;          /* return # of bytes read */
    	}
        else                            /* if socket */
        {
            SOCKPTR_UNLOCK(sock);       /* then translate read into recv*/
            ret = recv(fno, ptr, len, 0);
        }	    
	    
    }	    
cleanup:
    SOCKPTR_UNLOCK(sock);               /* this is safe.  It won't free it a 2nd time */
    return ret;
}


/*
 * CLIB lseek routine.  If this is a real file handle, then pass onto the
 * file system.  Return in error if the file handle is for a device or 
 * a socket.
 */
int
_lseek (int file,
	int ptr,
	int dir)
{

	int fno = file;

	if (IS_FILE(fno))           /* if this is for a file*/
	{                           /* then hand off to CLIB */
		return naClib_fs_lseek(GET_FILE_HANDLE(fno), ptr, dir);
    }
	else                        /* if not a file */
	{                           /* then seek is invalid */
		setErrno(EINVAL);
		return -1;
	}
}

/*
 * CLIB write routine.  The file handle may be for a file, device, or a
 * socket.  Determine what it is and call the appropriate internal function
 * to handle it.
 */
int
_write (int    file,
	char * ptr,
	int    len)
{
    int actual_write, idx;
    int ret = -1;
    int fno = file;
    char * buf =ptr;
    int size =len;
    ttSocketEntryPtr sock = NULL;
    
    NA_ASSERT(buf != NULL);
    NA_ASSERT(size > 0 );               /* range check parameters*/
    NA_ASSERT(size < 32767 );
    setErrno(0);
	
    if (IS_FILE(fno))               /* if this is for a file */
    {                               /* hand off to CLIB */
        return naClib_fs_write(GET_FILE_HANDLE(fno), buf, size);
    }	    
    else                            /* else if socket number */
    {                               /* then get the socket */
        sock = nasockptr_lock(fno);
		
        if (sock == NULL)
        {
            setErrno(EINVAL);
            goto cleanup;
        }
        if (IS_DEVICE(sock))        /* if write to device */
        {
            idx = naSockGetDevid(sock);   /* device table index is stored in socDevid */
            if (idx >= MAX_DEV || idx < 0) 
            {
                setErrno(EINVAL);
                goto cleanup;
            }

            SOCKPTR_UNLOCK(sock); /* unlock socket to allow concurrent read/write to I/O device */

            /* call the device driver write routine */
            ret = (deviceTable[idx].write) (deviceTable[idx].channel, (void*)buf, size, &actual_write);

            sock = nasockptr_lock(fno);     /* get socket pointer */	
            if (sock == NULL)               /* terminate if invalid socket */
            {
                setErrno(EINVAL);
                goto cleanup;
            }

	    if (ret != EXIT_SUCCESS) /* if error*/
            {                       /* then copy error code to errno*/
                setErrno(ret);
                ret = -1;
                goto cleanup;
            }
            ret = actual_write;
        }
        else                        /* else if socket */
        {
            SOCKPTR_UNLOCK(sock);   /* translate write to send */
            ret = send(fno, ptr, len, 0);
        }	    

    }
cleanup:
    SOCKPTR_UNLOCK(sock);           /* this is safe, it won't free a 2nd time */
    return ret;
}



/*
 * CLIB open routine used to open a new file handle for a file or a device.  
 * If the filename is for a device, then:
 *
 *      1.  Create a socket for it.  Device file handles are actually socket
 *          numbers.
 *      2.  Set up fields in the socket to indicate that it is for a device,
 *          and not an IP socket.
 *      3.  Call the device driver's open routine.
 *      4.  If the open succeeds, then save the socket number in the device driver
 *          table so we can do a reverse look up later.
 *      5.  Return the socket number as the file handle.
 *
 * Otherwise attempt to open a file with the indicated name.  If that succeeds, then
 * translate the file system's file handle into a CLIB file handle by oring in the
 * DDI_FILE_BIT.  Return the ored file handle.
 */
int
_open (const char * path,
       int          flags,
       ...)
{
	const char * filename = path;
	int mode = flags, permissions = -1;
	int ccode, i=0;
	int fd = -1;
    
    va_list argptr;

	setErrno (0);
	/* scan the device driver table for an exact match */
	while (i < MAX_DEV && strcmp (filename, deviceTable[i].name))
		i++;
	if (i != MAX_DEV)               /* if filename is name of device */
	{
		/* check flag */
		if ( mode & ~deviceTable[i].flags )
		{
			setErrno (EACCES);
			goto open_ret;
		}

        /*
         * Sockets are allocated for device files so select() can be used with
         * devices such as serial ports.
         */
        fd = nasockno_allocate(i, mode);
        if (fd < 0)
        {
        	setErrno(ENOMEM);
        	goto open_ret;
        }
    
    		                                    /* call device's open function*/
		ccode = deviceTable[i].open (deviceTable[i].channel, mode);
        if (ccode != EXIT_SUCCESS)
        {                                       /* if open failed then clean up */
        	int rc = nasockno_free(fd);
        	NA_ASSERT(rc == 0);
        	fd = -1;
        	setErrno(ccode);
        	goto open_ret;
        }
        else
        {
            deviceTable[i].fileDescriptor = fd;
        }
		goto open_ret;
	}
	else                                        /* else not a device, so try opening as a file*/
	{

	    if ((flags & O_CREAT) != 0x0) {         /* if create then get permissions */
	        va_start(argptr, flags); 
    	    permissions = va_arg(argptr, int); 
		    va_end(argptr);
	    }                                       /* call the file system open routine*/
		fd = naClib_fs_open(filename, mode, permissions); 
        if (fd != -1)                           /* if it worked then or in the DDI_FILE_BIT*/
        {
            fd |= DDI_FILE_BIT;                 /* This bit lets us differentiate between files and devices*/
        }        
	}


open_ret:
  return fd;
}


/*
 * Utility routine to translate a device name and channel number into a
 * file handle.  The file handle is stored in a field in the device's entry
 * in the device table.  This routine is used by the serial driver.
 */
int naTranslateChannelToFileDescriptor(char *prefix, int channel)
{
    unsigned index;
    int fd = -1;
    
    for (index = 0; index < MAX_DEV; index++)
    {
        if (strncmp(prefix, deviceTable[index].name, strlen(prefix)) == 0)
        {
            if (deviceTable[index].channel == channel)
            {
                fd = deviceTable[index].fileDescriptor;
                break;
            }
        }
    }
    
    return fd;
}


/*
 * CLIB close routine.  The file handle can be for a socket, file, or device.  Determine
 * which one a call the appropriate close routine.  If it is for a device, then make sure
 * we free up the socket too.
 */
int
_close (int file)
{
	int ret = -1;
	int idx;
	int fno = file;
    ttSocketEntryPtr sock = NULL;
    
    setErrno (0);
	if (IS_FILE(fno))                           /* if real file */
	{                                           /* pass onto CLIB interface */
		ret = naClib_fs_close(GET_FILE_HANDLE(fno));
		goto cleanup;
    }	    
	else                                        /* else if socket or device handle */
	{
	    sock = nasockptr_lock(fno);               /* get socket entry */
		
	    if (sock == NULL)
	    {
	        setErrno(EINVAL);
	        goto cleanup;
	    }
	    if (IS_DEVICE(sock))                    /* if a device handle */
	    {
    	    idx = naSockGetDevid(sock);               /* get device table index which is stored in socDevid*/
    		if (idx >= MAX_DEV || idx < 0) 
    		{
    			setErrno(EINVAL);
    			goto cleanup;
    		}                                   /* call device's close routine */
            ret = (deviceTable[idx].close) (deviceTable[idx].channel);
            SOCKPTR_UNLOCK(sock);               /* unlock socket so it can be freed*/
            nasockno_free(fno);                   /* free it */
    		if (ret != EXIT_SUCCESS)            /* if error closing */
    		{                                   /* copy error code into errno*/
    			setErrno(ret);
    			ret = -1;
    			goto cleanup;
    		}
        }
        else                                    /* if socket */
        {
            SOCKPTR_UNLOCK(sock);               /* unlock so we can make socket call */
            ret = tfClose(fno);                 /* pass onto closesocket */
        }
    }

cleanup:
    SOCKPTR_UNLOCK(sock);
    return ret;
}

void
_exit (int n)
{
    n = n;
}

int
_kill (int n, int m)
{
  n = n; 
  m = m;
  return -1;
}

int
_getpid (int n)
{
  return 1;
  n = n;
}

/*li Zhen*****  register char * stack_ptr asm ("sp"); */

caddr_t
_sbrk (int incr)
{
  extern char end asm ("__end__");	/* Defined by the linker */
  extern char  heap_limit[];	        /* Li Zhen Defined by the linker */
  static char * heap_end =NULL;
  char *        prev_heap_end;
  
  /*This is to support extended heap */
  extern unsigned long heap_adjustment; /* Li Zhen, defined in ncc_init.h. RAM addjustment after auto-sizing */
   

  if (heap_end == NULL)
    heap_end = & end;
  
  prev_heap_end = heap_end;
  
  if (heap_end + incr > (heap_limit + heap_adjustment))
    {
    	 
	  	/* Some of the libstdc++-v3 tests rely upon detecting
	 	out of memory errors, so do not abort here.  */
		#if 0
      			extern void abort (void);

      			_write (1, "_sbrk: Heap and stack collision\n", 32);
      
      			abort ();
		#else
      
      		errno = ENOMEM;
      		return (caddr_t) -1;
		#endif
	 
    }
    else
    {
  	heap_end += incr;

  	return (caddr_t) prev_heap_end;
    }
}


int
_fstat (int file, struct stat * st)
{
  memset (st, 0, sizeof (* st));
  st->st_mode = S_IFCHR;
  st->st_blksize = 1024;
  return 0;
  file = file;
}

int _stat (const char *fname, struct stat *st)
{
  int file;

  /* The best we can do is try to open the file readonly.  If it exists,
     then we can guess a few things about it.  */
  if ((file = _open (fname, O_RDONLY)) < 0)
    return -1;

  memset (st, 0, sizeof (* st));
  st->st_mode = S_IFREG | S_IREAD;
  st->st_blksize = 1024;
  _close (file); /* Not interested in the error.  */
  return 0;
}

int
_link (void)
{
  return -1;
}

extern int naClib_fs_unlink(const char * filename);
_unlink (const char * filename)
{
  return naClib_fs_unlink(filename);
}


void
_raise (void)
{
  return;
}

/* Return a clock that ticks at 100Hz.  */
clock_t 
_times (struct tms * tp)
{

  if (tp)
    {
      tp->tms_utime  = 0;
      tp->tms_stime  = 0;	/* system time */
      tp->tms_cutime = 0;	/* user time, children */
      tp->tms_cstime = 0;	/* system time, children */
    }
  
  return 0;
};


/*
 * This routine returns TRUE iff the file handle is for a TTY device.  The
 * the information is stored in one of the fields in the device driver table.
 */
int
isatty (int fno)
{
    int ret = 0;                                /* default return value is "no this is not a TTY device" */
    ttSocketEntryPtr sock = NULL;
    
	if (!IS_FILE(fno))                          /* if a device or socket*/
	{
	    sock = nasockptr_lock(fno);               /* get a pointer to the socket entry*/
		
	    if (sock == NULL)
	    {
	        setErrno(EINVAL);
	        goto cleanup;
	    }
	    if (IS_DEVICE(sock))                    /* if this is one of our device entries*/
	    {
    	    unsigned idx = naSockGetDevid(sock);      /* then look up device table index*/
    		if (idx >= MAX_DEV || idx < 0) 
    		{
    			setErrno(EINVAL);
    			goto cleanup;
    		}
        	ret = deviceTable[idx].isTty;       /* and get info from isTty field*/
        }
    }

cleanup:
    SOCKPTR_UNLOCK(sock);
    return ret;
}

int
_system (const char *s)
{
  if (s == NULL)
    return 0;
  errno = ENOSYS;
  return -1;
}

extern int naClib_fs_rename(const char * oldpath, const char * newpath);
int
_rename (const char * oldpath, const char * newpath)
{
  return naClib_fs_rename(oldpath, newpath);
}

/* This is added for NET+OS                 */
/* Function : ioctl                         */
/* Parameters: fd - handle return by open   */
/*             request - attrib to be s/get */
/*             argp - value of attrib       */
int ioctl (int fno, int request, char *argp)
{
	int actual_read, ret = -1, idx;
    ttSocketEntryPtr sock = NULL;
    
	if (IS_FILE(fno))                           /* is this a file*/
	{                                           /* NET+OS doesn't like ioctls on files */
		setErrno(EINVAL);
		goto cleanup;
    }	    
	else                                        /* else if this might be a device*/
	{
	    sock = nasockptr_lock(fno);               /* get pointer to socket entry*/
		
		setErrno (0);
	    if (sock == NULL)
	    {
	        setErrno(EINVAL);
	        goto cleanup;
	    }
	    if (IS_DEVICE(sock))                    /* if it is a device */
	    {
    	    idx = naSockGetDevid(sock);
    		if (idx >= MAX_DEV || idx < 0) 
    		{
    			setErrno(EINVAL);
    			goto cleanup;
    		}                                   /* then call drivers ioctl routine */
        	ret = (deviceTable[idx].ioctl) (deviceTable[idx].channel, request, argp);
        	if (ret != EXIT_SUCCESS)
        	{
        		setErrno(ret);
        		ret = -1;
        		goto cleanup;
        	}
        }
        else /* if socket */
        {
   	        setErrno(EINVAL);                   /* must use setsockopt/getsockopt with sockets*/
	        goto cleanup;
	    }
    }
cleanup:
    SOCKPTR_UNLOCK(sock);
    return ret;
}


int mkdir (const char *dirname, mode_t prot)
{
	return naClib_fs_mkdir(dirname, prot);
}

int creat (const char *filename, mode_t prot)
{

  return open (filename, prot);
}

