aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/event.cpp
diff options
context:
space:
mode:
authorGravatar David Adam <zanchey@ucc.gu.uwa.edu.au>2015-07-26 10:20:13 +0800
committerGravatar David Adam <zanchey@ucc.gu.uwa.edu.au>2015-07-26 10:20:13 +0800
commit3929e9de0e69666b37df87347d5ce15663e81347 (patch)
treeb2701c439c0260840ce1c68beaebf7de1178cc53 /src/event.cpp
parent793e1afa084982dac92c4fe19e50c25e326a79c2 (diff)
parentf4d1657c22c81a7720a91026f915b80d2d6aa6e8 (diff)
Merge branch 'master' into iwyu
Diffstat (limited to 'src/event.cpp')
-rw-r--r--src/event.cpp737
1 files changed, 737 insertions, 0 deletions
diff --git a/src/event.cpp b/src/event.cpp
new file mode 100644
index 00000000..eeff3eb5
--- /dev/null
+++ b/src/event.cpp
@@ -0,0 +1,737 @@
+/** \file event.c
+
+ Functions for handling event triggers
+
+*/
+#include "config.h" // IWYU pragma: keep
+
+#include <signal.h>
+#include <algorithm>
+#include <assert.h>
+#include <stddef.h>
+#include <string>
+
+#include "fallback.h" // IWYU pragma: keep
+#include "wutil.h" // IWYU pragma: keep - needed for gettext
+#include "input_common.h"
+#include "proc.h"
+#include "parser.h"
+#include "common.h"
+#include "event.h"
+#include "signal.h"
+#include "io.h"
+
+
+/**
+ Number of signals that can be queued before an overflow occurs
+*/
+#define SIG_UNHANDLED_MAX 64
+
+/**
+ This struct contains a list of generated signals waiting to be
+ dispatched
+*/
+typedef struct
+{
+ /**
+ Number of delivered signals
+ */
+ volatile int count;
+ /**
+ Whether signals have been skipped
+ */
+ volatile int overflow;
+ /**
+ Array of signal events
+ */
+ volatile int signal[SIG_UNHANDLED_MAX];
+}
+signal_list_t;
+
+/**
+ The signal event list. Actually two separate lists. One which is
+ active, which is the one that new events is written to. The inactive
+ one contains the events that are currently beeing performed.
+*/
+static signal_list_t sig_list[2]= {{},{}};
+
+/**
+ The index of sig_list that is the list of signals currently written to
+*/
+static volatile int active_list=0;
+
+typedef std::vector<event_t *> event_list_t;
+
+/**
+ List of event handlers.
+*/
+static event_list_t s_event_handlers;
+/**
+ List of event handlers that should be removed
+*/
+static event_list_t killme;
+
+/**
+ List of events that have been sent but have not yet been delivered because they are blocked.
+*/
+static event_list_t blocked;
+
+/** Variables (one per signal) set when a signal is observed. This is inspected by a signal handler. */
+static volatile bool s_observed_signals[NSIG] = {};
+static void set_signal_observed(int sig, bool val)
+{
+ ASSERT_IS_MAIN_THREAD();
+ if (sig >= 0 && (size_t)sig < sizeof s_observed_signals / sizeof *s_observed_signals)
+ {
+ s_observed_signals[sig] = val;
+ }
+}
+
+/**
+ Tests if one event instance matches the definition of a event
+ class. If both the class and the instance name a function,
+ they must name the same function.
+
+*/
+static int event_match(const event_t &classv, const event_t &instance)
+{
+
+ /* If the function names are both non-empty and different, then it's not a match */
+ if (! classv.function_name.empty() &&
+ ! instance.function_name.empty() &&
+ classv.function_name != instance.function_name)
+ {
+ return 0;
+ }
+
+ if (classv.type == EVENT_ANY)
+ return 1;
+
+ if (classv.type != instance.type)
+ return 0;
+
+
+ switch (classv.type)
+ {
+
+ case EVENT_SIGNAL:
+ if (classv.param1.signal == EVENT_ANY_SIGNAL)
+ return 1;
+ return classv.param1.signal == instance.param1.signal;
+
+ case EVENT_VARIABLE:
+ return instance.str_param1 == classv.str_param1;
+
+ case EVENT_EXIT:
+ if (classv.param1.pid == EVENT_ANY_PID)
+ return 1;
+ return classv.param1.pid == instance.param1.pid;
+
+ case EVENT_JOB_ID:
+ return classv.param1.job_id == instance.param1.job_id;
+
+ case EVENT_GENERIC:
+ return instance.str_param1 == classv.str_param1;
+
+ }
+
+ /**
+ This should never be reached
+ */
+ debug(0, "Warning: Unreachable code reached in event_match in event.cpp\n");
+ return 0;
+}
+
+
+
+/**
+ Test if specified event is blocked
+*/
+static int event_is_blocked(const event_t &e)
+{
+ const block_t *block;
+ parser_t &parser = parser_t::principal_parser();
+
+ size_t idx = 0;
+ while ((block = parser.block_at_index(idx++)))
+ {
+ if (event_block_list_blocks_type(block->event_blocks, e.type))
+ return true;
+
+ }
+ return event_block_list_blocks_type(parser.global_event_blocks, e.type);
+}
+
+wcstring event_get_desc(const event_t &e)
+{
+
+ wcstring result;
+ switch (e.type)
+ {
+
+ case EVENT_SIGNAL:
+ result = format_string(_(L"signal handler for %ls (%ls)"), sig2wcs(e.param1.signal), signal_get_desc(e.param1.signal));
+ break;
+
+ case EVENT_VARIABLE:
+ result = format_string(_(L"handler for variable '%ls'"), e.str_param1.c_str());
+ break;
+
+ case EVENT_EXIT:
+ if (e.param1.pid > 0)
+ {
+ result = format_string(_(L"exit handler for process %d"), e.param1.pid);
+ }
+ else
+ {
+ job_t *j = job_get_from_pid(-e.param1.pid);
+ if (j)
+ result = format_string(_(L"exit handler for job %d, '%ls'"), j->job_id, j->command_wcstr());
+ else
+ result = format_string(_(L"exit handler for job with process group %d"), -e.param1.pid);
+ }
+
+ break;
+
+ case EVENT_JOB_ID:
+ {
+ job_t *j = job_get(e.param1.job_id);
+ if (j)
+ result = format_string(_(L"exit handler for job %d, '%ls'"), j->job_id, j->command_wcstr());
+ else
+ result = format_string(_(L"exit handler for job with job id %d"), e.param1.job_id);
+
+ break;
+ }
+
+ case EVENT_GENERIC:
+ result = format_string(_(L"handler for generic event '%ls'"), e.str_param1.c_str());
+ break;
+
+ default:
+ result = format_string(_(L"Unknown event type '0x%x'"), e.type);
+ break;
+
+ }
+
+ return result;
+}
+
+#if 0
+static void show_all_handlers(void)
+{
+ puts("event handlers:");
+ for (event_list_t::const_iterator iter = events.begin(); iter != events.end(); ++iter)
+ {
+ const event_t *foo = *iter;
+ wcstring tmp = event_get_desc(foo);
+ printf(" handler now %ls\n", tmp.c_str());
+ }
+}
+#endif
+
+/*
+ Give a more condensed description of \c event compared to \c event_get_desc.
+ It includes what function will fire if the \c event is an event handler.
+ */
+static wcstring event_desc_compact(const event_t &event)
+{
+ wcstring res;
+ wchar_t const *temp;
+ int sig;
+ switch (event.type)
+ {
+ case EVENT_ANY:
+ res = L"EVENT_ANY";
+ break;
+ case EVENT_VARIABLE:
+ if (event.str_param1.c_str())
+ {
+ res = format_string(L"EVENT_VARIABLE($%ls)", event.str_param1.c_str());
+ }
+ else
+ {
+ res = L"EVENT_VARIABLE([any])";
+ }
+ break;
+ case EVENT_SIGNAL:
+ sig = event.param1.signal;
+ if (sig == EVENT_ANY_SIGNAL)
+ {
+ temp = L"[all signals]";
+ }
+ else if (sig == 0)
+ {
+ temp = L"not set";
+ }
+ else
+ {
+ temp = sig2wcs(sig);
+ }
+ res = format_string(L"EVENT_SIGNAL(%ls)", temp);
+ break;
+ case EVENT_EXIT:
+ if (event.param1.pid == EVENT_ANY_PID)
+ {
+ res = wcstring(L"EVENT_EXIT([all child processes])");
+ }
+ else if (event.param1.pid > 0)
+ {
+ res = format_string(L"EVENT_EXIT(pid %d)", event.param1.pid);
+ }
+ else
+ {
+ job_t *j = job_get_from_pid(-event.param1.pid);
+ if (j)
+ res = format_string(L"EVENT_EXIT(jobid %d: \"%ls\")", j->job_id, j->command_wcstr());
+ else
+ res = format_string(L"EVENT_EXIT(pgid %d)", -event.param1.pid);
+ }
+ break;
+ case EVENT_JOB_ID:
+ {
+ job_t *j = job_get(event.param1.job_id);
+ if (j)
+ res = format_string(L"EVENT_JOB_ID(job %d: \"%ls\")", j->job_id, j->command_wcstr());
+ else
+ res = format_string(L"EVENT_JOB_ID(jobid %d)", event.param1.job_id);
+ break;
+ }
+ case EVENT_GENERIC:
+ res = format_string(L"EVENT_GENERIC(%ls)", event.str_param1.c_str());
+ break;
+ default:
+ res = format_string(L"unknown/illegal event(%x)", event.type);
+ }
+ if (event.function_name.size())
+ {
+ return format_string(L"%ls: \"%ls\"", res.c_str(), event.function_name.c_str());
+ }
+ else
+ {
+ return res;
+ }
+}
+
+
+void event_add_handler(const event_t &event)
+{
+ event_t *e;
+
+ if (debug_level >= 3)
+ {
+ wcstring desc = event_desc_compact(event);
+ debug(3, "register: %ls\n", desc.c_str());
+ }
+
+ e = new event_t(event);
+
+ if (e->type == EVENT_SIGNAL)
+ {
+ signal_handle(e->param1.signal, 1);
+ set_signal_observed(e->param1.signal, true);
+ }
+
+ s_event_handlers.push_back(e);
+}
+
+void event_remove(const event_t &criterion)
+{
+ event_list_t new_list;
+
+ if (debug_level >= 3)
+ {
+ wcstring desc = event_desc_compact(criterion);
+ debug(3, "unregister: %ls\n", desc.c_str());
+ }
+
+ /*
+ Because of concurrency issues (env_remove could remove an event
+ that is currently being executed), env_remove does not actually
+ free any events - instead it simply moves all events that should
+ be removed from the event list to the killme list, and the ones
+ that shouldn't be killed to new_list, and then drops the empty
+ events-list.
+ */
+
+ if (s_event_handlers.empty())
+ return;
+
+ for (size_t i=0; i<s_event_handlers.size(); i++)
+ {
+ event_t *n = s_event_handlers.at(i);
+ if (event_match(criterion, *n))
+ {
+ killme.push_back(n);
+
+ /*
+ If this event was a signal handler and no other handler handles
+ the specified signal type, do not handle that type of signal any
+ more.
+ */
+ if (n->type == EVENT_SIGNAL)
+ {
+ event_t e = event_t::signal_event(n->param1.signal);
+ if (event_get(e, 0) == 1)
+ {
+ signal_handle(e.param1.signal, 0);
+ set_signal_observed(e.param1.signal, 0);
+ }
+ }
+ }
+ else
+ {
+ new_list.push_back(n);
+ }
+ }
+ s_event_handlers.swap(new_list);
+}
+
+int event_get(const event_t &criterion, std::vector<event_t *> *out)
+{
+ int found = 0;
+ for (size_t i=0; i < s_event_handlers.size(); i++)
+ {
+ event_t *n = s_event_handlers.at(i);
+ if (event_match(criterion, *n))
+ {
+ found++;
+ if (out)
+ out->push_back(n);
+ }
+ }
+ return found;
+}
+
+bool event_is_signal_observed(int sig)
+{
+ /* We are in a signal handler! Don't allocate memory, etc.
+ */
+ bool result = false;
+ if (sig >= 0 && sig < sizeof s_observed_signals / sizeof *s_observed_signals)
+ {
+ result = s_observed_signals[sig];
+ }
+ return result;
+}
+
+/**
+ Free all events in the kill list
+*/
+static void event_free_kills()
+{
+ for_each(killme.begin(), killme.end(), event_free);
+ killme.resize(0);
+}
+
+/**
+ Test if the specified event is waiting to be killed
+*/
+static int event_is_killed(const event_t &e)
+{
+ return std::find(killme.begin(), killme.end(), &e) != killme.end();
+}
+
+/* Callback for firing (and then deleting) an event */
+static void fire_event_callback(void *arg)
+{
+ ASSERT_IS_MAIN_THREAD();
+ assert(arg != NULL);
+ event_t *event = static_cast<event_t *>(arg);
+ event_fire(event);
+ delete event;
+}
+
+/**
+ Perform the specified event. Since almost all event firings will
+ not be matched by even a single event handler, we make sure to
+ optimize the 'no matches' path. This means that nothing is
+ allocated/initialized unless needed.
+*/
+static void event_fire_internal(const event_t &event)
+{
+
+ event_list_t fire;
+
+ /*
+ First we free all events that have been removed, but only if this
+ invocation of event_fire_internal is not a recursive call.
+ */
+ if (is_event <= 1)
+ event_free_kills();
+
+ if (s_event_handlers.empty())
+ return;
+
+ /*
+ Then we iterate over all events, adding events that should be
+ fired to a second list. We need to do this in a separate step
+ since an event handler might call event_remove or
+ event_add_handler, which will change the contents of the \c
+ events list.
+ */
+ for (size_t i=0; i<s_event_handlers.size(); i++)
+ {
+ event_t *criterion = s_event_handlers.at(i);
+
+ /*
+ Check if this event is a match
+ */
+ if (event_match(*criterion, event))
+ {
+ fire.push_back(criterion);
+ }
+ }
+
+ /*
+ No matches. Time to return.
+ */
+ if (fire.empty())
+ return;
+
+ if (signal_is_blocked())
+ {
+ /* Fix for https://github.com/fish-shell/fish-shell/issues/608. Don't run event handlers while signals are blocked. */
+ event_t *heap_event = new event_t(event);
+ input_common_add_callback(fire_event_callback, heap_event);
+ return;
+ }
+
+ /*
+ Iterate over our list of matching events
+ */
+
+ for (size_t i=0; i<fire.size(); i++)
+ {
+ event_t *criterion = fire.at(i);
+ int prev_status;
+
+ /*
+ Check if this event has been removed, if so, dont fire it
+ */
+ if (event_is_killed(*criterion))
+ continue;
+
+ /*
+ Fire event
+ */
+ wcstring buffer = criterion->function_name;
+
+ for (size_t j=0; j < event.arguments.size(); j++)
+ {
+ wcstring arg_esc = escape_string(event.arguments.at(j), 1);
+ buffer += L" ";
+ buffer += arg_esc;
+ }
+
+ // debug( 1, L"Event handler fires command '%ls'", buffer.c_str() );
+
+ /*
+ Event handlers are not part of the main flow of code, so
+ they are marked as non-interactive
+ */
+ proc_push_interactive(0);
+ prev_status = proc_get_last_status();
+ parser_t &parser = parser_t::principal_parser();
+
+ block_t *block = new event_block_t(event);
+ parser.push_block(block);
+ parser.eval(buffer, io_chain_t(), TOP);
+ parser.pop_block();
+ proc_pop_interactive();
+ proc_set_last_status(prev_status);
+ }
+
+ /*
+ Free killed events
+ */
+ if (is_event <= 1)
+ event_free_kills();
+
+}
+
+/**
+ Handle all pending signal events
+*/
+static void event_fire_delayed()
+{
+ /*
+ If is_event is one, we are running the event-handler non-recursively.
+
+ When the event handler has called a piece of code that triggers
+ another event, we do not want to fire delayed events because of
+ concurrency problems.
+ */
+ if (! blocked.empty() && is_event==1)
+ {
+ event_list_t new_blocked;
+
+ for (size_t i=0; i<blocked.size(); i++)
+ {
+ event_t *e = blocked.at(i);
+ if (event_is_blocked(*e))
+ {
+ new_blocked.push_back(new event_t(*e));
+ }
+ else
+ {
+ event_fire_internal(*e);
+ event_free(e);
+ }
+ }
+ blocked.swap(new_blocked);
+ }
+
+ int al = active_list;
+
+ while (sig_list[al].count > 0)
+ {
+ signal_list_t *lst;
+
+ /*
+ Switch signal lists
+ */
+ sig_list[1-al].count=0;
+ sig_list[1-al].overflow=0;
+ al = 1-al;
+ active_list=al;
+
+ /*
+ Set up
+ */
+ lst = &sig_list[1-al];
+ event_t e = event_t::signal_event(0);
+ e.arguments.resize(1);
+
+ if (lst->overflow)
+ {
+ debug(0, _(L"Signal list overflow. Signals have been ignored."));
+ }
+
+ /*
+ Send all signals in our private list
+ */
+ for (int i=0; i < lst->count; i++)
+ {
+ e.param1.signal = lst->signal[i];
+ e.arguments.at(0) = sig2wcs(e.param1.signal);
+ if (event_is_blocked(e))
+ {
+ blocked.push_back(new event_t(e));
+ }
+ else
+ {
+ event_fire_internal(e);
+ }
+ }
+
+ }
+}
+
+void event_fire_signal(int signal)
+{
+ /*
+ This means we are in a signal handler. We must be very
+ careful not do do anything that could cause a memory
+ allocation or something else that might be bad when in a
+ signal handler.
+ */
+ if (sig_list[active_list].count < SIG_UNHANDLED_MAX)
+ sig_list[active_list].signal[sig_list[active_list].count++]=signal;
+ else
+ sig_list[active_list].overflow=1;
+}
+
+
+void event_fire(const event_t *event)
+{
+
+ if (event && event->type == EVENT_SIGNAL)
+ {
+ event_fire_signal(event->param1.signal);
+ }
+ else
+ {
+ is_event++;
+
+ /*
+ Fire events triggered by signals
+ */
+ event_fire_delayed();
+
+ if (event)
+ {
+ if (event_is_blocked(*event))
+ {
+ blocked.push_back(new event_t(*event));
+ }
+ else
+ {
+ event_fire_internal(*event);
+ }
+ }
+ is_event--;
+ }
+}
+
+
+void event_init()
+{
+}
+
+void event_destroy()
+{
+
+ for_each(s_event_handlers.begin(), s_event_handlers.end(), event_free);
+ s_event_handlers.clear();
+
+ for_each(killme.begin(), killme.end(), event_free);
+ killme.clear();
+}
+
+void event_free(event_t *e)
+{
+ CHECK(e,);
+ delete e;
+}
+
+void event_fire_generic(const wchar_t *name, wcstring_list_t *args)
+{
+ CHECK(name,);
+
+ event_t ev(EVENT_GENERIC);
+ ev.str_param1 = name;
+ if (args)
+ ev.arguments = *args;
+ event_fire(&ev);
+}
+
+event_t::event_t(int t) : type(t), param1(), str_param1(), function_name(), arguments()
+{
+}
+
+event_t::~event_t()
+{
+}
+
+event_t event_t::signal_event(int sig)
+{
+ event_t event(EVENT_SIGNAL);
+ event.param1.signal = sig;
+ return event;
+}
+
+event_t event_t::variable_event(const wcstring &str)
+{
+ event_t event(EVENT_VARIABLE);
+ event.str_param1 = str;
+ return event;
+}
+
+event_t event_t::generic_event(const wcstring &str)
+{
+ event_t event(EVENT_GENERIC);
+ event.str_param1 = str;
+ return event;
+}
+