/* $Id: core.C,v 1.26 2001/08/19 00:27:15 dm Exp $ */

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

#include "async.h"
#include "fdlim.h"
#include "ihash.h"
#include "itree.h"

#include <typeinfo>

#define FD_SETSIZE_ROUND (sizeof (long))/* # Bytes to which to round fd_sets */
int fd_set_bytes;		// Size in bytes of a [wide] fd_set
int maxfd;

timespec tsnow;
const time_t &timenow = tsnow.tv_sec;

static timeval selwait;

struct child {
  pid_t pid;
  cbi cb;
  ihash_entry<child> link;
  child (pid_t p, cbi c) : pid (p), cb (c) {}
};
static ihash<pid_t, child, &child::pid, &child::link> chldcbs;

struct timecb_t {
  timespec ts;
  cbv cb;
  itree_entry<timecb_t> link;
  timecb_t (const timespec &t, const cbv &c) : ts (t), cb (c) {}
};
static itree<timespec, timecb_t, &timecb_t::ts, &timecb_t::link> timecbs;
static bool timecbs_altered;

const int fdsn = 2;
static cbv::ptr *fdcbs[fdsn];
static fd_set *fdsp[fdsn];
static fd_set *fdspt[fdsn];

static int sigpipes[2] = { -1, -1 };
#ifdef NSIG
const int nsig = NSIG;
#else /* !NSIG */
const int nsig = 32;
#endif /* !NSIG */
/* Note: sigdocheck and sigcaught intentionally ints rather than
 * bools.  The hope is that an int can safely be written without
 * affecting surrounding memory.  (This is certainly not the case on
 * some architectures if bool is a char.  Consider getting signal 2
 * right after signal 3 on an alpha, for instance.  You might end up
 * clearing sigcaught[2] when you finish setting sigcaught[3].) */
static volatile int sigdocheck;
static volatile int sigcaught[nsig];
static bssptr<cbv::type> sighandler[nsig];

static void sigcb_check ();

void
chldcb (pid_t pid, cbi::ptr cb)
{
  if (child *c = chldcbs[pid])
    chldcbs.remove (c);
  if (cb)
    chldcbs.insert (New child (pid, cb));
}

void
chldcb_check ()
{
  for (;;) {
    int status;
    pid_t pid = waitpid (-1, &status, WNOHANG);
    if (pid == 0 || pid == -1)
      return;
    if (child *c = chldcbs[pid]) {
      chldcbs.remove (c);
      (*c->cb) (status);
      delete c;
    }
  }
}

timecb_t *
timecb (const timespec &ts, cbv cb)
{
  timecb_t *to = New timecb_t (ts, cb);
  timecbs.insert (to);
  // timecbs_altered = true;
  return to;
}

timecb_t *
delaycb (time_t sec, u_int32_t nsec, cbv cb)
{
  timespec ts;
  clock_gettime (CLOCK_REALTIME, &ts);
  ts.tv_sec += sec;
  ts.tv_nsec += nsec;
  if (ts.tv_nsec >= 1000000000) {
    ts.tv_nsec -= 1000000000;
    ts.tv_sec++;
  }
  return timecb (ts, cb);
}

void
timecb_remove (timecb_t *to)
{
  if (!to)
    return;

  for (timecb_t *tp = timecbs[to->ts]; tp != to; tp = timecbs.next (tp))
    if (!tp || tp->ts != to->ts)
      panic ("timecb_remove: invalid timecb_t\n");
  timecbs_altered = true;
  timecbs.remove (to);
  delete to;
}

void
timecb_check ()
{
  clock_gettime (CLOCK_REALTIME, &tsnow);
  timecb_t *tp, *ntp;

  for (tp = timecbs.first (); tp && tp->ts <= tsnow;
       tp = timecbs_altered ? timecbs.first () : ntp) {
    ntp = timecbs.next (tp);
    timecbs.remove (tp);
    timecbs_altered = false;
    (*tp->cb) ();
    delete tp;
  }
  selwait.tv_usec = 0;
  if (!(tp = timecbs.first ()))
    selwait.tv_sec = 86400;
  else {
    clock_gettime (CLOCK_REALTIME, &tsnow);
    if (tp->ts < tsnow)
      selwait.tv_sec = 0;
    else if (tp->ts.tv_nsec >= tsnow.tv_nsec) {
      selwait.tv_sec = tp->ts.tv_sec - tsnow.tv_sec;
      selwait.tv_usec = (tp->ts.tv_nsec - tsnow.tv_nsec) / 1000;
    }
    else {
      selwait.tv_sec = tp->ts.tv_sec - tsnow.tv_sec - 1;
      selwait.tv_usec = (1000000000 + tp->ts.tv_nsec - tsnow.tv_nsec) / 1000;
    }
  }
  if (sigdocheck)
    selwait.tv_sec = selwait.tv_usec = 0;
}

void
fdcb (int fd, selop op, cbv::ptr cb)
{
  assert (fd >= 0);
  assert (fd < maxfd);
  fdcbs[op][fd] = cb;
  if (cb)
    FD_SET (fd, fdsp[op]);
  else
    FD_CLR (fd, fdsp[op]);
}

static void
fdcb_check (void)
{
  for (int i = 0; i < fdsn; i++)
    memcpy (fdspt[i], fdsp[i], fd_set_bytes);
  int n = select (maxfd, fdspt[0], fdspt[1], NULL, &selwait);
  if (n < 0 && errno != EINTR)
    panic ("select: %m\n");
  clock_gettime (CLOCK_REALTIME, &tsnow);
  if (sigdocheck)
    sigcb_check ();
  for (int fd = 0; fd < maxfd && n > 0; fd++)
    for (int i = 0; i < fdsn; i++)
      if (FD_ISSET (fd, fdspt[i])) {
	n--;
	if (FD_ISSET (fd, fdsp[i]))
	  (*fdcbs[i][fd]) ();
      }
}

static void
sigcatch (int sig)
{
  sigdocheck = 1;
  sigcaught[sig] = 1;
  selwait.tv_sec = selwait.tv_usec = 0;
  /* On some operating systems, select is not a system call but is
   * implemented inside libc.  This may cause a race condition in
   * which select ends up being called with the original (non-zero)
   * value of selwait.  We avoid the problem by writing to a pipe that
   * will wake up the select. */
  write (sigpipes[1], "", 1);
}

cbv::ptr
sigcb (int sig, cbv::ptr cb, int flags)
{
  struct sigaction sa;
  assert (sig > 0 && sig < nsig);
  bzero (&sa, sizeof (sa));
  sa.sa_handler = cb ? sigcatch : SIG_DFL;
  sa.sa_flags = flags;
  if (sigaction (sig, &sa, NULL) < 0) // Must be bad signal, serious bug
    panic ("sigcb: sigaction: %m\n");
  cbv::ptr ocb = sighandler[sig];
  sighandler[sig] = cb;
  return ocb;
}

static void
sigcb_check ()
{
  if (sigdocheck) {
    char buf[64];
    while (read (sigpipes[0], buf, sizeof (buf)) > 0)
      ;
    sigdocheck = 0;
    for (int i = 1; i < nsig; i++)
      if (sigcaught[i]) {
	sigcaught[i] = 0;
	if (cbv::ptr cb = sighandler[i])
	  (*cb) ();
      }
  }
}

static void
ainit ()
{
  if (sigpipes[0] == -1) {
    if (pipe (sigpipes) < 0)
      fatal ("could not create sigpipes: %m\n");

    _make_async (sigpipes[0]);
    _make_async (sigpipes[1]);
    close_on_exec (sigpipes[0]);
    close_on_exec (sigpipes[1]);
    fdcb (sigpipes[0], selread, cbv_null);

    /* Set SA_RESTART for SIGCHLD, primarily for the benefit of
     * stdio-using code like lex/flex scanners.  These tend to flip out
     * if read ever returns EINTR. */
    sigcb (SIGCHLD, wrap (chldcb_check), (SA_NOCLDSTOP
#ifdef SA_RESTART
					  | SA_RESTART
#endif /* SA_RESTART */
					));
    sigcatch (SIGCHLD);
  }
}

static inline void
_acheck ()
{
  fdcb_check ();
  sigcb_check ();
  timecb_check ();
}

void
acheck ()
{
  timecb_check ();
  ainit ();
  _acheck ();
}

void
amain ()
{
  static bool amain_called;
  if (amain_called)
    panic ("amain called recursively\n");
  amain_called = true;

  ainit ();
  err_init ();

  timecb_check ();
  for (;;)
    _acheck ();
}

int async_init::count;

void
async_init::start ()
{
  static bool initialized;
  if (initialized)
    panic ("async_init called twice\n");
  initialized = true;

  /* Ignore SIGPIPE, since we may get a lot of these */
  struct sigaction sa;
  bzero (&sa, sizeof (sa));
  sa.sa_handler = SIG_IGN;
  sigaction (SIGPIPE, &sa, NULL);

#ifndef HAVE_WIDE_SELECT
  fdlim_set (FD_SETSIZE, suidsafe ());
  maxfd = fdlim_get (0);
  fd_set_bytes = sizeof (fd_set);
#else /* HAVE_WIDE_SELECT */
  if (fdlim_set (0x10000, suidsafe ()))
    fdlim_set (fdlim_get (suidsafe ()), suidsafe ());
  maxfd = fdlim_get (0);
  fd_set_bytes = (maxfd+7)/8;
  if (fd_set_bytes % FD_SETSIZE_ROUND)
    fd_set_bytes += FD_SETSIZE_ROUND - (fd_set_bytes % FD_SETSIZE_ROUND);
#endif /* HAVE_WIDE_SELECT */

  for (int i = 0; i < fdsn; i++) {
    fdcbs[i] = New cbv::ptr[maxfd];
    fdsp[i] = (fd_set *) xmalloc (fd_set_bytes);
    bzero (fdsp[i], fd_set_bytes);
    fdspt[i] = (fd_set *) xmalloc (fd_set_bytes);
    bzero (fdspt[i], fd_set_bytes);
  }
}

void
async_init::stop ()
{
  err_flush ();
}
