/* emo.c */

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

   1997-07-19 Initial version
   1997-08-22 Fix typo
   2000-05-28 Add emo_set_read(), emo_read(), and emo_shutdown()
   2000-08-07 Bigger buffer for emo_vprintf() */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include "firewall.h"		/* DONT_HAVE_VSNPRINTF */
#include "libemfw.h"
#include "emio.h"

/* Open the file whose name is specified by the string pointed to by
   FNAME.  Create the file if it does not exist.  Truncate the file if
   it does exist.  BUFSIZE is the size of the buffer to be used for
   the file.  Return NULL on error. */

EMO_FILE *emo_open (const char *fname, size_t bufsize)
{
  int fd;

  if (fname == NULL || bufsize == 0)
    {
      errno = EINVAL; return NULL;
    }
  fd = open (fname, O_WRONLY | O_CREAT | O_TRUNC, 0644);
  if (fd == -1)
    return NULL;
  return emo_fdopen (fd, bufsize);
}

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

EMO_FILE *emo_fdopen (int fd, size_t bufsize)
{
  EMO_FILE *f;

  if (bufsize == 0)
    {
      errno = EINVAL; return NULL;
    }
  f = (EMO_FILE *)malloc (sizeof (EMO_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->timeout = 0;		/* No alarm */
  f->error = 0; f->amount = 0;
  f->ptr = f->buf;
  f->end = f->buf + bufsize;
  return f;
}

/* Flush the buffer of F, then close F.  Return 0 on success, -1 on
   error. */

int emo_close (EMO_FILE *f)
{
  int rc, fd;

  DEBUG_ASSERT (f != NULL);
  rc = emo_flush (f);
  fd = f->fd;
  DEBUG_ASSERT (f->buf != NULL);
  free (f->buf);
  free (f);
  if (rc == 0)
    return close (fd);
  else
    {
      int e = errno;
      close (fd);
      e = errno;
      return rc;
    }
}

/* Helper function for emo_write(), emo_flush(), and emo_putc1().
   Write SIZE bytes from the array pointed to by SRC to the file F.
   Return 0 on success, -1 on error.  No buffering.  This is where the
   time out is implemented. */

static int emo_write1 (EMO_FILE *f, const void *src, int size)
{
  int n;

  DEBUG_ASSERT (f != NULL);
  DEBUG_ASSERT (src != NULL);

  while (size != 0)
    {
      DEBUG_ASSERT (size > 0);

      /* Handle timeout and EMO_FLAG_READ. */

      if (f->timeout != 0 || (f->flags & EMO_FLAG_READ))
	for (;;)
	  {
	    fd_set rfds, wfds;
	    struct timeval tv;
	    char dummy[512];

	    DEBUG_ASSERT (f->timeout >= 0);
	    FD_ZERO (&rfds);
	    FD_ZERO (&wfds);
	    if (f->flags & EMO_FLAG_READ)
	      FD_SET (f->fd, &rfds); /* TODO: Check for overflow */
	    FD_SET (f->fd, &wfds); /* TODO: Check for overflow */
	    tv.tv_sec = f->timeout; tv.tv_usec = 0;
	    n = select (f->fd + 1, &rfds, &wfds, (fd_set *)0,
			f->timeout != 0 ? &tv : (struct timeval *)0);
	    if (n <= 0)
	      {
		/* Error or time out. */
		if (n == 0)
		  errno = ETIMEDOUT;
		f->flags |= EMO_FLAG_ERROR; f->error = errno;
		return -1;
	      }
	    if (FD_ISSET (f->fd, &rfds))
	      {
		/* Read and discard any pending data. */
		n = read (f->fd, dummy, sizeof (dummy));
		if (n <= 0)
		  break;
	      }
	    if (FD_ISSET (f->fd, &wfds))
	      break;
	  }

      /* Write. */

      n = write (f->fd, src, size);
      if (n <= 0)
	{
	  if (n == 0)
	    errno = ENOSPC;
	  f->flags |= EMO_FLAG_ERROR; f->error = errno;
	  return -1;
	}
      src = (const void *)((const char *)src + n);
      size -= n; f->amount += n;
    }
  return 0;
}

/* Write SIZE bytes from the buffer pointed to by SRC to F.  Return 0
   on success (subject to buffering), -1 on error. */

int emo_write (EMO_FILE *f, const void *src, int size)
{
  int n;

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

  /* Put the new data fits into the buffer if there's enough space.
     (The case of the new data fitting exactly is handled below.) */

  n = f->end - f->ptr;
  DEBUG_ASSERT (n >= 0 && n <= f->bufsize);
  if (size < n)
    {
      memcpy (f->ptr, src, size);
      f->ptr += size;
      DEBUG_ASSERT (f->ptr >= f->buf && f->ptr <= f->end);
      return 0;
    }

  /* We have to flush the buffer (well, for n==size this is
     optional), so fill it as much as possible. */

  memcpy (f->ptr, src, n);
  f->ptr += n; size -= n; src = (const void *)((const char *)src + n);
  if (emo_flush (f) != 0)
    return -1;

  /* Put the new data fits into the buffer if there's enough space.
     (If it fits exactly, there's no point in buffering.) */

  DEBUG_ASSERT (size >= 0);
  if (size < f->bufsize)
    {
      DEBUG_ASSERT (f->ptr >= f->buf && f->ptr <= f->end);
      memcpy (f->ptr, src, size);
      f->ptr += size;
      DEBUG_ASSERT (f->ptr >= f->buf && f->ptr <= f->end);
      return 0;
    }

  /* Write directly. */

  return emo_write1 (f, src, size);
}

/* Helper function for the emo_putc() macro.  This function has the
   same interface as emo_putc(): Write the character C to the file F.
   On success (subject to buffering), return C converted to unsigned
   char.  Return EMO_EOF on error. */

int emo_putc1 (EMO_FILE *f, int c)
{
  DEBUG_ASSERT (f != NULL);
  DEBUG_ASSERT (f->ptr == f->end);
  if (f->flags & EMO_FLAG_ERROR)
    {
      errno = f->error; return EMO_EOF;
    }
  if (emo_write1 (f, f->buf, f->bufsize) != 0)
    return EMO_EOF;
  f->ptr = f->buf;
  *f->ptr++ = (char)c;
  return (unsigned char)c;
}

/* Write (flush) the buffer of F to the underlying file descriptor.
   Return 0 on success, -1 on error. */

int emo_flush (EMO_FILE *f)
{
  int n;

  if (f->flags & EMO_FLAG_ERROR)
    {
      errno = f->error; return -1;
    }
  n = f->ptr - f->buf;
  DEBUG_ASSERT (n >= 0 && n <= f->bufsize);
  f->ptr = f->buf;
  return emo_write1 (f, f->buf, n);
}

/* Write the null-terminated string pointed to by S to the file F.
   Return 0 on success (subject to buffering), -1 on error. */

int emo_puts (EMO_FILE *f, const char *s)
{
  DEBUG_ASSERT (f != NULL);
  DEBUG_ASSERT (s != NULL);
  return emo_write (f, s, strlen (s));
}

/* vfprintf()-like output to the file F controlled by the format
   string pointed to by FMT.  Return 0 on success (subject to
   buffering), -1 on error.  IMPORTANT: emo_vprintf() uses a 8191-byte
   buffer.  Output might be truncated.  If DONT_HAVE_VSNPRINTF is
   defined, you have to be very careful to avoid buffer overflow. */

int emo_vprintf (EMO_FILE *f, const char *fmt, va_list arg_ptr)
{
  static char buffer[65536];
  int n;

  DEBUG_ASSERT (f != NULL);
  DEBUG_ASSERT (fmt != NULL);

#ifndef DONT_HAVE_VSNPRINTF
  n = vsnprintf (buffer, sizeof (buffer), fmt, arg_ptr);
  if (n < 0)
    return -1;
  if (n >= sizeof (buffer))
    {
      /* vsnprintf() returns the untruncated length. */

      n = sizeof (buffer) - 1;

      /* Check for broken implementation of vsnprintf(). (Consider
	 libdb of Linux!)  We assume that our caller didn't print a
	 null character using "%c".  Do not use exit() as it may use
	 memory we have overwritten (function pointers!). */

      if (buffer[n] != 0)
	_exit (127);
    }
#else
  n = vsprintf (buffer, fmt, arg_ptr);
  if (n < 0)
    return -1;

  /* Do not use exit() as it may use memory we have overwritten
     (function pointers!). */

  if (n >= sizeof (buffer))
    _exit (127);
#endif

  return emo_write (f, buffer, n);
}

/* fprintf()-like output to the file F controlled by the format
   string pointed to by FMT.  See emo_vprintf() for details. */

int emo_printf (EMO_FILE *f, const char *fmt, ...)
{
  va_list arg_ptr;
  int rc;

  va_start (arg_ptr, fmt);
  rc = emo_vprintf (f, fmt, arg_ptr);
  va_end (arg_ptr);
  return rc;
}

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

int emo_timeout (EMO_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 written.  Include buffered bytes
   if USER is non-zero. */

unsigned long emo_amount (EMO_FILE *f, int user)
{
  if (f == NULL)
    return 0;			/* This simplifies quit() of squid-gw */
  if (user)
    {
      DEBUG_ASSERT (f->ptr >= f->buf);
      return f->amount + (f->ptr - f->buf);
    }
  else
    return f->amount;
}

/* Read (and discard) data while writing to avoid dead lock (e.g.,
   while sending back an HTTP error message -- the client won't read
   our response until it has sent its request completely, but we won't
   read its request). */

int emo_set_read (EMO_FILE *f)
{
  int fl;

  f->flags |= EMO_FLAG_READ;
  fl = fcntl (f->fd, F_GETFL, 0);
  if (fl != -1)
    fl = fcntl (f->fd, F_SETFL, fl | O_NDELAY);
  if (fl == -1)
    return -1;
  return 0;
}

int emo_read (EMO_FILE *f, int timeout)
{
  ALWAYS_ASSERT (timeout > 0);
  for (;;)
    {
      fd_set rfds;
      struct timeval tv;
      int n;
      char dummy[512];

      FD_ZERO (&rfds);
      FD_SET (f->fd, &rfds);	/* TODO: Check for overflow */
      tv.tv_sec = timeout; tv.tv_usec = 0;
      n = select (f->fd + 1, &rfds, (fd_set *)0, (fd_set *)0, &tv);
      if (n == 0)
	break;			/* No data received for TIMEOUT seconds */
      if (n < 0)
	return -1;
      ALWAYS_ASSERT (FD_ISSET (f->fd, &rfds));
      n = read (f->fd, dummy, sizeof (dummy));
      if (n < 0)
	{
	  if (errno == ECONNRESET || errno == EPIPE)
	    break;
	  return -1;
	}
      if (n == 0)
	break;
    }
  return 0;
}

int emo_shutdown (EMO_FILE *f)
{
  if (shutdown (f->fd, 1) != 0 && errno != ENOTCONN)
    return -1;
  return 0;
}

#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 emo.c
   with NDEBUG undefined and the application with NDEBUG defined.  We
   want to get a linker error in that case. */

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

#endif
