blob: f154df75a63969dcb62c593172154b07089ce522 [file] [log] [blame]
/* upstart
*
* event.c - event queue and handling
*
* Copyright © 2009 Canonical Ltd.
* Author: Scott James Remnant <scott@netsplit.com>.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2, as
* published by the Free Software Foundation.
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* HAVE_CONFIG_H */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fnmatch.h>
#include <nih/macros.h>
#include <nih/alloc.h>
#include <nih/string.h>
#include <nih/tree.h>
#include <nih/logging.h>
#include <nih/error.h>
#include "environ.h"
#include "event.h"
#include "event_operator.h"
#include "blocked.h"
#include "errors.h"
/**
* event_operator_new:
* @parent: parent object for new operator,
* @type: type of operator,
* @name: name of event to match,
* @env: NULL-terminated array of environment variables to match.
*
* Allocates and returns a new EventOperator structure with the @type given,
* if @type is EVENT_MATCH then the operator will be used to match an event
* with the given @name and @arguments using event_match().
*
* @env is optional, and may be NULL; if given it should be a NULL-terminated
* array of environment variables in KEY=VALUE form. @env will be referenced
* by the new event. After calling this function, you should never use
* nih_free() to free @env and instead use nih_unref() or nih_discard() if
* you longer need to use it.
*
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned operator. When all parents
* of the returned operator are freed, the returned operator will also be
* freed.
*
* Returns: newly allocated EventOperator structure, or NULL if
* insufficient memory.
**/
EventOperator *
event_operator_new (const void *parent,
EventOperatorType type,
const char *name,
char **env)
{
EventOperator *oper;
nih_assert ((type == EVENT_MATCH) || (name == NULL));
nih_assert ((type == EVENT_MATCH) || (env == NULL));
nih_assert ((type != EVENT_MATCH) || (name != NULL));
oper = nih_new (parent, EventOperator);
if (! oper)
return NULL;
nih_tree_init (&oper->node);
oper->type = type;
oper->value = FALSE;
if (oper->type == EVENT_MATCH) {
oper->name = nih_strdup (oper, name);
if (! oper->name) {
nih_free (oper);
return NULL;
}
oper->env = env;
if (oper->env)
nih_ref (oper->env, oper);
} else {
oper->name = NULL;
oper->env = NULL;
}
oper->event = NULL;
nih_alloc_set_destructor (oper, event_operator_destroy);
return oper;
}
/**
* event_operator_copy:
* @parent: parent object for new operator,
* @old_oper: operator to copy.
*
* Allocates and returns a new EventOperator structure which is an identical
* copy of @old_oper; including any matched state or events.
*
* If @old_oper is referencing an event, that status is also copied into the
* newly returned operator; which will hold an additional block if
* appropriate, on the event.
*
* If @old_oper has children, these will be copied as well, and will be
* given their parent as their nih_alloc() parent.
*
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned operator. When all parents
* of the returned operator are freed, the returned operator will also be
* freed.
*
* Returns: newly allocated EventOperator structure, or NULL if
* insufficient memory.
**/
EventOperator *
event_operator_copy (const void *parent,
const EventOperator *old_oper)
{
EventOperator *oper, *child;
nih_assert (old_oper != NULL);
oper = event_operator_new (parent, old_oper->type,
old_oper->name, NULL);
if (! oper)
return NULL;
oper->value = old_oper->value;
if (old_oper->env) {
oper->env = nih_str_array_copy (oper, NULL, old_oper->env);
if (! oper->env) {
nih_free (oper);
return NULL;
}
}
if (old_oper->event) {
oper->event = old_oper->event;
event_block (oper->event);
}
if (old_oper->node.left) {
child = event_operator_copy (
oper, (EventOperator *)old_oper->node.left);
if (! child) {
nih_free (oper);
return NULL;
}
nih_tree_add (&oper->node, &child->node, NIH_TREE_LEFT);
}
if (old_oper->node.right) {
child = event_operator_copy (
oper, (EventOperator *)old_oper->node.right);
if (! child) {
nih_free (oper);
return NULL;
}
nih_tree_add (&oper->node, &child->node, NIH_TREE_RIGHT);
}
return oper;
}
/**
* event_operator_destroy:
* @oper: operator to be destroyed.
*
* Unblocks and unreferences the event referenced by @oper and unlinks it
* from the event tree.
*
* Normally used or called from an nih_alloc() destructor so that the list
* item is automatically removed from its containing list when freed.
*
* Returns: zero.
**/
int
event_operator_destroy (EventOperator *oper)
{
nih_assert (oper != NULL);
if (oper->event)
event_unblock (oper->event);
nih_tree_destroy (&oper->node);
return 0;
}
/**
* event_operator_update:
* @oper: operator to update.
*
* Updates the value of @oper to reflect the value of its child nodes
* when combined with the particular operation this represents.
*
* This may only be called if the type of @oper is EVENT_OR or EVENT_AND.
**/
void
event_operator_update (EventOperator *oper)
{
EventOperator *left, *right;
nih_assert (oper != NULL);
nih_assert (oper->node.left != NULL);
nih_assert (oper->node.right != NULL);
left = (EventOperator *)oper->node.left;
right = (EventOperator *)oper->node.right;
switch (oper->type) {
case EVENT_OR:
oper->value = (left->value || right->value);
break;
case EVENT_AND:
oper->value = (left->value && right->value);
break;
default:
nih_assert_not_reached ();
}
}
/**
* event_operator_match:
* @oper: operator to match against.
* @event: event to match,
* @env: NULL-terminated array of environment variables for expansion.
*
* Compares @oper against @event to see whether they are identical in name,
* and whether @event contains a superset of the environment variables
* given in @oper.
*
* @env is optional, and may be NULL; if given it should be a NULL-terminated
* array of environment variables in KEY=VALUE form.
*
* Matching of environment is done first by position until the first variable
* in @oper with a name specified is found, and subsequently by name. Each
* value is matched against the equivalent in @event as a glob, undergoing
* expansion against @env first.
*
* This may only be called if the type of @oper is EVENT_MATCH.
*
* Returns: TRUE if the events match, FALSE otherwise.
**/
int
event_operator_match (EventOperator *oper,
Event *event,
char * const *env)
{
char * const *oenv;
char * const *eenv;
nih_assert (oper != NULL);
nih_assert (oper->type == EVENT_MATCH);
nih_assert (oper->node.left == NULL);
nih_assert (oper->node.right == NULL);
nih_assert (event != NULL);
/* Names must match */
if (strcmp (oper->name, event->name))
return FALSE;
/* Match operator environment variables against those from the event,
* starting both from the beginning.
*/
for (oenv = oper->env, eenv = event->env; oenv && *oenv;
oenv++, eenv++) {
nih_local char *expoval = NULL;
char *oval, *eval;
int ret;
oval = strchr (*oenv, '=');
if (oval) {
/* Hunt through the event environment to find the
* equivalent entry */
eenv = environ_lookup (event->env, *oenv,
oval - *oenv);
/* Value to match against follows the equals. */
oval++;
} else {
/* Value to match against is the whole string. */
oval = *oenv;
}
/* Make sure we haven't gone off the end of the event
* environment array; this catches both too many positional
* matches and no such variable.
*/
if (! (eenv && *eenv))
return FALSE;
/* Grab the value out by looking for the equals, we don't
* care about the name if we're positional and we've already
* matched it when not.
*/
eval = strchr (*eenv, '=');
nih_assert (eval != NULL);
eval++;
/* Expand operator value against given environment before
* matching; silently discard errors, since otherwise we'd
* be excessively noisy on every event.
*/
while (! (expoval = environ_expand (NULL, oval, env))) {
NihError *err;
err = nih_error_get ();
if (err->number != ENOMEM) {
nih_free (err);
return FALSE;
}
nih_free (err);
}
ret = fnmatch (expoval, eval, 0);
if (ret)
return FALSE;
}
return TRUE;
}
/**
* event_operator_handle:
* @root: operator tree to update,
* @event: event to match against,
* @env: NULL-terminated array of environment variables for expansion.
*
* Handles the emission of @event, matching it against EVENT_MATCH nodes in
* the EventOperator tree rooted at @oper, and updating the values of other
* nodes to match.
*
* @env is optional, and may be NULL; if given it should be a NULL-terminated
* array of environment variables in KEY=VALUE form and will be used to expand
* EVENT_MATCH nodes before matching them,
*
* If @event is matched within this tree, it will be referenced and blocked
* by the nodes that match it. The blockage and references can be cleared
* using event_operator_reset().
*
* Note that this returns to indicate whether a successful match was made;
* you should also check the value of @oper to make sure you react to this,
* as that still may be FALSE.
*
* Returns: TRUE if @event matched an entry in the tree under @oper, FALSE
* otherwise.
**/
int
event_operator_handle (EventOperator *root,
Event *event,
char * const *env)
{
int ret = FALSE;
nih_assert (root != NULL);
nih_assert (event != NULL);
/* A post-order traversal will give us the nodes in exactly the
* order we want. We get a chance to update all of a node's children
* before we update the node itself. Simply iterate the tree and
* update the nodes.
*/
NIH_TREE_FOREACH_POST (&root->node, iter) {
EventOperator *oper = (EventOperator *)iter;
switch (oper->type) {
case EVENT_OR:
case EVENT_AND:
event_operator_update (oper);
break;
case EVENT_MATCH:
if ((! oper->value)
&& event_operator_match (oper, event, env)) {
oper->value = TRUE;
oper->event = event;
event_block (oper->event);
ret = TRUE;
}
break;
default:
nih_assert_not_reached ();
}
}
return ret;
}
/**
* event_operator_filter:
* @data: not used,
* @oper: EventOperator to check.
*
* Used when iterating the operator tree to filter out those operators and
* their children for which the value is not TRUE.
*
* Returns: TRUE if operator should be ignored, FALSE otherwise.
**/
static int
event_operator_filter (void *data,
EventOperator *oper)
{
nih_assert (oper != NULL);
return oper->value != TRUE;
}
/**
* event_operator_environment:
* @root: operator tree to collect from,
* @env: NULL-terminated array of environment variables to add to,
* @parent: parent object for new array,
* @len: length of @env,
* @key: key of variable to contain event names.
*
* Collects environment from the portion of the EventOperator tree rooted at
* @oper that are TRUE, ignoring the rest.
*
* Environment variables from each event (in tree order) will be added to
* the NULL-terminated array at @env so that it contains the complete
* environment of the operator.
*
* @len will be updated to contain the new array length and @env will
* be updated to point to the new array pointer.
*
* Note that on failure, some of the entries may have been appended to
* @env already. This is normally not a problem, since entries will be
* replaced in @env if this is repeated.
*
* If @key is not NULL, a key of that name will be set in @env (which must
* not be NULL) containing a space-separated list of event names.
*
* If the array pointed to by @env is NULL, the array will be allocated and
* if @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned array. When all parents of the
* returned array are freed, the returned array will also be freed.
*
* When the array pointed to by @env is not NULL, @parent is ignored;
* though it usual to pass a parent of @env for style reasons.
*
* Returns: pointer to new array on success, NULL on insufficient memory.
**/
char **
event_operator_environment (EventOperator *root,
char ***env,
const void *parent,
size_t *len,
const char *key)
{
nih_local char *evlist = NULL;
nih_assert (root != NULL);
nih_assert (env != NULL);
nih_assert (len != NULL);
/* Initialise the event list variable with the name given. */
if (key) {
evlist = nih_sprintf (NULL, "%s=", key);
if (! evlist)
return NULL;
}
/* Always return an array, even if its zero length */
if (! *env) {
*env = nih_str_array_new (parent);
if (! *env)
return NULL;
}
/* Iterate the operator tree, filtering out nodes with a non-TRUE
* value and their children. The rationale for this is that this
* then matches only the events that had an active role in starting
* the job, not the ones that were also blocked, but the other half
* of their logic wasn't present.
*/
NIH_TREE_FOREACH_FULL (&root->node, iter,
(NihTreeFilter)event_operator_filter, NULL) {
EventOperator *oper = (EventOperator *)iter;
if (oper->type != EVENT_MATCH)
continue;
nih_assert (oper->event != NULL);
/* Add environment from the event */
if (! environ_append (env, parent, len, TRUE, oper->event->env))
return NULL;
/* Append the name of the event to the string we're building */
if (evlist) {
if (evlist[strlen (evlist) - 1] != '=') {
if (! nih_strcat_sprintf (&evlist, NULL, " %s",
oper->event->name))
return NULL;
} else {
if (! nih_strcat (&evlist, NULL,
oper->event->name))
return NULL;
}
}
}
/* Append the event list to the environment */
if (evlist)
if (! environ_add (env, parent, len, TRUE, evlist))
return NULL;
return *env;
}
/**
* event_operator_events:
* @root: operator tree to collect from,
* @parent: parent object for blocked structures,
* @list: list to add events to.
*
* Collects events from the portion of the EventOperator tree rooted at @oper
* that are TRUE, ignoring the rest.
*
* Each event is blocked and a Blocked structure will be appended to @list
* for it.
*
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned structures. When all parents
* of a returned structure are freed, the returned structure will also be
* freed.
**/
void
event_operator_events (EventOperator *root,
const void *parent,
NihList *list)
{
nih_assert (root != NULL);
nih_assert (list != NULL);
/* Iterate the operator tree, filtering out nodes with a non-TRUE
* value and their children. The rationale for this is that this
* then matches only the events that had an active role in starting
* the job, not the ones that were also blocked, but the other half
* of their logic wasn't present.
*/
NIH_TREE_FOREACH_FULL (&root->node, iter,
(NihTreeFilter)event_operator_filter, NULL) {
EventOperator *oper = (EventOperator *)iter;
Blocked *blocked;
if (oper->type != EVENT_MATCH)
continue;
nih_assert (oper->event != NULL);
blocked = NIH_MUST (blocked_new (parent, BLOCKED_EVENT,
oper->event));
nih_list_add (list, &blocked->entry);
event_block (blocked->event);
}
}
/**
* event_operator_reset:
* @root: operator tree to update.
*
* Resets the EventOperator tree rooted at @oper, unblocking and
* unreferencing any events that were matched by the tree and changing
* the values of other operators to match.
**/
void
event_operator_reset (EventOperator *root)
{
nih_assert (root != NULL);
/* A post-order iteration means we visit children first, perfect! */
NIH_TREE_FOREACH_POST (&root->node, iter) {
EventOperator *oper = (EventOperator *)iter;
switch (oper->type) {
case EVENT_OR:
case EVENT_AND:
event_operator_update (oper);
break;
case EVENT_MATCH:
oper->value = FALSE;
if (oper->event) {
event_unblock (oper->event);
oper->event = NULL;
}
break;
default:
nih_assert_not_reached ();
}
}
}