diff options
-rw-r--r-- | extension/ChangeLog | 10 | ||||
-rw-r--r-- | extension/select.c | 210 | ||||
-rw-r--r-- | extension/siglist.h | 75 |
3 files changed, 285 insertions, 10 deletions
diff --git a/extension/ChangeLog b/extension/ChangeLog index 37cfccf2..e4dcf009 100644 --- a/extension/ChangeLog +++ b/extension/ChangeLog @@ -1,3 +1,13 @@ +2013-07-01 Andrew J. Schorr <aschorr@telemetry-investments.com> + + * siglist.h: New file copied from glibc to provide a mapping between + signal number and name. + * select.c: Add a new "select_signal" function and provide support + for trapping signals. + (do_select): Add support for a 5th argument to contain an array + of returned signals. Improve the argument processing, and add + better warning messages. + 2013-06-30 Andrew J. Schorr <aschorr@telemetry-investments.com> * Makefile.am (pkgextension_LTLIBRARIES): Add select.la. diff --git a/extension/select.c b/extension/select.c index f74ae250..8729a59e 100644 --- a/extension/select.c +++ b/extension/select.c @@ -58,7 +58,138 @@ int plugin_is_GPL_compatible; #include <signal.h> #endif -/* do_ord --- return numeric value of first char of string */ +static const char *const signum2name[] = { +#define init_sig(A, B, C) [A] = B, +#include "siglist.h" +#undef init_sig +}; +#define NUMSIG sizeof(signum2name)/sizeof(signum2name[0]) + +#define MIN_VALID_SIGNAL 1 /* 0 is not allowed! */ +/* + * We would like to use NSIG, but I think this seems to be a BSD'ism that is not + * POSIX-compliant. It is used internally by glibc, but not always + * available. We add a buffer to the maximum number in the provided mapping + * in case the list is not comprehensive: + */ +#define MAX_VALID_SIGNAL (NUMSIG+100) +#define IS_VALID_SIGNAL(X) \ + (((X) >= MIN_VALID_SIGNAL) && ((X) <= MAX_VALID_SIGNAL)) + +static int +signame2num(const char *name) +{ + size_t i; + + if (strncasecmp(name, "sig", 3) == 0) + /* skip "sig" prefix */ + name += 3; + for (i = MIN_VALID_SIGNAL; i < NUMSIG; i++) { + if (signum2name[i] && ! strcasecmp(signum2name[i], name)) + return i; + } + return -1; +} + +static volatile struct { + int flag; + sigset_t mask; +} caught; + +static void +signal_handler(int signum) +{ + /* + * All signals should be blocked, so we do not have to worry about + * whether sigaddset is thread-safe. It is documented to be + * async-signal-safe. + */ + sigaddset(& caught.mask, signum); + caught.flag = 1; +} + +static int +integer_string(const char *s, long *x) +{ + char *endptr; + + *x = strtol(s, & endptr, 10); + return ((endptr != s) && (*endptr == '\0')) ? 0 : -1; +} + +static int +get_signal_number(awk_value_t signame) +{ + int x; + + switch (signame.val_type) { + case AWK_NUMBER: + x = signame.num_value; + if ((x != signame.num_value) || ! IS_VALID_SIGNAL(x)) { + update_ERRNO_string(_("select_signal: invalid signal number")); + return -1; + } + return x; + case AWK_STRING: + if ((x = signame2num(signame.str_value.str)) >= 0) + return x; + { + long z; + if ((integer_string(signame.str_value.str, &z) == 0) && IS_VALID_SIGNAL(z)) + return z; + } + update_ERRNO_string(_("select_signal: invalid signal name")); + return -1; + default: + update_ERRNO_string(_("select_signal: signal name argument must be string or numeric")); + return -1; + } +} + +/* do_signal --- trap signals */ + +static awk_value_t * +do_signal(int nargs, awk_value_t *result) +{ +#ifdef HAVE_SIGACTION + awk_value_t signame, disposition; + int signum; + struct sigaction sa; + + if (! get_argument(0, AWK_UNDEFINED, & signame)) { + update_ERRNO_string(_("select_signal: missing required signal name argument")); + return make_number(-1, result); + } + if ((signum = get_signal_number(signame)) < 0) + return make_number(-1, result); + if (! get_argument(1, AWK_STRING, & disposition)) { + update_ERRNO_string(_("select_signal: missing required signal disposition argument")); + return make_number(-1, result); + } + if (strcasecmp(disposition.str_value.str, "default") == 0) + sa.sa_handler = SIG_DFL; + else if (strcasecmp(disposition.str_value.str, "ignore") == 0) + sa.sa_handler = SIG_IGN; + else if (strcasecmp(disposition.str_value.str, "trap") == 0) + sa.sa_handler = signal_handler; + else { + update_ERRNO_string(_("select_signal: invalid disposition argument")); + return make_number(-1, result); + } + sigfillset(& sa.sa_mask); /* block all signals in handler */ + sa.sa_flags = SA_RESTART; + if (sigaction(signum, &sa, NULL) < 0) { + update_ERRNO_int(errno); + return make_number(-1, result); + } + return make_number(0, result); +#else + update_ERRNO_string(_("select_signal: not supported on this platform")); + return make_number(-1, result); +#endif +} + +/* do_select --- I/O multiplexing */ static awk_value_t * do_select(int nargs, awk_value_t *result) @@ -76,11 +207,23 @@ do_select(int nargs, awk_value_t *result) struct timeval *timeout; int nfds = 0; int rc; + awk_value_t sigarr; + int dosig = 0; if (do_lint && nargs > 5) lintwarn(ext_id, _("select: called with too many arguments")); #define EL fds[i].flat->elements[j] + if (nargs == 5) { + dosig = 1; + if (! get_argument(4, AWK_ARRAY, &sigarr)) { + warning(ext_id, _("select: the signal argument must be an array")); + update_ERRNO_string(_("select: bad signal parameter")); + return make_number(-1, result); + } + clear_array(sigarr.array_cookie); + } + for (i = 0; i < sizeof(fds)/sizeof(fds[0]); i++) { size_t j; @@ -99,19 +242,33 @@ do_select(int nargs, awk_value_t *result) case AWK_NUMBER: if (EL.index.num_value >= 0) fds[i].array2fd[j] = EL.index.num_value; - if (fds[i].array2fd[j] != EL.index.num_value) + if (fds[i].array2fd[j] != EL.index.num_value) { fds[i].array2fd[j] = -1; + warning(ext_id, _("select: invalid numeric index `%g' in `%s' array (should be a non-negative integer)"), EL.index.num_value, argname[i]); + } break; case AWK_STRING: - if (EL.value.val_type == AWK_STRING) { - const awk_input_buf_t *buf; - if ((buf = get_file(EL.index.str_value.str, EL.index.str_value.len, EL.value.str_value.str, EL.value.str_value.len)) != NULL) - fds[i].array2fd[j] = buf->fd; + { + long x; + + if ((integer_string(EL.index.str_value.str, &x) == 0) && (x >= 0)) + fds[i].array2fd[j] = x; + else if (EL.value.val_type == AWK_STRING) { + const awk_input_buf_t *buf; + if ((buf = get_file(EL.index.str_value.str, EL.index.str_value.len, EL.value.str_value.str, EL.value.str_value.len)) != NULL) + fds[i].array2fd[j] = buf->fd; + else + warning(ext_id, _("select: get_file(`%s', `%s') failed in `%s' array"), EL.index.str_value.str, EL.value.str_value.str, argname[i]); + } + else + warning(ext_id, _("select: command type should be a string for `%s' in `%s' array"), EL.index.str_value.str, argname[i]); } break; + default: + warning(ext_id, _("select: invalid index type in `%s' array (must be a string or a non-negative integer"), argname[i]); + break; } if (fds[i].array2fd[j] < 0) { - warning(ext_id, _("select: get_file failed")); update_ERRNO_string(_("select: get_file failed")); if (! release_flattened_array(fds[i].array.array_cookie, fds[i].flat)) warning(ext_id, _("select: release_flattened_array failed")); @@ -126,7 +283,12 @@ do_select(int nargs, awk_value_t *result) else fds[i].flat = NULL; } - if (get_argument(3, AWK_NUMBER, &timeout_arg)) { + if (dosig && caught.flag) { + /* take a quick poll, but do not block, since signals have been trapped */ + maxwait.tv_sec = maxwait.tv_usec = 0; + timeout = &maxwait; + } + else if (get_argument(3, AWK_NUMBER, &timeout_arg)) { double secs = timeout_arg.num_value; if (secs < 0) { warning(ext_id, _("select: treating negative timeout as zero")); @@ -137,10 +299,37 @@ do_select(int nargs, awk_value_t *result) timeout = &maxwait; } else timeout = NULL; - rc = select(nfds, &fds[0].bits, &fds[1].bits, &fds[2].bits, timeout); - if (rc < 0) { + if ((rc = select(nfds, &fds[0].bits, &fds[1].bits, &fds[2].bits, timeout)) < 0) update_ERRNO_int(errno); + + if (dosig && caught.flag) { + int i; + sigset_t set, oldset, trapped; + sigfillset(& set); + sigprocmask(SIG_SETMASK, &set, &oldset); + trapped = caught.mask; + sigemptyset(& caught.mask); + caught.flag = 0; + sigprocmask(SIG_SETMASK, &oldset, NULL); + /* populate sigarr with trapped signals */ + /* + * XXX this is very inefficient! Note that get_signal_number + * ensures that we trap only signals between MIN_VALID_SIGNAL + * and MAX_VALID_SIGNAL. + */ + for (i = MIN_VALID_SIGNAL; i <= MAX_VALID_SIGNAL; i++) { + if (sigismember(& trapped, i) > 0) { + awk_value_t idx, val; + if ((i < NUMSIG) && signum2name[i]) + set_array_element(sigarr.array_cookie, make_number(i, &idx), make_const_string(signum2name[i], strlen(signum2name[i]), &val)); + else + set_array_element(sigarr.array_cookie, make_number(i, &idx), make_null_string(&val)); + } + } + } + + if (rc < 0) { /* bit masks are undefined, so delete all array entries */ for (i = 0; i < sizeof(fds)/sizeof(fds[0]); i++) { if (fds[i].flat) { @@ -176,6 +365,7 @@ do_select(int nargs, awk_value_t *result) static awk_ext_func_t func_table[] = { { "select", do_select, 5 }, + { "select_signal", do_signal, 2 }, }; /* define the dl_load function using the boilerplate macro */ diff --git a/extension/siglist.h b/extension/siglist.h new file mode 100644 index 00000000..dacd4a1f --- /dev/null +++ b/extension/siglist.h @@ -0,0 +1,75 @@ +/* Canonical list of all signal names. + Copyright (C) 1996,97,98,99 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +/* This file should be usable for any platform, since it just associates + the SIG* macros with text names and descriptions. The actual values + come from <bits/signum.h> (via <signal.h>). For any signal macros do not + exist on every platform, we can use #ifdef tests here and still use + this single common file for all platforms. */ + +/* This file is included multiple times. */ + +/* Standard signals */ + init_sig (SIGHUP, "HUP", N_("Hangup")) + init_sig (SIGINT, "INT", N_("Interrupt")) + init_sig (SIGQUIT, "QUIT", N_("Quit")) + init_sig (SIGILL, "ILL", N_("Illegal instruction")) + init_sig (SIGTRAP, "TRAP", N_("Trace/breakpoint trap")) + init_sig (SIGABRT, "ABRT", N_("Aborted")) + init_sig (SIGFPE, "FPE", N_("Floating point exception")) + init_sig (SIGKILL, "KILL", N_("Killed")) + init_sig (SIGBUS, "BUS", N_("Bus error")) + init_sig (SIGSEGV, "SEGV", N_("Segmentation fault")) + init_sig (SIGPIPE, "PIPE", N_("Broken pipe")) + init_sig (SIGALRM, "ALRM", N_("Alarm clock")) + init_sig (SIGTERM, "TERM", N_("Terminated")) + init_sig (SIGURG, "URG", N_("Urgent I/O condition")) + init_sig (SIGSTOP, "STOP", N_("Stopped (signal)")) + init_sig (SIGTSTP, "TSTP", N_("Stopped")) + init_sig (SIGCONT, "CONT", N_("Continued")) + init_sig (SIGCHLD, "CHLD", N_("Child exited")) + init_sig (SIGTTIN, "TTIN", N_("Stopped (tty input)")) + init_sig (SIGTTOU, "TTOU", N_("Stopped (tty output)")) + init_sig (SIGIO, "IO", N_("I/O possible")) + init_sig (SIGXCPU, "XCPU", N_("CPU time limit exceeded")) + init_sig (SIGXFSZ, "XFSZ", N_("File size limit exceeded")) + init_sig (SIGVTALRM, "VTALRM", N_("Virtual timer expired")) + init_sig (SIGPROF, "PROF", N_("Profiling timer expired")) + init_sig (SIGWINCH, "WINCH", N_("Window changed")) + init_sig (SIGUSR1, "USR1", N_("User defined signal 1")) + init_sig (SIGUSR2, "USR2", N_("User defined signal 2")) + +/* Variations */ +#ifdef SIGEMT + init_sig (SIGEMT, "EMT", N_("EMT trap")) +#endif +#ifdef SIGSYS + init_sig (SIGSYS, "SYS", N_("Bad system call")) +#endif +#ifdef SIGSTKFLT + init_sig (SIGSTKFLT, "STKFLT", N_("Stack fault")) +#endif +#ifdef SIGINFO + init_sig (SIGINFO, "INFO", N_("Information request")) +#elif defined(SIGPWR) && (!defined(SIGLOST) || (SIGPWR != SIGLOST)) + init_sig (SIGPWR, "PWR", N_("Power failure")) +#endif +#ifdef SIGLOST + init_sig (SIGLOST, "LOST", N_("Resource lost")) +#endif |