
/*
 * event.c
 * 
 * Copyright (C) 1996 by Whistle Communications Corp.
 * All rights reserved.
 */

  #include "ppp.h"
  #include "event.h"

/*
 * DEFINITIONS
 */

  #define MAX_EVENT_NAME	32
  #define EVENT_MAGIC		0x3de80f67

  #define MAXOF(x,y)		((x)>(y)?(x):(y))

  struct event
  {
    u_int		magic;
    EventRef		ref;
    EventRef		*refp;
    int			val;
    int			prio;
    u_short		type;
    u_short		occurring:1;	/* Event is occurring */
    struct timeval	to;
    struct event	*next;
    void		*cookie;
    void		(*action)(int type, void *cookie);
  };
  typedef struct event	*Event;

/*
 * INTERNAL VARIABLES
 */

  static u_int	gNextRefNum;	/* non-zero if initialized */
  static Event	gEvents;
  static int	gServiceOn;
  static int	gEventOk;
  static u_char	gSigsCaught[NSIG];

  static int	gSanityCheck = 1;
  static void	(*gWarnx)(const char *fmt, ...) = warnx;

  static struct timeval *top;
  static struct timeval tv_zero = { 0, 0 };

/*
 * INTERNAL FUNCTIONS
 */

  static void		EventInit(void);
  static void		EventCatchSignal(int sig);
  static char		*EventDesc(Event event);
  static struct timeval	TimevalDiff(struct timeval *lo, struct timeval *hi);

  static void		MyWarn(const char *fmt, ...) __printflike(1, 2);
  static void		MyWarnx(const char *fmt, ...) __printflike(1, 2);

  static void		ShowList(const char *fmt, ...) __printflike(1, 2);

/*
 * MyWarn()
 */

static void
MyWarn(const char *fmt, ...)
{
  va_list	args;
  char		buf[100];

  va_start(args, fmt);
  vsnprintf(buf, sizeof(buf), fmt, args);
  snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
    ": %s", sys_errlist[errno]);
  (*gWarnx)("%s", buf);
  va_end(args);
}

/*
 * MyWarnx()
 */

static void
MyWarnx(const char *fmt, ...)
{
  va_list	args;
  char		buf[100];

  va_start(args, fmt);
  vsnprintf(buf, sizeof(buf), fmt, args);
  (*gWarnx)("%s", buf);
  va_end(args);
}

/*
 * EventSetLog()
 */

void
EventSetLog(int sanity, void (*warnx)(const char *fmt, ...))
{
  gSanityCheck = sanity;
  if (warnx) gWarnx = warnx;
}

/*
 * EventInit()
 */

static void
EventInit(void)
{
  if (gNextRefNum == 0)
    gNextRefNum = (((getpid() << 16) ^ ((u_int) EventStart)) & 0x0fffffff);
}

/*
 * EventStart()
 *
 * Start servicing events. Any signal events that are registered
 * should have their signals blocked before calling this.
 *
 * This function does not return unless one of these two things
 * becomes true:
 *
 *	Return value	Condition
 *	------------	---------
 *	     -1		EventStop() was called
 *	      0		No events are registered
 */

int
EventStart(void)
{
  struct timeval	lastTime;

/* Initialize */

  if (gNextRefNum == 0)
    EventInit();

/* Main loop */

  gettimeofday(&lastTime, NULL);
  for (gServiceOn = 1; gServiceOn; )
  {
    Event		event;
    int			rtn, maxfd, select_errno;
    struct timeval	to;
    struct timeval	now, diff;
    fd_set		rfds, wfds, efds;
    sigset_t		sigs;

  /* If no events are registered, return */

    if (!gEvents)
      return(0);

  /* Initialize info */

    maxfd = 0;
    top = NULL;
    FD_ZERO(&rfds);
    FD_ZERO(&wfds);
    FD_ZERO(&efds);
    sigemptyset(&sigs);

  /* Accumulate info from each pending event */

    for (event = gEvents; event; event = event->next)
    {
      switch (event->type)
      {
	case EVENT_READ:
	  FD_SET(event->val, &rfds);
	  maxfd = MAXOF(maxfd, event->val + 1);
	  break;
	case EVENT_WRITE:
	  FD_SET(event->val, &wfds);
	  maxfd = MAXOF(maxfd, event->val + 1);
	  break;
	case EVENT_EXCEPTION:
	  FD_SET(event->val, &efds);
	  maxfd = MAXOF(maxfd, event->val + 1);
	  break;
	case EVENT_SIGNAL:
	  sigaddset(&sigs, event->val);
	  break;
	case EVENT_TIMEOUT:
	  if (top == NULL || timercmp(&event->to, &to, < ))
	    memcpy(&to, &event->to, sizeof(to));
	  top = &to;
	  break;
	default:
	  MyWarnx("%s: bogus type %d", __FUNCTION__, event->type);
	  break;
      }
    }

  /* Sanity check, if desired */

    if (gSanityCheck)
    {
      Event	*ep, *nextp;

    /* Check all events */

      for (ep = &gEvents; (event = *ep); ep = nextp)
      {
	if (event->occurring)
	{
	  MyWarnx("%s: occurring", __FUNCTION__);
	  goto problem;
	}
	if (event->magic != EVENT_MAGIC)
	{
	  MyWarnx("%s: bad magic2", __FUNCTION__);
	  goto problem;
	}
	if (event->refp && (*event->refp != event->ref))
	{
	  MyWarnx("%s: bad ref at %p: %u != %u",
	    __FUNCTION__, event->refp, *event->refp, event->ref);
	  goto problem;
	}
	nextp = &event->next;
	continue;
problem:
	*ep = event->next;
	memset(event, 0, sizeof(*event));
	free(event);
	nextp = ep;
      }
    }

  /* Wait for some event(s) to happen */

    gEventOk = 1;
    memset(gSigsCaught, 0, sizeof(gSigsCaught));
    sigprocmask(SIG_UNBLOCK, &sigs, NULL);

    rtn = select(maxfd, &rfds, &wfds, &efds, top);
    select_errno = errno;

    sigprocmask(SIG_BLOCK, &sigs, NULL);
    gEventOk = 0;

  /* Calculate time difference since last service */

    gettimeofday(&now, NULL);
    diff = TimevalDiff(&lastTime, &now);
    lastTime = now;

  /* Check return value */

    if (rtn == -1 && select_errno != EINTR)	/* should never happen! */
    {
      MyWarn("select");
      MyWarnx("select args: %d [%ld, %ld]", maxfd,
	top ? top->tv_sec : 0, top ? top->tv_usec : 0);
      continue;		/* XXX? */
    }

  /* Mark occurring events */

    for (event = gEvents; event; event = event->next)
    {
      event->occurring = 0;
      switch (event->type)
      {
	case EVENT_READ:
	  if (rtn >= 0 && FD_ISSET(event->val, &rfds))
	    event->occurring = 1;
	  break;
	case EVENT_WRITE:
	  if (rtn >= 0 && FD_ISSET(event->val, &wfds))
	    event->occurring = 1;
	  break;
	case EVENT_EXCEPTION:
	  if (rtn >= 0 && FD_ISSET(event->val, &efds))
	    event->occurring = 1;
	  break;
	case EVENT_TIMEOUT:
	  event->to = TimevalDiff(&diff, &event->to);
	  if (!timerisset(&event->to))
	    event->occurring = 1;
	  break;
	case EVENT_SIGNAL:
	  if (gSigsCaught[event->val])
	    event->occurring = 1;
	  break;
	default:
	  MyWarnx("%s: bogus type %d", __FUNCTION__, event->type);
	  break;
      }
    }

#ifdef DEBUG_EVENT
    {
      Event	e2;

      MyWarnx("ACTIVE EVENTS:");
      for (e2 = gEvents; e2; e2 = e2->next)
	if (e2->occurring)
	  MyWarnx(" -> 0x%08x %s", (u_int) e2, EventDesc(e2));
      MyWarnx("DOING ACTIONS:");
    }
#endif

  /* Do event actions */

    while (1)
    {
      Event	*nextp, *ep;

    /* Get next occurring event */

      for (ep = &gEvents;
	(event = *ep) && !event->occurring;
	ep = &event->next);
      if (!event)
	break;

#ifdef DEBUG_EVENT
      MyWarnx("Doing action for %s", EventDesc(event));
#endif

    /* Remove this event and all others with the same ref # from list */

      for (ep = &gEvents; *ep; ep = nextp)
      {
	Event	const event2 = *ep;

	if (event2->ref != event->ref)
	  nextp = &event2->next;
	else
	{
	  nextp = ep;
	  *ep = event2->next;
	  if (event2 != event)
	  {
	    memset(event2, 0, sizeof(*event2));
	    free(event2);
	  }
	}
      }

    /* Deactivate caller's reference and do action */

      if (event->refp)
	*event->refp = 0;
      if (event->action)
	(*event->action)(event->type, event->cookie);

    /* Nuke entry */

      memset(event, 0, sizeof(*event));
      free(event);
    }
  }

/* EventStop() was called */

  return(-1);
}

/*
 * EventStop()
 *
 * Stop servicing events
 */

void
EventStop(void)
{
  gServiceOn = 0;
}

/*
 * EventDump()
 */

void
EventDump(const char *msg)
{
  ShowList("%s", msg);
}

/*
 * EventRegister()
 */

int
EventRegister(EventRef *refp, int type, int val, int prio,
	void (*action)(int type, void *cookie), void *cookie)
{
  Event	event, e2, *ep;
  int	bad;

/* Initialize */

  if (gNextRefNum == 0)
    EventInit();

/* Create new event descriptor */

  if ((event = malloc(sizeof(*event))) == NULL)
  {
    MyWarn("%s: malloc", __FUNCTION__);
    return(-1);
  }

/* Initialize */

  memset(event, 0, sizeof(*event));
  event->magic = EVENT_MAGIC;
  event->type = type;
  event->val = val;
  event->prio = prio;
  event->action = action;
  event->cookie = cookie;
  event->refp = refp;

/* Check type and value */

  switch (event->type)
  {
    case EVENT_READ:
    case EVENT_WRITE:
    case EVENT_EXCEPTION:
      bad = (val < 0) || val >= FD_SETSIZE;
      break;
    case EVENT_TIMEOUT:
      bad = (val < 0);
      break;
    case EVENT_SIGNAL:
      bad = (val <= 0 || val >= NSIG);
      break;
    default:
      bad = 1;
      break;
  }
  if (bad)
  {
    MyWarnx("%s: bad event: %s", __FUNCTION__, EventDesc(event));
bogus:
    memset(event, 0, sizeof(event));
    free(event);
    return(-1);
  }

/* See if it conflicts with some other event */

  if (event->type != EVENT_TIMEOUT)
    for (e2 = gEvents; e2; e2 = e2->next)
      if (event->type == e2->type && event->val == e2->val)
      {
	MyWarnx("%s: event %s conflicts", __FUNCTION__, EventDesc(event));
	goto bogus;
      }

/* Assign reference; duplicates of pending events are allowed */

  if (refp && *refp != 0)
  {
    event->ref = *refp;
    for (e2 = gEvents; e2; e2 = e2->next)
      if (e2->ref == event->ref && e2->refp == refp)
	break;
    if (!e2)
    {
      MyWarnx("%s: invalid ref %s", __FUNCTION__, EventDesc(event));
      goto bogus;
    }
  }
  else
  {
    event->ref = gNextRefNum++;
    if (refp)
      *refp = event->ref;
  }

/* Block/catch newly registered signals */

  if (event->type == EVENT_SIGNAL)
  {
    sigset_t	sigs;

    signal(event->val, EventCatchSignal);
    if (sigprocmask(SIG_BLOCK, NULL, &sigs) < 0)
      MyWarn("sigprocmask1");
    sigaddset(&sigs, event->val);
    if (sigprocmask(SIG_BLOCK, &sigs, NULL) < 0)
      MyWarn("sigprocmask2");
  }

/* Convert miliseconds to timeval */

  if (event->type == EVENT_TIMEOUT)
  {
    event->to.tv_sec = val / 1000;
    event->to.tv_usec = (val % 1000) * 1000;
  }

/* Add to the list, sorted by priority */

  for (ep = &gEvents; *ep && prio <= (*ep)->prio; ep = &(*ep)->next);
  event->next = *ep;
  *ep = event;

/* Done */

#ifdef DEBUG_EVENT
  ShowList("After %s(%u)", __FUNCTION__, event->ref);
#endif
  return(0);
}

/*
 * EventUnRegister()
 */

int
EventUnRegister(EventRef *refp)
{
  Event		*ep, *nextp;
  const int	ref = refp ? *refp : 0;

/* Check reference */

  if (!ref)
    return(0);

/* Find matching events in chain and detach */

  for (ep = &gEvents; *ep; ep = nextp)
  {
    Event	const event = *ep;

    if (event->ref != ref)
      nextp = &event->next;
    else
    {
      nextp = ep;
      *ep = event->next;
      memset(event, 0, sizeof(*event));
      free(event);
      *refp = 0;
    }
  }
  if (*refp)
  {
    MyWarnx("%s: event not found", __FUNCTION__);
    return(-1);
  }

/* Done */

#ifdef DEBUG_EVENT
  ShowList("After %s", __FUNCTION__);
#endif
  return(0);
}

/*
 * EventIsRegistered()
 */

int
EventIsRegistered(EventRef ref)
{
  Event	event;

  if (ref == 0)
    return(0);
  for (event = gEvents; event; event = event->next)
    if (event->ref == ref)
      return(1);
  return(0);
}

/*
 * EventTimerRemain()
 *
 * Returns the number of milliseconds remaining on a timer.
 * Returns -1 if the timer is not registered or is not a timer event.
 */

int
EventTimerRemain(EventRef ref)
{
  Event	event;

  for (event = gEvents; event; event = event->next)
    if (event->ref == ref && event->type == EVENT_TIMEOUT)
      return(event->to.tv_sec * 1000 + event->to.tv_usec / 1000);
  return(-1);
}

/*
 * EventCatchSignal()
 */

static void
EventCatchSignal(int sig)
{

/* Spurious? */

  if (!gEventOk)
  {
    MyWarnx("caught unexpected signal %s", sys_signame[sig]);
    return;
  }

/* Mark signal as having occurred */

  gSigsCaught[sig] = 1;

/* Fall out of the select */

  top = &tv_zero;
}

/*
 * EventDesc()
 *
 * Return a brief textual description of what an event is
 */

#define DESC_NBUFS	5

static char *
EventDesc(Event event)
{
  static int	bn;
  static char	buf[DESC_NBUFS][50];

  bn = (bn + 1) % DESC_NBUFS;
  switch (event->type)
  {
    case EVENT_READ:
      snprintf(buf[bn], sizeof(buf[bn]), "Read(%d)", event->val);
      break;
    case EVENT_WRITE:
      snprintf(buf[bn], sizeof(buf[bn]), "Write(%d)", event->val);
      break;
    case EVENT_EXCEPTION:
      snprintf(buf[bn], sizeof(buf[bn]), "Except(%d)", event->val);
      break;
    case EVENT_SIGNAL:
      snprintf(buf[bn], sizeof(buf[bn]), "Signal(%s)", sys_signame[event->val]);
      break;
    case EVENT_TIMEOUT:
      snprintf(buf[bn], sizeof(buf[bn]), "Timeout(%d)", event->val);
      break;
    default:
      snprintf(buf[bn], sizeof(buf[bn]), "??[%d](%d)", event->type, event->val);
      return(NULL);
  }
  snprintf(buf[bn] + strlen(buf[bn]), sizeof(buf[bn]) - strlen(buf[bn]),
    " REF %u @ %p", event->ref, event->refp);
  if (event->magic != EVENT_MAGIC)
    snprintf(buf[bn] + strlen(buf[bn]),
      sizeof(buf[bn]) - strlen(buf[bn]), " BAD MAGIC");
  return(buf[bn]);
}

/*
 * TimevalDiff()
 */

static struct timeval
TimevalDiff(struct timeval *lo, struct timeval *hi)
{
  struct timeval	diff;

/* Convert "negative" answer to zero */

  if (timercmp(lo, hi, > ))
  {
    memset(&diff, 0, sizeof(diff));
    return(diff);
  }

/* Subtract */

  diff.tv_sec = hi->tv_sec - lo->tv_sec;
  if (hi->tv_usec >= lo->tv_usec)
    diff.tv_usec = hi->tv_usec - lo->tv_usec;
  else
  {
    diff.tv_sec--;
    diff.tv_usec = hi->tv_usec + 1000000 - lo->tv_usec;
  }
  return(diff);
}

/*
 * ShowList()
 */

static void
ShowList(const char *fmt, ...)
{
  Event		e2;
  char		buf[100];
  va_list	args;

  va_start(args, fmt);
  vsnprintf(buf, sizeof(buf), fmt, args);
  va_end(args);
  MyWarnx("Event list: %s", buf);
  for (e2 = gEvents; e2; e2 = e2->next)
    MyWarnx("  %p: %s -> %p", e2, EventDesc(e2), e2->action);
}

