OpenSolaris_b135/lib/libinetutil/common/eh.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stropts.h>	/* INFTIM */

#include <libinetutil.h>
#include "libinetutil_impl.h"

static int	grow_fds(iu_eh_t *, int);

/*
 * signal_to_eh[] is pretty much useless, since the event handler is
 * really a singleton (we pass iu_eh_t *'s around to maintain an
 * abstraction, not to allow multiple event handlers to exist).  we
 * need some way to get back our event handler in post_signal(),
 * and since the signal model is too lame to provide opaque pointers,
 * we have to resort to global variables.
 */

static iu_eh_t *signal_to_eh[NSIG];

/*
 * iu_eh_create(): creates, initializes, and returns an event handler for use
 *
 *   input: void
 *  output: iu_eh_t *: the new event handler
 */

iu_eh_t *
iu_eh_create(void)
{
	iu_eh_t	*eh = malloc(sizeof (iu_eh_t));
	int	sig;

	if (eh == NULL)
		return (NULL);

	eh->iueh_pollfds	= NULL;
	eh->iueh_events		= NULL;
	eh->iueh_shutdown	= NULL;
	eh->iueh_num_fds	= 0;
	eh->iueh_stop		= B_FALSE;
	eh->iueh_reason		= 0;
	eh->iueh_shutdown_arg	= NULL;

	(void) sigemptyset(&eh->iueh_sig_regset);
	for (sig = 0; sig < NSIG; sig++) {
		eh->iueh_sig_info[sig].iues_pending = B_FALSE;
		eh->iueh_sig_info[sig].iues_handler = NULL;
		eh->iueh_sig_info[sig].iues_data = NULL;
	}

	return (eh);
}

/*
 * iu_eh_destroy(): destroys an existing event handler
 *
 *   input: iu_eh_t *: the event handler to destroy
 *  output: void
 *   notes: it is assumed all events related to this eh have been unregistered
 *          prior to calling iu_eh_destroy()
 */

void
iu_eh_destroy(iu_eh_t *eh)
{
	int	sig;

	for (sig = 0; sig < NSIG; sig++)
		if (signal_to_eh[sig] == eh)
			(void) iu_eh_unregister_signal(eh, sig, NULL);

	free(eh->iueh_pollfds);
	free(eh->iueh_events);
	free(eh);
}

/*
 * iu_stop_handling_events(): informs the event handler to stop handling events
 *
 *   input: iu_eh_t *: the event handler to stop.
 *	    unsigned int: the (user-defined) reason why
 *          iu_eh_shutdown_t *: the shutdown callback. if it is NULL,
 *				the event handler will stop right away;
 *				otherwise, the event handler will not
 *				stop until the callback returns B_TRUE
 *	    void *: data for the shutdown callback. it may be NULL
 *  output: void
 *   notes: the event handler in question must be in iu_handle_events()
 */

void
iu_stop_handling_events(iu_eh_t *eh, unsigned int reason,
    iu_eh_shutdown_t *shutdown, void *arg)
{
	eh->iueh_stop   = B_TRUE;
	eh->iueh_reason = reason;
	eh->iueh_shutdown = shutdown;
	eh->iueh_shutdown_arg = arg;
}

/*
 * grow_fds(): grows the internal file descriptor set used by the event
 *		  handler
 *
 *   input: iu_eh_t *: the event handler whose descriptor set needs to be grown
 *          int: the new total number of descriptors needed in the set
 *  output: int: zero on failure, success otherwise
 */

static int
grow_fds(iu_eh_t *eh, int total_fds)
{
	unsigned int	i;
	struct pollfd	*new_pollfds;
	iu_event_node_t	*new_events;

	if (total_fds <= eh->iueh_num_fds)
		return (1);

	new_pollfds = realloc(eh->iueh_pollfds,
	    total_fds * sizeof (struct pollfd));
	if (new_pollfds == NULL)
		return (0);

	eh->iueh_pollfds = new_pollfds;

	new_events = realloc(eh->iueh_events,
	    total_fds * sizeof (iu_event_node_t));
	if (new_events == NULL) {

		/*
		 * yow.  one realloc failed, but the other succeeded.
		 * we will just leave the descriptor size at the
		 * original size.  if the caller tries again, then the
		 * first realloc() will do nothing since the requested
		 * number of descriptors is already allocated.
		 */

		return (0);
	}

	for (i = eh->iueh_num_fds; i < total_fds; i++)
		eh->iueh_pollfds[i].fd = -1;

	eh->iueh_events  = new_events;
	eh->iueh_num_fds = total_fds;
	return (1);
}

/*
 * when increasing the file descriptor set size, how much to increase by:
 */

#define	EH_FD_SLACK	10

/*
 * iu_register_event(): adds an event to the set managed by an event handler
 *
 *   input: iu_eh_t *: the event handler to add the event to
 *          int: the descriptor on which to listen for events.  must be
 *		 a descriptor which has not yet been registered.
 *          short: the events to listen for on that descriptor
 *          iu_eh_callback_t: the callback to execute when the event happens
 *          void *: the argument to pass to the callback function
 *  output: iu_event_id_t: -1 on failure, the new event id otherwise
 */

iu_event_id_t
iu_register_event(iu_eh_t *eh, int fd, short events, iu_eh_callback_t *callback,
    void *arg)
{
	if (eh->iueh_num_fds <= fd)
		if (grow_fds(eh, fd + EH_FD_SLACK) == 0)
			return (-1);

	/*
	 * the current implementation uses the file descriptor itself
	 * as the iu_event_id_t, since we know the kernel's gonna be
	 * pretty smart about managing file descriptors and we know
	 * that they're per-process unique.  however, it does mean
	 * that the same descriptor cannot be registered multiple
	 * times for different callbacks depending on its events.  if
	 * this behavior is desired, either use dup(2) to get a unique
	 * descriptor, or demultiplex in the callback function based
	 * on `events'.
	 */

	if (eh->iueh_pollfds[fd].fd != -1)
		return (-1);

	eh->iueh_pollfds[fd].fd 		= fd;
	eh->iueh_pollfds[fd].events		= events;
	eh->iueh_events[fd].iuen_callback	= callback;
	eh->iueh_events[fd].iuen_arg		= arg;

	return (fd);
}

/*
 * iu_unregister_event(): removes an event from the set managed by an event
 *			  handler
 *
 *   input: iu_eh_t *: the event handler to remove the event from
 *          iu_event_id_t: the event to remove (from iu_register_event())
 *          void **: if non-NULL, will be set to point to the argument passed
 *                   into iu_register_event()
 *  output: int: zero on failure, success otherwise
 */

int
iu_unregister_event(iu_eh_t *eh, iu_event_id_t event_id, void **arg)
{
	if (event_id < 0 || event_id >= eh->iueh_num_fds ||
	    eh->iueh_pollfds[event_id].fd == -1)
		return (0);

	/*
	 * fringe condition: in case this event was about to be called
	 * back in iu_handle_events(), zero revents to prevent it.
	 * (having an unregistered event get called back could be
	 * disastrous depending on if `arg' is reference counted).
	 */

	eh->iueh_pollfds[event_id].revents = 0;
	eh->iueh_pollfds[event_id].fd = -1;
	if (arg != NULL)
		*arg = eh->iueh_events[event_id].iuen_arg;

	return (1);
}

/*
 * iu_handle_events(): begins handling events on an event handler
 *
 *   input: iu_eh_t *: the event handler to begin event handling on
 *          tq_t *: a timer queue of timers to process while handling events
 *                  (see timer_queue.h for details)
 *  output: int: the reason why we stopped, -1 if due to internal failure
 */

int
iu_handle_events(iu_eh_t *eh, iu_tq_t *tq)
{
	int		n_lit, timeout, sig, saved_errno;
	unsigned int	i;
	sigset_t	oset;

	eh->iueh_stop = B_FALSE;
	do {
		timeout = tq ? iu_earliest_timer(tq) : INFTIM;

		/*
		 * we only unblock registered signals around poll(); this
		 * way other parts of the code don't have to worry about
		 * restarting "non-restartable" system calls and so forth.
		 */

		(void) sigprocmask(SIG_UNBLOCK, &eh->iueh_sig_regset, &oset);
		n_lit = poll(eh->iueh_pollfds, eh->iueh_num_fds, timeout);
		saved_errno = errno;
		(void) sigprocmask(SIG_SETMASK, &oset, NULL);

		switch (n_lit) {

		case -1:
			if (saved_errno != EINTR)
				return (-1);

			for (sig = 0; sig < NSIG; sig++) {
				if (eh->iueh_sig_info[sig].iues_pending) {
					eh->iueh_sig_info[sig].iues_pending =
					    B_FALSE;
					eh->iueh_sig_info[sig].iues_handler(eh,
					    sig,
					    eh->iueh_sig_info[sig].iues_data);
				}
			}

			if (eh->iueh_shutdown != NULL)
				break;

			continue;

		case  0:
			/*
			 * timeout occurred.  we must have a valid tq pointer
			 * since that's the only way a timeout can happen.
			 */

			(void) iu_expire_timers(tq);
			continue;

		default:
			break;
		}

		/* file descriptors are lit; call 'em back */

		for (i = 0; i < eh->iueh_num_fds && n_lit > 0; i++) {

			if (eh->iueh_pollfds[i].revents == 0)
				continue;

			n_lit--;

			/*
			 * turn off any descriptors that have gone
			 * bad.  shouldn't happen, but...
			 */

			if (eh->iueh_pollfds[i].revents & (POLLNVAL|POLLERR)) {
				/* TODO: issue a warning here - but how? */
				(void) iu_unregister_event(eh, i, NULL);
				continue;
			}

			eh->iueh_events[i].iuen_callback(eh, i,
			    eh->iueh_pollfds[i].revents, i,
			    eh->iueh_events[i].iuen_arg);
		}

	} while (eh->iueh_stop == B_FALSE || (eh->iueh_shutdown != NULL &&
	    eh->iueh_shutdown(eh, eh->iueh_shutdown_arg) == B_FALSE));

	return (eh->iueh_reason);
}

/*
 * post_signal(): posts a signal for later consumption in iu_handle_events()
 *
 *   input: int: the signal that's been received
 *  output: void
 */

static void
post_signal(int sig)
{
	if (signal_to_eh[sig] != NULL)
		signal_to_eh[sig]->iueh_sig_info[sig].iues_pending = B_TRUE;
}

/*
 * iu_eh_register_signal(): registers a signal handler with an event handler
 *
 *   input: iu_eh_t *: the event handler to register the signal handler with
 *	    int: the signal to register
 *	    iu_eh_sighandler_t *: the signal handler to call back
 *	    void *: the argument to pass to the signal handler function
 *   output: int: zero on failure, success otherwise
 */

int
iu_eh_register_signal(iu_eh_t *eh, int sig, iu_eh_sighandler_t *handler,
    void *data)
{
	struct sigaction	act;

	if (sig < 0 || sig >= NSIG || signal_to_eh[sig] != NULL)
		return (0);

	act.sa_flags = 0;
	act.sa_handler = &post_signal;
	(void) sigemptyset(&act.sa_mask);
	(void) sigaddset(&act.sa_mask, sig); /* used for sigprocmask() */

	if (sigaction(sig, &act, NULL) == -1)
		return (0);

	(void) sigprocmask(SIG_BLOCK, &act.sa_mask, NULL);

	eh->iueh_sig_info[sig].iues_data = data;
	eh->iueh_sig_info[sig].iues_handler = handler;
	signal_to_eh[sig] = eh;

	(void) sigaddset(&eh->iueh_sig_regset, sig);
	return (0);
}

/*
 * iu_eh_unregister_signal(): unregisters a signal handler from an event handler
 *
 *   input: iu_eh_t *: the event handler to unregister the signal handler from
 *	    int: the signal to unregister
 *	    void **: if non-NULL, will be set to point to the argument passed
 *		     into iu_eh_register_signal()
 *  output: int: zero on failure, success otherwise
 */

int
iu_eh_unregister_signal(iu_eh_t *eh, int sig, void **datap)
{
	sigset_t	set;

	if (sig < 0 || sig >= NSIG || signal_to_eh[sig] != eh)
		return (0);

	if (signal(sig, SIG_DFL) == SIG_ERR)
		return (0);

	if (datap != NULL)
		*datap = eh->iueh_sig_info[sig].iues_data;

	(void) sigemptyset(&set);
	(void) sigaddset(&set, sig);
	(void) sigprocmask(SIG_UNBLOCK, &set, NULL);

	eh->iueh_sig_info[sig].iues_data = NULL;
	eh->iueh_sig_info[sig].iues_handler = NULL;
	eh->iueh_sig_info[sig].iues_pending = B_FALSE;
	signal_to_eh[sig] = NULL;

	(void) sigdelset(&eh->iueh_sig_regset, sig);
	return (1);
}