/*
 * Amanda, The Advanced Maryland Automatic Network Disk Archiver
 * Copyright (c) 1999 University of Maryland at College Park
 * Copyright (c) 2007-2012 Zmanda, Inc.  All Rights Reserved.
 * Copyright (c) 2013-2016 Carbonite, Inc.  All Rights Reserved.
 * All Rights Reserved.
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of U.M. not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  U.M. makes no representations about the
 * suitability of this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 *
 * U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
 * BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Authors: the Amanda Development Team.  Its members are listed in a
 * file named AUTHORS, in the root directory of this distribution.
 */
/*
 * $Id: event.c,v 1.24 2006/06/16 10:55:05 martinea Exp $
 *
 * Event handler.  Serializes different kinds of events to allow for
 * a uniform interface, central state storage, and centralized
 * interdependency logic.
 *
 * This is a compatibility wrapper over Glib's GMainLoop.  New code should
 * use Glib's interface directly.
 *
 * Each event_handle is associated with a unique GSource, identified by it
 * event_source_id.
 */

#include "amanda.h"
#include "conffile.h"
#include "event.h"
#include "glib-util.h"

/* TODO: use mem chunks to allocate event_handles */
/* TODO: lock stuff for threading */

/* Write a debugging message if the config variable debug_event
 * is greater than or equal to i */
#define event_debug(i, ...) do {	\
       if ((i) <= debug_event) {	\
           dbprintf(__VA_ARGS__);	\
       }				\
} while (0)

/*
 * The opaque handle passed back to the caller.  This is typedefed to
 * event_handle_t in our header file.
 */
struct event_handle {
    event_fn_t fn;		/* function to call when this fires */
    void *arg;			/* argument to pass to previous function */

    event_type_t type;		/* type of event */
    event_id_t data;		/* type data */

    GSource *source;		/* Glib event source, if one exists */
    guint source_id;	        /* ID of the glib event source */

    gboolean has_fired;		/* for use by event_wait() */
    gboolean is_dead;		/* should this event be deleted? */
};

/* A list of all extant event_handle objects, used for searching for particular
 * events and for deleting dead events */
GSList *all_events = NULL;

#if (GLIB_MAJOR_VERSION > 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 31))
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#endif
GStaticMutex event_mutex = G_STATIC_MUTEX_INIT;
#if (GLIB_MAJOR_VERSION > 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 31))
# pragma GCC diagnostic pop
#endif

/* should event_loop_run stop? */
gboolean stop = FALSE;
gboolean global_return_when_empty = TRUE;

/*
 * Utility functions
 */

static const char *event_type2str(event_type_t type);
static gboolean any_mainloop_events(void);

/* "Fire" an event handle, by calling its callback function */
#define	fire(eh) do { \
	event_debug(1, "firing %p: %s/%jd\n", eh, event_type2str((eh)->type), (eh)->data); \
	if (*(eh)->fn) { (*(eh)->fn)((eh)->arg); } \
	(eh)->has_fired = TRUE; \
} while(0)

/* Adapt a Glib callback to an event_handle_t callback; assumes that the
 * user_ptr for the Glib callback is a pointer to the event_handle_t.  */
static gboolean 
event_handle_callback(
    gpointer user_ptr)
{
    event_handle_t *hdl = (event_handle_t *)user_ptr;

    /* if the handle is dead, then don't fire the callback; this means that
     * we're in the process of freeing the event */
    if (!hdl->is_dead) {
	fire(hdl);
    }

    /* don't ever let GMainLoop destroy GSources */
    return TRUE;
}

/*
 * Public functions
 *  DEPRECATED because not safe in multi-thread, callback can be called before event_register return
 */
/*
event_handle_t *
event_register(
    event_id_t data,
    event_type_t type,
    event_fn_t fn,
    void *arg)
{
    event_handle_t *handle;

    handle = event_create(data, type, fn, arg);
    event_activate(handle);
    return handle;
}
*/

event_handle_t *
event_create(
    event_id_t data,
    event_type_t type,
    event_fn_t fn,
    void *arg)
{
    event_handle_t *handle;

    g_static_mutex_lock(&event_mutex);

    /* sanity-checking */
    if ((type == EV_READFD) || (type == EV_WRITEFD)) {
	/* make sure we aren't given a high fd that will overflow a fd_set */
	if (data >= (int)FD_SETSIZE) {
	    error(_("event_create: Invalid file descriptor %jd"), data);
	    /*NOTREACHED*/
	}
    } else if (type == EV_TIME) {
	if (data < 0) {
	    error(_("event_create: interval for EV_TIME must be greater than 0; got %jd"), data);
	}
    }

    handle = g_new0(event_handle_t, 1);
    handle->fn = fn;
    handle->arg = arg;
    handle->type = type;
    handle->data = data;
    handle->is_dead = FALSE;

    event_debug(1, _("event: register: %p->data=%jd, type=%s\n"),
		    handle, handle->data, event_type2str(handle->type));

    g_static_mutex_unlock(&event_mutex);
    return handle;
}

void
event_activate(
    event_handle_t *handle)
{
    GIOCondition cond;
    assert(handle != NULL);

    g_static_mutex_lock(&event_mutex);

    /* add to the list of events */
    all_events = g_slist_prepend(all_events, (gpointer)handle);

    /* and set up the GSource for this event */
    switch (handle->type) {
	case EV_READFD:
	case EV_WRITEFD:
	    /* create a new source */
	    if (handle->type == EV_READFD) {
		cond = G_IO_IN | G_IO_HUP | G_IO_ERR;
	    } else {
		cond = G_IO_OUT | G_IO_ERR;
	    }

	    handle->source = new_fdsource(handle->data, cond);

	    /* attach it to the default GMainLoop */
	    g_source_attach(handle->source, NULL);
	    handle->source_id = g_source_get_id(handle->source);

	    /* And set its callbacks */
	    g_source_set_callback(handle->source, event_handle_callback,
				  (gpointer)handle, NULL);

	    /* drop our reference to it, so when it's detached, it will be
	     * destroyed. */
	    g_source_unref(handle->source);
	    break;

	case EV_TIME:
	    /* Glib provides a nice shortcut for timeouts.  The *1000 converts
	     * seconds to milliseconds. */
	    handle->source_id = g_timeout_add(handle->data * 1000, event_handle_callback,
					      (gpointer)handle);

	    /* But it doesn't give us the source directly.. */
	    handle->source = g_main_context_find_source_by_id(NULL, handle->source_id);
	    /* EV_TIME must always be handled after EV_READ */
	    g_source_set_priority(handle->source, 10);
	    break;

	case EV_WAIT:
	    /* nothing to do -- these are handled independently of GMainLoop */
	    break;

	default:
	    error(_("Unknown event type %s"), event_type2str(handle->type));
    }

    g_static_mutex_unlock(&event_mutex);
    return;
}


/*
 * Mark an event to be released.  Because we may be traversing the queue
 * when this is called, we must wait until later to actually remove
 * the event.
 */
void
event_release(
    event_handle_t *handle)
{
    assert(handle != NULL);

    g_static_mutex_lock(&event_mutex);
    event_debug(1, _("event: release (mark): %p data=%jd, type=%s\n"),
		    handle, handle->data,
		    event_type2str(handle->type));
    assert(!handle->is_dead);

    /* Mark it as dead and leave it for the event_loop to remove */
    handle->is_dead = TRUE;

    if (global_return_when_empty && !any_mainloop_events()) {
	g_main_loop_quit(default_main_loop());
    }

    g_static_mutex_unlock(&event_mutex);
}

/*
 * Fire all EV_WAIT events waiting on the specified id.
 */
int
event_wakeup(
    event_id_t id)
{
    GSList *iter;
    GSList *tofire = NULL;
    int nwaken = 0;

    g_static_mutex_lock(&event_mutex);
    event_debug(1, _("event: wakeup: enter (%jd)\n"), id);

    /* search for any and all matching events, and record them.  This way
     * we have determined the whole list of events we'll be firing *before*
     * we fire any of them. */
    for (iter = all_events; iter != NULL; iter = g_slist_next(iter)) {
	event_handle_t *eh = (event_handle_t *)iter->data;
	if (eh->type == EV_WAIT && eh->data == id && !eh->is_dead) {
	    tofire = g_slist_append(tofire, (gpointer)eh);
	}
    }

    /* fire them */
    for (iter = tofire; iter != NULL; iter = g_slist_next(iter)) {
	event_handle_t *eh = (event_handle_t *)iter->data;
	if (eh->type == EV_WAIT && eh->data == id && !eh->is_dead) {
	    event_debug(1, _("A: event: wakeup triggering: %p id=%jd\n"), eh, id);
	    /* The lcok must be release before running the event */
	    g_static_mutex_unlock(&event_mutex);
	    fire(eh);
	    g_static_mutex_lock(&event_mutex);
	    nwaken++;
	}
    }

    /* and free the temporary list */
    g_slist_free(tofire);

    g_static_mutex_unlock(&event_mutex);
    return (nwaken);
}


/*
 * The event loop.
 */

static void event_loop_wait (event_handle_t *, const int, gboolean return_when_empty);

void
event_loop(
    int nonblock)
{
    event_loop_wait(NULL, nonblock, TRUE);
}

void
event_loop_run(
    void)
{
    stop = FALSE;
    event_loop_wait(NULL, 0, FALSE);
}

void
event_loop_quit(
    void)
{
    stop = TRUE;
}

void
event_wait(
    event_handle_t *eh)
{
    event_loop_wait(eh, 0, TRUE);
}

/* Flush out any dead events in all_events.  Be careful that this
 * isn't called while someone is iterating over all_events.
 *
 * @param wait_eh: the event handle we're waiting on, which shouldn't
 *	    be flushed.
 */
static void
flush_dead_events(event_handle_t *wait_eh)
{
    GSList *iter, *next;

    for (iter = all_events; iter != NULL; iter = next) {
	event_handle_t *hdl = (event_handle_t *)iter->data;
	next = g_slist_next(iter);

	/* (handle the case when wait_eh is dead by simply not deleting
	 * it; the next run of event_loop will take care of it) */
	if (hdl->is_dead && hdl != wait_eh) {
	    all_events = g_slist_delete_link(all_events, iter);
	    if (hdl->source) g_source_destroy(hdl->source);

	    amfree(hdl);
	}
    }
}

/* Return TRUE if we have any events outstanding that can be dispatched
 * by GMainLoop.  Recall EV_WAIT events appear in all_events, but are
 * not dispatched by GMainLoop.  */
static gboolean
any_mainloop_events(void)
{
    GSList *iter;
    gboolean ret = FALSE;

    for (iter = all_events; iter != NULL; iter = g_slist_next(iter)) {
	event_handle_t *hdl = (event_handle_t *)iter->data;
	event_debug(2, _("list %p: %s %s/%jd\n"), hdl, hdl->is_dead?"dead":"alive", event_type2str((hdl)->type), (hdl)->data);
	if (hdl->type != EV_WAIT && !hdl->is_dead)
	    ret = TRUE;
    }

    return ret;
}

static void
event_loop_wait(
    event_handle_t *wait_eh,
    int nonblock,
    gboolean return_when_empty)
{
    global_return_when_empty = return_when_empty;
    g_static_mutex_lock(&event_mutex);
    event_debug(1, _("event: loop: enter: nonblockg=%d, eh=%p\n"), nonblock, wait_eh);

    /* If we're waiting for a specific event, then reset its has_fired flag */
    if (wait_eh) {
	wait_eh->has_fired = FALSE;
    }

    /* Keep looping until there are no events, or until wait_eh has fired */
    while (1) {
	/* clean up first, so we don't accidentally check a dead source */
	flush_dead_events(wait_eh);

	/* if there's nothing to wait for, then don't block, but run an
	 * iteration so that any other users of GMainLoop will get a chance
	 * to run. */
	if (return_when_empty && !any_mainloop_events())
	    break;

	/* Do an iteration */
	/* Relese the lock before running an iteration */
	g_static_mutex_unlock(&event_mutex);
	g_main_context_iteration(NULL, !nonblock);
	g_static_mutex_lock(&event_mutex);

	/* stop if we're told to */
	if (!return_when_empty && stop)
	    break;

	/* If the event we've been waiting for has fired or been released, as
	 * appropriate, we're done.  See the comments for event_wait in event.h
	 * for the skinny on this weird expression. */
	if (wait_eh && ((wait_eh->type == EV_WAIT && wait_eh->is_dead)
	             || (wait_eh->type != EV_WAIT && wait_eh->has_fired)))
	    break;

	/* Don't loop if we're not blocking */
	if (nonblock)
	    break;
    }

    /* extra cleanup, to keep all_events short, and to delete wait_eh if it
     * has been released. */
    flush_dead_events(NULL);

    g_static_mutex_unlock(&event_mutex);
}

GMainLoop *
default_main_loop(void)
{
    static GMainLoop *loop = NULL;
    if (!loop)
	loop = g_main_loop_new(NULL, TRUE);
    return loop;
}

/*
 * Convert an event type into a string
 */
static const char *
event_type2str(
    event_type_t type)
{
    static const struct {
	event_type_t type;
	const char name[12];
    } event_types[] = {
#define	X(s)	{ s, stringize(s) }
	X(EV_READFD),
	X(EV_WRITEFD),
	X(EV_TIME),
	X(EV_WAIT),
#undef X
    };
    size_t i;

    for (i = 0; i < G_N_ELEMENTS(event_types); i++)
	if (type == event_types[i].type)
	    return (event_types[i].name);
    return (_("BOGUS EVENT TYPE"));
}

/*
 * FDSource -- a source for a file descriptor
 *
 * We could use Glib's GIOChannel for this, but it adds some buffering
 * and Unicode functionality that we really don't want.  The custom GSource
 * is simple enough anyway, and the Glib documentation describes it in prose.
 */

typedef struct FDSource {
    GSource source; /* must be the first element in the struct */
    GPollFD pollfd; /* Our file descriptor */
} FDSource;

static gboolean
fdsource_prepare(
    GSource *source G_GNUC_UNUSED,
    gint *timeout_)
{
    *timeout_ = -1; /* block forever, as far as we're concerned */
    return FALSE;
}

static gboolean
fdsource_check(
    GSource *source)
{
    FDSource *fds = (FDSource *)source;

    /* we need to be dispatched if any interesting events have been received by the FD */
    return fds->pollfd.events & fds->pollfd.revents;
}

static gboolean
fdsource_dispatch(
    GSource *source G_GNUC_UNUSED,
    GSourceFunc callback,
    gpointer user_data)
{
    if (callback)
	return callback(user_data);

    /* Don't automatically detach the event source if there's no callback. */
    return TRUE;
}

GSource *
new_fdsource(gint fd, GIOCondition events)
{
    static GSourceFuncs *fdsource_funcs = NULL;
    GSource *src;
    FDSource *fds;

    /* initialize these here to avoid a compiler warning */
    if (!fdsource_funcs) {
	fdsource_funcs = g_new0(GSourceFuncs, 1);
	fdsource_funcs->prepare = fdsource_prepare;
	fdsource_funcs->check = fdsource_check;
	fdsource_funcs->dispatch = fdsource_dispatch;
    }

    src = g_source_new(fdsource_funcs, sizeof(FDSource));
    fds = (FDSource *)src;

    fds->pollfd.fd = fd;
    fds->pollfd.events = events;
    g_source_add_poll(src, &fds->pollfd);

    return src;
}

/*
 * ChildWatchSource -- a source for a file descriptor
 *
 * Newer versions of glib provide equivalent functionality; consider
 * optionally using that, protected by a GLIB_CHECK_VERSION condition.
 */

/* Versions before glib-2.4.0 didn't include a child watch source, and versions
 * before 2.6.0 used unreliable signals.  On these versions, we implement
 * a "dumb" version of our own invention.  This is dumb in the sense that it
 * doesn't use SIGCHLD to detect a dead child, preferring to just poll at
 * exponentially increasing interals.  Writing a smarter implementation runs into
 * some tricky race conditions and extra machinery.  Since there are few, if any,
 * users of a glib version this old, such machinery wouldn't get much testing.
 *
 * FreeBSD users have also reported problems with the glib child watch source,
 * so we use the dumb version on FreeBSD, too.
 */

#if (defined(__FreeBSD__) || GLIB_MAJOR_VERSION < 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION < 6))
typedef struct ChildWatchSource {
    GSource source; /* must be the first element in the struct */

    pid_t pid;

    gint dead;
    gint status;

    gint timeout;
} ChildWatchSource;

/* this corresponds to rapid checks for about 10 seconds, after which the
 * waitpid() check occurs every 2 seconds. */
#define CWS_BASE_TIMEOUT 20
#define CWS_MULT_TIMEOUT 1.1
#define CWS_MAX_TIMEOUT 2000

static gboolean
child_watch_source_prepare(
    GSource *source,
    gint *timeout_)
{
    ChildWatchSource *cws = (ChildWatchSource *)source;

    *timeout_ = cws->timeout;

    cws->timeout *= CWS_MULT_TIMEOUT;
    if (cws->timeout > CWS_MAX_TIMEOUT) cws->timeout = CWS_MAX_TIMEOUT;

    return FALSE;
}

static gboolean
child_watch_source_check(
    GSource *source)
{
    ChildWatchSource *cws = (ChildWatchSource *)source;

    /* is it dead? */
    if (!cws->dead && waitpid(cws->pid, &cws->status, WNOHANG) > 0) {
	cws->dead = TRUE;
    }

    return cws->dead;
}

static gboolean
child_watch_source_dispatch(
    GSource *source G_GNUC_UNUSED,
    GSourceFunc callback,
    gpointer user_data)
{
    ChildWatchSource *cws = (ChildWatchSource *)source;

    /* this shouldn't happen, but just in case */
    if (cws->dead) {
	if (!callback) {
	    g_warning("child %jd died before callback was registered", (uintmax_t)cws->pid);
	    return FALSE;
	}

	((ChildWatchFunc)callback)(cws->pid, cws->status, user_data);

	/* Un-queue this source unconditionally -- the child can't die twice */
	return FALSE;
    }

    return TRUE;
}

GSource *
new_child_watch_source(pid_t pid)
{
    static GSourceFuncs *child_watch_source_funcs = NULL;
    GSource *src;
    ChildWatchSource *cws;

    /* initialize these here to avoid a compiler warning */
    if (!child_watch_source_funcs) {
	child_watch_source_funcs = g_new0(GSourceFuncs, 1);
	child_watch_source_funcs->prepare = child_watch_source_prepare;
	child_watch_source_funcs->check = child_watch_source_check;
	child_watch_source_funcs->dispatch = child_watch_source_dispatch;
    }

    src = g_source_new(child_watch_source_funcs, sizeof(ChildWatchSource));
    cws = (ChildWatchSource *)src;

    cws->pid = pid;
    cws->dead = FALSE;
    cws->timeout = CWS_BASE_TIMEOUT;

    return src;
}
#else
/* In more recent versions of glib, we just use the built-in glib source */
GSource *
new_child_watch_source(pid_t pid)
{
    return g_child_watch_source_new(pid);
}
#endif
