/*
 * (c) Copyright 1992, 1993 by Panagiotis Tsirigotis
 * All rights reserved.  The file named COPYRIGHT specifies the terms 
 * and conditions for redistribution.
 */

static char RCSid[] = "$Id: sio.c,v 1.3 1995/09/10 16:40:28 chuck Exp $" ;
static char sio_version[] = VERSION ;

#include <sys/types.h>
#include <sys/stat.h>

#include "sio.h"
#include "impl.h"

#ifdef EVENTS
#include "events.h"
#endif

#if defined(__FreeBSD__) || defined(__bsdi__) || defined(__NetBSD__)
PRIVATE char *sio_memscan( char *from, int how_many, register char ch ) ;
#endif

/*
 * SIO WRITE FUNCTIONS: Swrite, Sputc
 */

/*
 * Stream write call: arguments same as those of write(2)
 */
int Swrite( fd, addr, nbytes )
	int fd ;
	register char *addr ;
	register int nbytes ;
{
	register __sio_descriptor_t *dp = &__sio_descriptors[ fd ] ;
	register __sio_od_t *odp = ODP( dp ) ;
	register int b_transferred ;
	register int b_avail ;
	int total_b_transferred ;
	int b_written ;
	int b_in_buffer ;

#ifdef EVENTS
	EVENT( fd, EV_SWRITE ) ;
#endif

	IO_SETUP( fd, dp, __SIO_OUTPUT_STREAM, SIO_ERR ) ;
	ASSERT( odp->start <= odp->nextb && odp->nextb <= odp->buf_end ) ;

	b_avail = odp->buf_end - odp->nextb ;
	b_transferred = MIN( nbytes, b_avail ) ;
	sio_memcopy( addr, odp->nextb, b_transferred ) ;
	odp->nextb += b_transferred ;

	/*
	 * check if we are done
	 */
	if ( b_transferred == nbytes )
		return( b_transferred ) ;

	/*
	 * at this point we know that the buffer is full
	 */
	b_in_buffer = odp->buf_end - odp->start ;
	b_written = __sio_writef( odp, fd ) ;
	if ( b_written != b_in_buffer )
		return( (b_written >= nbytes) ? nbytes : b_written ) ;
	
	total_b_transferred = b_transferred ;
	addr += b_transferred ;
	nbytes -= b_transferred ;

	for ( ;; )
	{
		b_transferred = MIN( nbytes, odp->buffer_size ) ;
		sio_memcopy( addr, odp->nextb, b_transferred ) ;
		odp->nextb += b_transferred ;
		nbytes -= b_transferred ;
		if ( nbytes == 0 )
		{
			total_b_transferred += b_transferred ;
			break ;
		}
		/*
		 * the buffer is full
		 */
		b_written = __sio_writef( odp, fd ) ;
		if ( b_written != odp->buffer_size )
		{
			if ( b_written != SIO_ERR )
			{
				total_b_transferred += b_written ;
				odp->nextb += b_written ;
			}
			break ;
		}
		/*
		 * everything is ok
		 */
		total_b_transferred += b_transferred ;
		addr += b_transferred ;
	}
	return( total_b_transferred ) ;
}


/*
 * Add a character to a file
 */
int Sputc( fd, c )
	int fd ;
	char c ;
{
	register __sio_descriptor_t *dp = &__sio_descriptors[ fd ] ;
	register __sio_od_t *odp = ODP( dp ) ;

#ifdef EVENTS
	EVENT( fd, EV_SPUTC ) ;
#endif
	
	IO_SETUP( fd, dp, __SIO_OUTPUT_STREAM, SIO_ERR ) ;
	ASSERT( odp->start <= odp->nextb && odp->nextb <= odp->buf_end ) ;

	/*
	 * The following is a weak check since we should really be
	 * checking that nextb == buf_end (it would be an error for
	 * nextb to exceed buf_end; btw, the assertion above, when
	 * enabled makes sure this does not occur).
	 *
	 * NOTE: __sio_writef NEVER uses data beyond the end of buffer.
	 */
	if ( odp->nextb >= odp->buf_end )
	{
		int b_in_buffer = odp->buf_end - odp->start ;

		/*
		 * There is nothing we can do if __sio_writef does not manage
		 * to write the whole buffer
		 */
		if ( __sio_writef( odp, fd ) != b_in_buffer )
			return( SIO_ERR ) ;
	}
	*odp->nextb++ = c ;
	if ( __SIO_MUST_FLUSH( *odp, c ) && __sio_writef( odp, fd ) == SIO_ERR )
		return( SIO_ERR ) ;
	return ( c ) ;
}



/*
 * SIO READ FUNCTIONS
 */

/*
 * Stream read call: arguments same as those of read(2)
 */
int Sread( fd, addr, nbytes )
	int fd ;
	char *addr ;
	int nbytes ;
{
	register __sio_descriptor_t *dp = &__sio_descriptors[ fd ] ;
	register __sio_id_t *idp = IDP( dp ) ;
	register int b_transferred ;
	int b_read ;
	int total_b_transferred ;
	int b_left ;

#ifdef EVENTS
	EVENT( fd, EV_SREAD ) ;
#endif

	IO_SETUP( fd, dp, __SIO_INPUT_STREAM, SIO_ERR ) ;
	ASSERT( idp->start <= idp->nextb && idp->nextb <= idp->end ) ;

	b_left = idp->end - idp->nextb ;
	b_transferred = MIN( b_left, nbytes ) ;
	sio_memcopy( idp->nextb, addr, b_transferred ) ;
	idp->nextb += b_transferred ;
	if ( b_transferred == nbytes )
		return( b_transferred ) ;
	
	nbytes -= b_transferred ;
	total_b_transferred = b_transferred ;
	addr += b_transferred ;

	do
	{
		b_read = __sio_readf( idp, fd ) ;
		switch ( b_read )
		{
			case SIO_ERR:
				if ( total_b_transferred == 0 )
					return( SIO_ERR ) ;
				/* FALL THROUGH */
			
			case 0:
				return( total_b_transferred ) ;
		}
			
		b_transferred = MIN( b_read, nbytes ) ;
		sio_memcopy( idp->nextb, addr, b_transferred ) ;
		addr += b_transferred ;
		idp->nextb += b_transferred ;
		total_b_transferred += b_transferred ;
		nbytes -= b_transferred ;
	}
	while ( nbytes && b_read == idp->buffer_size ) ;
	return( total_b_transferred ) ;
}



/*
 * Read a line from a file
 * Returns a pointer to the beginning of the line or NULL
 */
char *Srdline( fd )
	int fd ;
{
	register __sio_descriptor_t *dp = &__sio_descriptors[ fd ] ;
	register __sio_id_t *idp = IDP( dp ) ;
	register char *cp ;
	register char *line_start ;
	register int b_left ;
	register int extension ;

#ifdef EVENTS
	EVENT( fd, EV_SRDLINE ) ;
#endif

	IO_SETUP( fd, dp, __SIO_INPUT_STREAM, NULL ) ;
	ASSERT( idp->start <= idp->nextb && idp->nextb <= idp->end ) ;

#ifdef HAS_MMAP
	if ( idp->memory_mapped && __sio_switch( idp, fd ) == FAILURE )
		return( NULL ) ;
#endif

	b_left = idp->end - idp->nextb ;
	/*
	 * Look for a '\n'. If the search fails, extend the buffer
	 * and search again (the extension is performed by copying the
	 * bytes that were searched to the auxiliary buffer and reading 
	 * new input in the main buffer).
	 * If the new input still does not contain a '\n' and there is
	 * more space in the main buffer (this can happen with network
	 * connections), read more input until either the buffer is full
	 * or a '\n' is found.
	 * Finally, set cp to point to the '\n', and line_start to
	 * the beginning of the line
	 */
	if ( b_left && ( cp = sio_memscan( idp->nextb, b_left, '\n' ) ) != NULL )
	{
		line_start = idp->nextb ;
		idp->nextb = cp + 1 ;
	}
	else
	{
		extension = __sio_extend_buffer( idp, fd, b_left ) ;
		if ( extension > 0 )
		{
			ASSERT( idp->start <= idp->nextb && idp->nextb <= idp->end ) ;

			line_start = idp->start ;
			cp = sio_memscan( idp->nextb, extension, '\n' ) ;
			if ( cp != NULL )
				idp->nextb = cp + 1 ;
			else
				for ( ;; )
				{
					idp->nextb = idp->end ;
					extension = __sio_more( idp, fd ) ;
					if ( extension > 0 )
					{
						cp = sio_memscan( idp->nextb, extension, '\n' ) ;
						if ( cp == NULL )
							continue ;
						idp->nextb = cp + 1 ;
						break ;
					}
					else
					{
						/*
						 * If there is spare room in the buffer avoid trashing
						 * the last character
						 */
						if ( idp->end < &idp->buf[ idp->buffer_size ] )
							cp = idp->end ;
						else
							cp = &idp->buf[ idp->buffer_size - 1 ] ;
						break ;
					}
				}
		}
		else					/* buffer could not be extended */
			if ( b_left == 0 )
			{
				/*
				 * Set errno to 0 if EOF has been reached
				 */
				if ( extension == 0 )
					errno = 0 ;
				return( NULL ) ;
			}
			else
			{
				line_start = idp->start ;
				cp = idp->end ;
				/*
				 * By setting idp->nextb to be equal to idp->end,
				 * subsequent calls to Srdline will return NULL because
				 * __sio_extend_buffer will be invoked and it will return 0.
				 */
				idp->nextb = idp->end ;
			}
	}
	*cp = NUL ;
	idp->line_length = cp - line_start ;
	return( line_start ) ;
}


/*
 * Get a character from a file
 */
int Sgetc( fd )
	int fd ;
{
	register __sio_descriptor_t *dp = &__sio_descriptors[ fd ] ;
	register __sio_id_t *idp = IDP( dp ) ;

#ifdef EVENTS
	EVENT( fd, EV_SGETC ) ;
#endif

	IO_SETUP( fd, dp, __SIO_INPUT_STREAM, SIO_ERR ) ;
	ASSERT( idp->start <= idp->nextb && idp->nextb <= idp->end ) ;
	if ( idp->nextb >= idp->end )
	{
		register int b_read = __sio_readf( idp, fd ) ;

		if ( b_read == 0 )
			return( SIO_EOF ) ;
		else if ( b_read == SIO_ERR )
			return( SIO_ERR ) ;
	}
	return( (int) *idp->nextb++ ) ;
}


char *Sfetch( fd, lenp )
	int fd ;
	long *lenp ;
{
	register __sio_descriptor_t *dp = &__sio_descriptors[ fd ] ;
	register __sio_id_t *idp = IDP( dp ) ;
	register int b_read ;
	register char *p ;

#ifdef EVENTS
	EVENT( fd, EV_SFETCH ) ;
#endif

	IO_SETUP( fd, dp, __SIO_INPUT_STREAM, NULL ) ;
	ASSERT( idp->start <= idp->nextb && idp->nextb <= idp->end ) ;
	if ( idp->nextb >= idp->end )
	{
		b_read = __sio_readf( idp, fd ) ;
		if ( b_read == SIO_ERR )
			return( NULL ) ;
		if ( b_read == 0 )
		{
			errno = 0 ;
			return( NULL ) ;
		}
	}
	*lenp = idp->end - idp->nextb ;
	p = idp->nextb ;
	idp->nextb = idp->end ;
	return( p ) ;
}



/*
 * SIO CONTROL FUNCTIONS
 */

/*
 * Undo the last Srdline or Sgetc
 */
int Sundo( fd, type )
	int fd ;
	int type ;
{
	register __sio_descriptor_t *dp = &__sio_descriptors[ fd ] ; 
	register __sio_id_t *idp = IDP( dp ) ;
	int retval = 0 ;

#ifdef EVENTS
	EVENT( fd, EV_SUNDO ) ;
#endif

	CONTROL_SETUP( dp, __SIO_INPUT_STREAM, SIO_ERR ) ;

	/*
	 * Undo works only for fd's used for input
	 */
	if ( dp->stream_type != __SIO_INPUT_STREAM )
		return( SIO_ERR ) ;

	/*
	 * Check if the operation makes sense; if so, do it, otherwise ignore it
	 */
	switch ( type )
	{
		case SIO_UNDO_LINE:
			if ( idp->nextb - idp->line_length > idp->start )
			{
				*--idp->nextb = '\n' ;
				idp->nextb -= idp->line_length ;
			}
			ASSERT( idp->start <= idp->nextb && idp->nextb <= idp->end ) ;
			break ;
		
		case SIO_UNDO_CHAR:
			if ( idp->nextb > idp->start )
				idp->nextb-- ;
			ASSERT( idp->start <= idp->nextb && idp->nextb <= idp->end ) ;
			break ;
		
		default:
			retval = SIO_ERR ;
			break ;
	}
	return( retval ) ;
}


/*
 * Flush the buffer associated with the given file descriptor
 * The special value, SIO_FLUSH_ALL flushes all buffers
 *
 * Return value:
 *			0 :  if fd is SIO_FLUSH_ALL or if the flush is successful
 *		SIO_ERR: if fd is not SIO_FLUSH_ALL and
 *								the flush is unsuccessful
 *							or the descriptor is not initialized or it is not 
 *								an output descriptor
 */
int Sflush( fd )
	int fd ;
{
   register __sio_descriptor_t *dp ;
   int b_in_buffer ;

#ifdef EVENTS
	EVENT( fd, EV_SFLUSH ) ;
#endif

   if ( fd == SIO_FLUSH_ALL )
   {
      for ( fd = 0, dp = __sio_descriptors ;
				fd < N_SIO_DESCRIPTORS ;
				dp++, fd++ )
         if ( DESCRIPTOR_INITIALIZED( dp ) &&
							dp->stream_type == __SIO_OUTPUT_STREAM )
            (void) __sio_writef( ODP( dp ), fd ) ;
      return( 0 ) ;
   }
   else
   {
      dp = &__sio_descriptors[ fd ] ;

		CONTROL_SETUP( dp, __SIO_OUTPUT_STREAM, SIO_ERR ) ;
      b_in_buffer = ODP( dp )->nextb - ODP( dp )->start ;
      if ( __sio_writef( ODP( dp ), fd ) != b_in_buffer )
         return( SIO_ERR ) ;
      else
         return( 0 ) ;
   }
}


/*
 * Close the file descriptor. This call is provided because
 * a file descriptor may be closed and then reopened. There is
 * no easy way for SIO to identify such a situation, so Sclose
 * must be used.
 *
 * Sclose invokes Sdone which finalizes the buffer.
 * There is no SIO_CLOSE_ALL value for fd because such a thing
 * would imply that the program will exit very soon, therefore
 * the closing of all file descriptors will be done in the kernel
 * (and the finalization will be done by the finalization function
 * NOTE: not true if the OS does not support a finalization function)
 *
 * There is no need to invoke SETUP; Sdone will do it.
 */
int Sclose( fd )
	int fd ;
{
#ifdef EVENTS
	EVENT( fd, EV_SCLOSE ) ;
#endif

	if ( __SIO_FD_INITIALIZED( fd ) )
		if ( Sdone( fd ) == SIO_ERR )
			return( SIO_ERR ) ;
	return( close( fd ) ) ;
}



/*
 * Tie the file descriptor in_fd to the file descriptor out_fd
 * This means that whenever a read(2) is done on in_fd, it is
 * preceded by a write(2) on out_fd.
 * Why this is nice to have:
 * 	1) When used in filters it maximizes concurrency
 *		2) When the program prompts the user for something it forces
 *			the prompt string to be displayed even if it does not
 *			end with a '\n' (which would cause a flush).
 * In this implementation, out_fd cannot be a regular file.
 * This is done to avoid non-block-size write's.
 * The file descriptors are initialized if that has not been done
 * already. If any of them is initialized, it must be for the appropriate
 * stream type (input or output).
 *
 * NOTE: the code handles correctly the case when in_fd == out_fd
 */
int Stie( in_fd, out_fd )
	int in_fd, out_fd ;
{
	struct stat st ;
	register __sio_descriptor_t *dp ;
	int was_initialized ;
	boolean_e failed = NO ;

#ifdef EVENTS
	EVENT( in_fd, EV_STIE ) ;
#endif

	/*
	 * Check if the out_fd is open
	 */
	if ( fstat( out_fd, &st ) == -1 )
		return( SIO_ERR ) ;

	/*
	 * If the out_fd refers to a regular file, the request is silently ignored
	 */
	if ( ( st.st_mode & S_IFMT ) == S_IFREG )
		return( 0 ) ;
	
	dp = &__sio_descriptors[ in_fd ] ;
	was_initialized = dp->initialized ;		/* remember if it was initialized */
	IO_SETUP( in_fd, dp, __SIO_INPUT_STREAM, SIO_ERR ) ;

	/*
	 * Perform manual initialization of out_fd to avoid leaving in_fd
	 * initialized if the initialization of out_fd fails.
	 * If out_fd is initialized, check if it is used for output.
	 * If it is not initialized, initialize it for output.
	 */
	dp = &__sio_descriptors[ out_fd ] ;
	if ( DESCRIPTOR_INITIALIZED( dp ) )
	{
		if ( dp->stream_type != __SIO_OUTPUT_STREAM )
		{
			failed = YES ;
			errno = EBADF ;
		}
	}
	else
		if ( __sio_init( dp, out_fd, __SIO_OUTPUT_STREAM ) == SIO_ERR )
			failed = YES ;

	if ( failed == NO )
	{
		__SIO_ID( in_fd ).tied_fd = out_fd ;
		return( 0 ) ;
	}
	else
	{
		/*
		 * We failed. If we did any initialization, undo it
		 */
		if ( ! was_initialized )
		{
			int save_errno = errno ;

			(void) Sdone( in_fd ) ;
			errno = save_errno ;
		}
		return( SIO_ERR ) ;
	}
}


/*
 * Untie a file descriptor
 */
int Suntie( fd )
	int fd ;
{
	register __sio_descriptor_t *dp = &__sio_descriptors[ fd ] ;

#ifdef EVENTS
	EVENT( fd, EV_SUNTIE ) ;
#endif

	CONTROL_SETUP( dp, __SIO_INPUT_STREAM, SIO_ERR ) ;
	
	if ( IDP( dp )->tied_fd != SIO_NO_TIED_FD )
	{
		IDP( dp )->tied_fd = SIO_NO_TIED_FD ;
		return( 0 ) ;
	}
	else
	{
		errno = EBADF ;
		return( SIO_ERR ) ;
	}
}


/*
 * Changes the type of buffering on the specified descriptor.
 * As a side-effect, it initializes the descriptor as an output stream.
 */
int Sbuftype( fd, type )
	int fd, type ;
{
	register __sio_descriptor_t *dp = &__sio_descriptors[ fd ] ;

#ifdef EVENTS
	EVENT( fd, EV_SBUFTYPE ) ;
#endif

	/*
	 * Check for a valid type
	 */
	if ( type != SIO_LINEBUF && type != SIO_FULLBUF && type != SIO_NOBUF )
	{
		errno = EINVAL ;
		return( SIO_ERR ) ;
	}

	IO_SETUP( fd, dp, __SIO_OUTPUT_STREAM, SIO_ERR ) ;
	ODP( dp )->buftype = type ;
	return( 0 ) ;
}


#ifndef sio_memscan

PRIVATE char *sio_memscan( from, how_many, ch )
   char *from ;
   int how_many ;
   register char ch ;
{
   register char *p ;
   register char *last = from + how_many ;

   for ( p = from ; p < last ; p++ )
      if ( *p == ch )
         return( p ) ;
      return( 0 ) ;
}

#endif	/* sio_memscan */


#ifdef NEED_MEMCOPY

void __sio_memcopy( from, to, nbytes )
   register char *from, *to ;
   register int nbytes ;
{
   while ( nbytes-- )
      *to++ = *from++ ;
}

#endif /* NEED_MEMCOPY */


