/*
 * This code is copyright 2001 by Craig Hughes
 * Portions copyright 2002 by Brad Jorsch
 * It is licensed under the same license as Perl itself.  The text of this
 * license is included in the SpamAssassin distribution in the file named
 * "License".
 */

#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <stdio.h>
#include "utils.h"

/* Dec 13 2001 jm: added safe full-read and full-write functions.  These
 * can cope with networks etc., where a write or read may not read all
 * the data that's there, in one call.
 */
/* Aug 14, 2002 bj: EINTR and EAGAIN aren't fatal, are they? */
/* Aug 14, 2002 bj: moved these to utils.c */
/* Jan 13, 2003 ym: added timeout functionality */
/* Apr 24, 2003 sjf: made full_read and full_write void* params */

/* -------------------------------------------------------------------------- */

typedef void    sigfunc(int);   /* for signal handlers */

sigfunc* sig_catch(int sig, void (*f)(int))
{
  struct sigaction act, oact;
  act.sa_handler = f;
  act.sa_flags = 0;
  sigemptyset(&act.sa_mask);
  sigaction(sig, &act, &oact);
  return oact.sa_handler;
}

static void catch_alrm(int x) {
  UNUSED_VARIABLE(x);
}

ssize_t
fd_timeout_read (int fd, void *buf, size_t nbytes)
{
  ssize_t nred;
  sigfunc* sig;

  sig = sig_catch(SIGALRM, catch_alrm);
  if (libspamc_timeout > 0) {
    alarm(libspamc_timeout);
  }

  do {
    nred = read (fd, buf, nbytes);
  } while(nred < 0 && errno == EAGAIN);

  if(nred < 0 && errno == EINTR)
    errno = ETIMEDOUT;

  if (libspamc_timeout > 0) {
    alarm(0);
  }

  /* restore old signal handler */
  sig_catch(SIGALRM, sig);

  return nred;
}

int
ssl_timeout_read (SSL *ssl, void *buf, int nbytes)
{
  int nred;
  sigfunc* sig;

#ifndef SPAMC_SSL
  UNUSED_VARIABLE(ssl);
  UNUSED_VARIABLE(buf);
  UNUSED_VARIABLE(nbytes);
#endif

  sig = sig_catch(SIGALRM, catch_alrm);
  if (libspamc_timeout > 0) {
    alarm(libspamc_timeout);
  }

  do {
#ifdef SPAMC_SSL
    nred = SSL_read (ssl, buf, nbytes);
#else
    nred = 0;			/* never used */
#endif
  } while(nred < 0 && errno == EAGAIN);

  if(nred < 0 && errno == EINTR)
    errno = ETIMEDOUT;

  if (libspamc_timeout > 0) {
    alarm(0);
  }

  /* restore old signal handler */
  sig_catch(SIGALRM, sig);

  return nred;
}

/* -------------------------------------------------------------------------- */

int
full_read (int fd, void *vbuf, int min, int len)
{
  unsigned char *buf = (unsigned char *)vbuf;
  int total;
  int thistime;

  for (total = 0; total < min; ) {
    thistime = fd_timeout_read (fd, buf+total, len-total);

    if (thistime < 0) {
      return -1;
    } else if (thistime == 0) {
      /* EOF, but we didn't read the minimum.  return what we've read
       * so far and next read (if there is one) will return 0. */
      return total;
    }

    total += thistime;
  }
  return total;
}

int
full_read_ssl (SSL *ssl, unsigned char *buf, int min, int len)
{
  int total;
  int thistime;

  for (total = 0; total < min; ) {
    thistime = ssl_timeout_read (ssl, buf+total, len-total);

    if (thistime < 0) {
      return -1;
    } else if (thistime == 0) {
      /* EOF, but we didn't read the minimum.  return what we've read
       * so far and next read (if there is one) will return 0. */
      return total;
    }

    total += thistime;
  }
  return total;
}

int
full_write (int fd, const void *vbuf, int len)
{
  const unsigned char *buf = (const unsigned char *)vbuf;
  int total;
  int thistime;

  for (total = 0; total < len; ) {
    thistime = write (fd, buf+total, len-total);

    if (thistime < 0) {
      if(EINTR == errno || EAGAIN == errno) continue;
      return thistime;        /* always an error for writes */
    }
    total += thistime;
  }
  return total;
}
