/* emi.c */

/* Copyright 1997-2000 by Eberhard Mattes <em-gw@windhager.de>
   Donated to the public domain.  No warranty.

   1997-07-19 Initial version
   2000-05-01 Add emi_fill()
   2000-05-12 Fix emi_fill() */

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/time.h>
#include "libemfw.h"
#include "emio.h"

/* Open the file whose name is specified by the string pointed to by
   FNAME.  The file must already exist.  BUFSIZE is the size of the
   buffer to be used for the file.  Return NULL on error. */

EMI_FILE *emi_open (const char *fname, size_t bufsize)
{
  int fd;

  if (fname == NULL || bufsize == 0)
    {
      errno = EINVAL; return NULL;
    }
  fd = open (fname, O_RDONLY);
  if (fd == -1)
    return NULL;
  return emi_fdopen (fd, bufsize);
}

/* Make an EMI_FILE from a file descriptor.  BUFSIZE is the size of
   the buffer to be used for the file.  Return NULL on error. */

EMI_FILE *emi_fdopen (int fd, size_t bufsize)
{
  EMI_FILE *f;

  if (bufsize == 0)
    {
      errno = EINVAL; return NULL;
    }
  f = (EMI_FILE *)malloc (sizeof (EMI_FILE));
  if (f == NULL)
    {
      errno = ENOMEM; return NULL;
    }
  f->buf = (char *)malloc (bufsize);
  if (f->buf == NULL)
    {
      free (f);
      errno = ENOMEM; return NULL;
    }
  f->fd = fd;
  f->flags = 0;
  f->bufsize = bufsize;
  f->limit = -1;		/* Unlimited */
  f->timeout = 0;		/* No timeout */
  f->error = 0; f->amount = 0;
  f->ptr = f->end = f->buf;
  return f;
}

/* Close F.  Return 0 on success, -1 on error. */

int emi_close (EMI_FILE *f)
{
  int fd;

  DEBUG_ASSERT (f != NULL);
  fd = f->fd;
  DEBUG_ASSERT (f->buf != NULL);
  free (f->buf);
  free (f);
  return close (fd);
}

/* Helper function for emi_read(), emi_peek(), and emi_getc1().  Read
   up to SIZE bytes from the file F to the array pointed to by DST.
   Return the actual number of bytes read.  Return -1 on error.  This
   is where the time out is implemented. */

static int emi_read1 (EMI_FILE *f, void *dst, int size)
{
  int n;

  DEBUG_ASSERT (f != NULL);
  DEBUG_ASSERT (dst != NULL);
  DEBUG_ASSERT (size > 0);

  /* Handle limit. */

  DEBUG_ASSERT (f->limit >= -1);
  if (f->limit != -1 && f->limit < size)
    size = (int)f->limit;
  if (size == 0)
    {
      f->flags |= EMI_FLAG_EOF;
      return 0;
    }

  /* Handle timeout. */

  if (f->timeout != 0)
    {
      fd_set rfds;
      struct timeval tv;

      DEBUG_ASSERT (f->timeout > 0);
      FD_ZERO (&rfds);
      FD_SET (f->fd, &rfds);	/* TODO: Check for overflow */
      tv.tv_sec = f->timeout; tv.tv_usec = 0;
      n = select (f->fd + 1, &rfds, (fd_set *)0, (fd_set *)0, &tv);
      if (n <= 0)
	{
	  if (n == 0)
	    errno = ETIMEDOUT;
	  f->flags |= EMI_FLAG_ERROR; f->error = errno;
	  return -1;
	}
    }

  n = read (f->fd, dst, size);
  if (n == -1)
    {
      f->flags |= EMI_FLAG_ERROR; f->error = errno;
    }
  else if (n == 0)
    f->flags |= EMI_FLAG_EOF;
  else
    {
      f->flags &= ~EMI_FLAG_EOF;
      if (f->limit != -1)
	{
	  f->limit -= n;
	  DEBUG_ASSERT (f->limit >= 0);
	}
      f->amount += n;
    }
  return n;
}

/* Read up to SIZE bytes from F to the buffer pointed to by DST.
   Return the actual number of bytes read.  Return -1 on error. */

int emi_read (EMI_FILE *f, void *dst, int size)
{
  int total = 0, n;

  DEBUG_ASSERT (f != NULL);
  DEBUG_ASSERT (dst != NULL);
  if (size < 0)
    {
      errno = EINVAL; return -1;
    }
  if (f->flags & EMI_FLAG_ERROR)
    {
      errno = f->error; return -1;
    }

  /* Get data from the buffer if there's any data in the buffer. */

  if (f->ptr != f->end)
    {
      n = (int) (f->end - f->ptr);
      DEBUG_ASSERT (n >= 0 && n <= f->bufsize);
      if (n > size)
	n = size;
      memcpy (dst, f->ptr, n);
      f->ptr += n; total += n; size -= n;
      dst = (void *)((char *)dst + n);
    }
  if (size == 0)
    return total;

  /* Read directly to the target object if buffering seems to be
     inefficient due to the amount of data requested. */

  DEBUG_ASSERT (f->ptr == f->end);
  DEBUG_ASSERT (size > 0);
  if (size > f->bufsize / 2)
    {
      n = emi_read1 (f, dst, size);
      if (n == -1)
	return -1;
      total += n;
      return total;
    }

  /* Read to the stream buffer, then copy from the stream buffer to
     the target object. */

  DEBUG_ASSERT (size <= f->bufsize);
  n = emi_read1 (f, f->buf, f->bufsize);
  if (n == -1)
    return -1;
  f->ptr = f->buf; f->end = f->buf + n;
  if (n > size)
    n = size;
  memcpy (dst, f->ptr, n);
  f->ptr += n; total += n;
  DEBUG_ASSERT (f->ptr >= f->buf && f->ptr <= f->end);
  return total;
}

/* Fill the buffer until it contains SIZE bytes (or less when hitting
   EOF or the buffer size).  Return the number of bytes in the buffer
   (but never a value greater than SIZE).  Return -1 on error. */

int emi_fill (EMI_FILE *f, int size)
{
  int have, need, add;

  DEBUG_ASSERT (f != NULL);
  if (size < 0)
    {
      errno = EINVAL; return -1;
    }
  if (f->flags & EMI_FLAG_ERROR)
    {
      errno = f->error; return -1;
    }

  if (size > f->bufsize)
    size = (int) f->bufsize;

  for (;;)
    {
      have = (int) (f->end - f->ptr);
      DEBUG_ASSERT (have >= 0 && have <= f->bufsize);
      if (have >= size)
	return size < have ? size : have; /* Enough */
      need = size - have;
      DEBUG_ASSERT (have + need <= f->bufsize);
      add = emi_read1 (f, (char *)f->ptr + have, need);
      if (add == -1)
	return -1;		/* Error */
      if (add == 0)
	return have;		/* EOF */
      f->end += add;
      DEBUG_ASSERT (f->ptr + have + add == f->end);
    }
}

/* Artificially limit the input from F to LIMIT bytes.  If LIMIT is
   -1, the limit will be disabled. */

int emi_limit (EMI_FILE *f, long limit)
{
  DEBUG_ASSERT (f != NULL);
  if (limit < -1)
    {
      errno = EINVAL;
      return -1;
    }
  f->limit = limit;
  return 0;
}

/* Set the time out for F to TIMEOUT seconds.  If TIMEOUT is 0, time
   out will be disabled. */

int emi_timeout (EMI_FILE *f, int timeout)
{
  DEBUG_ASSERT (f != NULL);
  if (timeout < 0)
    {
      errno = EINVAL;
      return -1;
    }
  f->timeout = timeout;
  return 0;
}

/* Return the total number of bytes read.  If USER is 0, return the
   number of bytes read to the buffer.  If USER is non-zero, return
   the number of bytes returned to the caller. */

unsigned long emi_amount (EMI_FILE *f, int user)
{
  if (f == NULL)
    return 0;			/* This simplifies quit() of squid-gw */
  if (user)
    {
      DEBUG_ASSERT (f->ptr <= f->end);
      return f->amount - (f->end - f->ptr);
    }
  else
    return f->amount;
}

/* Copy up to SIZE bytes from F to the buffer pointed to by DST,
   without actually consuming those bytes.  That is, emi_peek() looks
   ahead and calling or not calling emi_peek() does not make a
   difference (except in the case of errors) for future read
   operations.  SIZE is limited by the buffer size of F.  Return the
   actual number of bytes read.  Return -1 on error. */

int emi_peek (EMI_FILE *f, void *dst, int size)
{
  int buffered, nread, off;

  DEBUG_ASSERT (f != NULL);
  DEBUG_ASSERT (dst != NULL);
  if (size < 0 || size > f->bufsize)
    {
      errno = EINVAL; return -1;
    }
  if (f->flags & EMI_FLAG_ERROR)
    {
      errno = f->error; return -1;
    }

  /* We have to fill the buffer if there are less than SIZE bytes in
     the buffer. */

  buffered = f->end - f->ptr;
  DEBUG_ASSERT (buffered >= 0 && buffered <= f->bufsize);
  if (size > buffered)
    {
      /* There might not be enough space left at the end of the
         buffer.  In that case, we have to move down the active buffer
         contents. */

      DEBUG_ASSERT (f->ptr >= f->buf && f->ptr <= f->end);
      if (f->bufsize - (f->ptr - f->buf) < size)
	{
	  memmove (f->buf, f->ptr, buffered);
	  f->ptr = f->buf; f->end = f->buf + buffered;
	  DEBUG_ASSERT (f->bufsize - (f->ptr - f->buf) >= size);
	}
      off = f->end - f->buf;
      nread = emi_read1 (f, f->buf + off, f->bufsize - off);
      if (nread == -1)
	return -1;
      f->end += nread; buffered += nread;
      DEBUG_ASSERT (buffered >= 0 && buffered <= f->bufsize);
      DEBUG_ASSERT (f->end - f->ptr == buffered);
    }
  if (buffered < size)
    size = buffered;
  memcpy (dst, f->ptr, size);
  return size;
}

/* Helper function for the emi_getc() macro.  This function has the
   same interface as emi_getc(): On success (subject to buffering),
   return the next character of F converted to unsigned char.  Return
   EMI_EOF on error or when reaching EOF. */

int emi_getc1 (EMI_FILE *f)
{
  int n;

  DEBUG_ASSERT (f != NULL);
  DEBUG_ASSERT (f->ptr == f->end);
  if (f->flags & EMI_FLAG_ERROR)
    {
      errno = f->error; return EMI_EOF;
    }
  n = emi_read1 (f, f->buf, f->bufsize);
  if (n == -1 || n == 0)
    return EMI_EOF;
  f->ptr = f->buf; f->end = f->buf + n;
  return (unsigned char)*f->ptr++;
}

#ifndef NDEBUG

/* This function is defined only if DEBUG_ASSERT() is not disabled.
   If NDEBUG is defined, there's no point in this function as
   DEBUG_ASSERT() would be a no-op.  This happens when compiling emi.c
   with NDEBUG undefined and the application with NDEBUG defined.  We
   want to get a linker error in that case. */

void emi_getc_assert (EMI_FILE *f)
{
  DEBUG_ASSERT (f != NULL);
  DEBUG_ASSERT (f->end >= f->buf && f->end <= f->buf + f->bufsize);
  DEBUG_ASSERT (f->ptr >= f->buf && f->ptr <= f->end);
}

#endif
