/*
 * I/O streams.
 * Copyright (c) 1998 New Generation Software (NGS) Oy
 *
 * Author: Markku Rossi <mtr@ngs.fi>
 */

/*
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
 * MA 02111-1307, USA
 */

/*
 * $Source: /tmp/cvsroot-15-2-2007/clamav-devel/libclamav/js/iostream.c,v $
 * $Id: iostream.c,v 1.2 2006/10/28 11:27:44 njh Exp $
 */
#if HAVE_CONFIG_H
#include "clamav-config.h"
#endif

#ifdef	CL_EXPERIMENTAL

#include "jsint.h"

/*
 * Types and definitions.
 */

#define DEFAULT_BUFFER_SIZE 4096

/*
 * Global functions.
 */

JSIOStream *
js_iostream_new ()
{
  JSIOStream *stream;

  stream = js_calloc (NULL, 1, sizeof (*stream));
  if (stream == NULL)
    return NULL;

  stream->buflen = DEFAULT_BUFFER_SIZE;
  stream->buffer = js_malloc (NULL, stream->buflen);
  if (stream->buffer == NULL)
    {
      js_free (stream);
      return NULL;
    }

  return stream;
}


/* The `FILE *' stream. */

static int
file_read (void *context, unsigned char *buffer, unsigned int todo,
	   int *error_return)
{
  int got;

  errno = 0;
  got = fread (buffer, 1, todo, (FILE *) context);
  *error_return = errno;

  return got;
}


static int
file_write (void *context, unsigned char *buffer, unsigned int todo,
	    int *error_return)
{
  int wrote;

  errno = 0;
  wrote = fwrite (buffer, 1, todo, (FILE *) context);
  *error_return = errno;

  return wrote;
}


static int
file_seek (void *context, long offset, int whence)
{
  return fseek ((FILE *) context, offset, whence);
}


static long
file_get_position (void *context)
{
  return ftell ((FILE *) context);
}


static long
file_get_length (void *context)
{
  FILE *fp = (FILE *) context;
  long cpos;
  long result = -1;

  /* Save current position. */
  cpos = ftell (fp);
  if (cpos >= 0)
    {
      /* Seek to the end of the file. */
      if (fseek (fp, 0, SEEK_END) >= 0)
	{
	  /* Fetch result. */
	  result = ftell (fp);

	  /* Seek back. */
	  if (fseek (fp, cpos, SEEK_SET) < 0)
	    /* Couldn't revert the fp to the original position. */
	    result = -1;
	}
    }

  return result;
}


static void
file_close (void *context)
{
  fclose ((FILE *) context);
}


JSIOStream *
js_iostream_file (FILE *fp, int readp, int writep, int do_close)
{
  JSIOStream *stream;

  if (fp == NULL)
    return NULL;

  stream = js_iostream_new ();
  if (stream == NULL)
    return NULL;

  if (readp)
    stream->read = file_read;
  if (writep)
    stream->write = file_write;

  stream->seek		= file_seek;
  stream->get_position	= file_get_position;
  stream->get_length	= file_get_length;

  if (do_close)
    stream->close	= file_close;

  stream->context 	= fp;

  return stream;
}


static void
close_pipe (void *context)
{
  pclose ((FILE *) context);
}


JSIOStream *
js_iostream_pipe (FILE *fp, int readp)
{
  JSIOStream *stream;

  if (fp == NULL)
    return NULL;

  stream = js_iostream_new ();

  if (stream == NULL)
    return NULL;

  if (readp)
    stream->read = file_read;
  else
    stream->write = file_write;

  stream->seek		= file_seek;
  stream->get_position	= file_get_position;
  stream->get_length	= file_get_length;
  stream->close		= close_pipe;
  stream->context	= fp;

  return stream;
}


size_t
js_iostream_read (JSIOStream *stream, void *v, size_t size)
{
	unsigned char *ptr = (unsigned char *)v;
  size_t total = 0;
  int got;

  if (stream->writep)
    {
      /* We have buffered output data. */
      if (js_iostream_flush (stream) == EOF)
	return 0;

      assert (stream->writep == 0);
    }

  while (size > 0)
    {
      /* First, take everything from the buffer. */
      if (stream->bufpos < stream->data_in_buf)
	{
	  got = stream->data_in_buf - stream->bufpos;

	  if (size < got)
	    got = size;

	  memcpy (ptr, stream->buffer + stream->bufpos, got);

	  stream->bufpos += got;
	  size -= got;
	  ptr += got;
	  total += got;
	}
      else
	{
	  if (stream->at_eof)
	    /* EOF seen, can't read more. */
	    break;

	  js_iostream_fill_buffer (stream);
	}
    }

  return total;
}


size_t
js_iostream_write (JSIOStream *stream, void *v, size_t size)
{
  int space;
  size_t total = 0;
  unsigned char *ptr = (unsigned char *)v;

  if (stream->write == NULL)
    {
      stream->error = EBADF;
      return 0;
    }

  if (!stream->writep && stream->bufpos < stream->data_in_buf)
    {
      /*
       * We have some buffered data in the stream => the actual stream
       * position in stream->context is not in sync with stream->bufpos.
       * Seek back.
       */

      if ((*stream->seek) (stream->context, SEEK_CUR,
			   stream->bufpos - stream->data_in_buf) < 0)
	/* XXX Error value. */
	return 0;

      stream->bufpos = 0;
      stream->data_in_buf = 0;
    }

  while (size > 0)
    {
      space = stream->buflen - stream->data_in_buf;
      if (size < space)
	space = size;

      /* Append data to the buffer. */
      memcpy (stream->buffer + stream->data_in_buf, ptr, space);
      stream->data_in_buf += space;
      total += space;
      size -= space;
      ptr += space;

      /* Now the buffer contains buffered write data. */
      stream->writep = 1;

      if (size > 0)
	{
	  /* Still some data left.  Must flush  */
	  if (js_iostream_flush (stream) == EOF)
	    return total;
	}
    }

  /* Autoflush. */
  if (stream->autoflush && stream->writep)
    if (js_iostream_flush (stream) == EOF)
      /* Failed.  Just return something smaller than <size> */
      return total - stream->data_in_buf;

  return total;
}


int
js_iostream_flush (JSIOStream *stream)
{
  if (stream == NULL || stream->write == NULL || !stream->writep)
    return 0;

  stream->writep = 0;
  assert (stream->bufpos == 0);

  if (stream->data_in_buf > 0)
    {
      int to_write = stream->data_in_buf;

      stream->data_in_buf = 0;
      if ((*stream->write) (stream->context, stream->buffer,
			    to_write, &stream->error) < to_write)
	{
	  stream->error = errno;
	  return EOF;
	}
    }

  return 0;
}


int
js_iostream_unget (JSIOStream *stream, int byte)
{
  if (stream->writep)
    {
      /* We have buffered output data. */
      if (js_iostream_flush (stream) == EOF)
	return EOF;

      assert (stream->writep == 0);
    }

  if (stream->bufpos > 0)
    {
      /* It fits. */
      stream->buffer[--stream->bufpos] = byte;
    }
  else if (stream->data_in_buf < stream->buflen)
    {
    move:
      memmove (stream->buffer + 1, stream->buffer, stream->data_in_buf);
      stream->data_in_buf++;
      stream->buffer[0] = byte;
    }
  else
    {
      /* Allocate a bigger buffer.  */
      unsigned char *new_buffer = js_realloc (NULL, stream->buffer,
					      stream->buflen + 1);
      if (new_buffer == NULL)
	{
	  stream->error = errno;
	  return EOF;
	}

      stream->buflen++;
      stream->buffer = new_buffer;
      goto move;
    }

  /* Upon successful completion, we must return the byte. */
  return byte;
}


int
js_iostream_close (JSIOStream *stream)
{
  int result = 0;

  if (stream == NULL)
    return result;

  if (js_iostream_flush (stream) == EOF)
    result = EOF;

  if (stream->close)
    (*stream->close) (stream->context);

  js_free (stream->buffer);
  js_free (stream);

  return result;
}


int
js_iostream_seek (JSIOStream *stream, long offset, int whence)
{
  int result;

  if (js_iostream_flush (stream) == EOF)
    return -1;

  result = (*stream->seek) (stream->context, offset, whence);
  if (result == 0)
    /* Successful.  Clear the eof flag. */
    stream->at_eof = 0;

  return result;
}


long
js_iostream_get_position (JSIOStream *stream)
{
  long pos;

  /* Flush the possible buffered output. */
  if (js_iostream_flush (stream) == EOF)
    return -1;

  pos = (*stream->get_position) (stream->context);
  if (pos < 0)
    return pos;

  /*
   * The logical position if at <bufpos>, the context's idea is at
   * <data_in_buf>.  Adjust.
   */
  return pos - (stream->data_in_buf - stream->bufpos);
}


long
js_iostream_get_length (JSIOStream *stream)
{
  /* Flush the possible buffered output. */
  if (js_iostream_flush (stream) == EOF)
    return -1;

  return (*stream->get_length) (stream->context);
}


void
js_iostream_fill_buffer (JSIOStream *stream)
{
  if (stream->read == NULL)
    {
      stream->at_eof = 1;
      return;
    }

  stream->data_in_buf = (*stream->read) (stream->context, stream->buffer,
					 stream->buflen, &stream->error);
  stream->bufpos = 0;
  if (stream->data_in_buf == 0)
    stream->at_eof = 1;
}
#endif	/*CL_EXPERIMENTAL*/
