diff options
Diffstat (limited to 'jim-signal.c')
-rw-r--r-- | jim-signal.c | 528 |
1 files changed, 528 insertions, 0 deletions
diff --git a/jim-signal.c b/jim-signal.c new file mode 100644 index 0000000..b760ede --- /dev/null +++ b/jim-signal.c @@ -0,0 +1,528 @@ +/* + * jim-signal.c + * + */ + +#include <signal.h> +#include <string.h> +#include <ctype.h> + +#include "jimautoconf.h" +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif +#include <jim-subcmd.h> +#include <jim-signal.h> + +#define MAX_SIGNALS_WIDE (sizeof(jim_wide) * 8) +#if defined(NSIG) + #define MAX_SIGNALS ((NSIG < MAX_SIGNALS_WIDE) ? NSIG : MAX_SIGNALS_WIDE) +#else + #define MAX_SIGNALS MAX_SIGNALS_WIDE +#endif + +static jim_wide *sigloc; +static jim_wide sigsblocked; +static struct sigaction *sa_old; +static struct { + int status; + const char *name; +} siginfo[MAX_SIGNALS]; + +/* Make sure to do this as a wide, not int */ +#define sig_to_bit(SIG) ((jim_wide)1 << (SIG)) + +static void signal_handler(int sig) +{ + /* We just remember which signals occurred. Jim_Eval() will + * notice this as soon as it can and throw an error + */ + *sigloc |= sig_to_bit(sig); +} + +static void signal_ignorer(int sig) +{ + /* We just remember which signals occurred */ + sigsblocked |= sig_to_bit(sig); +} + +static void signal_init_names(void) +{ +#define SET_SIG_NAME(SIG) siginfo[SIG].name = #SIG + + SET_SIG_NAME(SIGABRT); + SET_SIG_NAME(SIGALRM); + SET_SIG_NAME(SIGBUS); + SET_SIG_NAME(SIGCHLD); + SET_SIG_NAME(SIGCONT); + SET_SIG_NAME(SIGFPE); + SET_SIG_NAME(SIGHUP); + SET_SIG_NAME(SIGILL); + SET_SIG_NAME(SIGINT); +#ifdef SIGIO + SET_SIG_NAME(SIGIO); +#endif + SET_SIG_NAME(SIGKILL); + SET_SIG_NAME(SIGPIPE); + SET_SIG_NAME(SIGPROF); + SET_SIG_NAME(SIGQUIT); + SET_SIG_NAME(SIGSEGV); + SET_SIG_NAME(SIGSTOP); + SET_SIG_NAME(SIGSYS); + SET_SIG_NAME(SIGTERM); + SET_SIG_NAME(SIGTRAP); + SET_SIG_NAME(SIGTSTP); + SET_SIG_NAME(SIGTTIN); + SET_SIG_NAME(SIGTTOU); + SET_SIG_NAME(SIGURG); + SET_SIG_NAME(SIGUSR1); + SET_SIG_NAME(SIGUSR2); + SET_SIG_NAME(SIGVTALRM); + SET_SIG_NAME(SIGWINCH); + SET_SIG_NAME(SIGXCPU); + SET_SIG_NAME(SIGXFSZ); +#ifdef SIGPWR + SET_SIG_NAME(SIGPWR); +#endif +#ifdef SIGCLD + SET_SIG_NAME(SIGCLD); +#endif +#ifdef SIGEMT + SET_SIG_NAME(SIGEMT); +#endif +#ifdef SIGLOST + SET_SIG_NAME(SIGLOST); +#endif +#ifdef SIGPOLL + SET_SIG_NAME(SIGPOLL); +#endif +#ifdef SIGINFO + SET_SIG_NAME(SIGINFO); +#endif +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_SignalId -- + * + * Return a textual identifier for a signal number. + * + * Results: + * This procedure returns a machine-readable textual identifier + * that corresponds to sig. The identifier is the same as the + * #define name in signal.h. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ +const char *Jim_SignalId(int sig) +{ + if (sig >=0 && sig < MAX_SIGNALS) { + if (siginfo[sig].name) { + return siginfo[sig].name; + } + } + return "unknown signal"; +} + +const char *Jim_SignalName(int sig) +{ +#ifdef HAVE_SYS_SIGLIST + if (sig >= 0 && sig < NSIG) { + return sys_siglist[sig]; + } +#endif + return Jim_SignalId(sig); +} + +/** + * Given the name of a signal, returns the signal value if found, + * or returns -1 (and sets an error) if not found. + * We accept -SIGINT, SIGINT, INT or any lowercase version or a number, + * either positive or negative. + */ +static int find_signal_by_name(Jim_Interp *interp, const char *name) +{ + int i; + const char *pt = name; + + /* Remove optional - and SIG from the front of the name */ + if (*pt == '-') { + pt++; + } + if (strncasecmp(name, "sig", 3) == 0) { + pt += 3; + } + if (isdigit(UCHAR(pt[0]))) { + i = atoi(pt); + if (i > 0 && i < MAX_SIGNALS) { + return i; + } + } + else { + for (i = 1; i < MAX_SIGNALS; i++) { + /* Jim_SignalId() returns names such as SIGINT, and + * returns "unknown signal" if unknown, so this will work + */ + if (strcasecmp(Jim_SignalId(i) + 3, pt) == 0) { + return i; + } + } + } + Jim_SetResultFormatted(interp, "unknown signal %s", name); + + return -1; +} + +#define SIGNAL_ACTION_HANDLE 1 +#define SIGNAL_ACTION_IGNORE -1 +#define SIGNAL_ACTION_DEFAULT 0 + +static int do_signal_cmd(Jim_Interp *interp, int action, int argc, Jim_Obj *const *argv) +{ + struct sigaction sa; + int i; + + if (argc == 0) { + Jim_SetResult(interp, Jim_NewListObj(interp, NULL, 0)); + for (i = 1; i < MAX_SIGNALS; i++) { + if (siginfo[i].status == action) { + /* Add signal name to the list */ + Jim_ListAppendElement(interp, Jim_GetResult(interp), + Jim_NewStringObj(interp, Jim_SignalId(i), -1)); + } + } + return JIM_OK; + } + + /* Catch all the signals we care about */ + if (action != SIGNAL_ACTION_DEFAULT) { + memset(&sa, 0, sizeof(sa)); + if (action == SIGNAL_ACTION_HANDLE) { + sa.sa_handler = signal_handler; + } + else { + sa.sa_handler = signal_ignorer; + } + } + + /* Iterate through the provided signals */ + for (i = 0; i < argc; i++) { + int sig = find_signal_by_name(interp, Jim_String(argv[i])); + + if (sig < 0) { + return JIM_ERR; + } + if (action != siginfo[sig].status) { + /* Need to change the action for this signal */ + switch (action) { + case SIGNAL_ACTION_HANDLE: + case SIGNAL_ACTION_IGNORE: + if (siginfo[sig].status == SIGNAL_ACTION_DEFAULT) { + if (!sa_old) { + /* Allocate the structure the first time through */ + sa_old = Jim_Alloc(sizeof(*sa_old) * MAX_SIGNALS); + } + sigaction(sig, &sa, &sa_old[sig]); + } + else { + sigaction(sig, &sa, 0); + } + break; + + case SIGNAL_ACTION_DEFAULT: + /* Restore old handler */ + if (sa_old) { + sigaction(sig, &sa_old[sig], 0); + } + } + siginfo[sig].status = action; + } + } + + return JIM_OK; +} + +static int signal_cmd_handle(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + return do_signal_cmd(interp, SIGNAL_ACTION_HANDLE, argc, argv); +} + +static int signal_cmd_ignore(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + return do_signal_cmd(interp, SIGNAL_ACTION_IGNORE, argc, argv); +} + +static int signal_cmd_default(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + return do_signal_cmd(interp, SIGNAL_ACTION_DEFAULT, argc, argv); +} + +static int signal_set_sigmask_result(Jim_Interp *interp, jim_wide sigmask) +{ + int i; + Jim_Obj *listObj = Jim_NewListObj(interp, NULL, 0); + + for (i = 0; i < MAX_SIGNALS; i++) { + if (sigmask & sig_to_bit(i)) { + Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, Jim_SignalId(i), -1)); + } + } + Jim_SetResult(interp, listObj); + return JIM_OK; +} + +static int signal_cmd_check(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int clear = 0; + jim_wide mask = 0; + jim_wide blocked; + + if (argc > 0 && Jim_CompareStringImmediate(interp, argv[0], "-clear")) { + clear++; + } + if (argc > clear) { + int i; + + /* Signals specified */ + for (i = clear; i < argc; i++) { + int sig = find_signal_by_name(interp, Jim_String(argv[i])); + + if (sig < 0 || sig >= MAX_SIGNALS) { + return -1; + } + mask |= sig_to_bit(sig); + } + } + else { + /* No signals specified, so check/clear all */ + mask = ~mask; + } + + if ((sigsblocked & mask) == 0) { + /* No matching signals, so empty result and nothing to do */ + return JIM_OK; + } + /* Be careful we don't have a race condition where signals are cleared but not returned */ + blocked = sigsblocked & mask; + if (clear) { + sigsblocked &= ~blocked; + } + /* Set the result */ + signal_set_sigmask_result(interp, blocked); + return JIM_OK; +} + +static int signal_cmd_throw(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int sig = SIGINT; + + if (argc == 1) { + if ((sig = find_signal_by_name(interp, Jim_String(argv[0]))) < 0) { + return JIM_ERR; + } + } + + /* If the signal is ignored (blocked) ... */ + if (siginfo[sig].status == SIGNAL_ACTION_IGNORE) { + sigsblocked |= sig_to_bit(sig); + return JIM_OK; + } + + /* Just set the signal */ + interp->sigmask |= sig_to_bit(sig); + + /* Set the canonical name of the signal as the result */ + Jim_SetResultString(interp, Jim_SignalId(sig), -1); + + /* And simply say we caught the signal */ + return JIM_SIGNAL; +} + +/* + *----------------------------------------------------------------------------- + * + * Jim_SignalCmd -- + * Implements the TCL signal command: + * signal handle|ignore|default|throw ?signals ...? + * signal throw signal + * + * Specifies which signals are handled by Tcl code. + * If the one of the given signals is caught, it causes a JIM_SIGNAL + * exception to be thrown which can be caught by catch. + * + * Use 'signal ignore' to ignore the signal(s) + * Use 'signal default' to go back to the default behaviour + * Use 'signal throw signal' to raise the given signal + * + * If no arguments are given, returns the list of signals which are being handled + * + * Results: + * Standard TCL results. + * + *----------------------------------------------------------------------------- + */ +static const jim_subcmd_type signal_command_table[] = { + { "handle", + "?signals ...?", + signal_cmd_handle, + 0, + -1, + /* Description: Lists handled signals, or adds to handled signals */ + }, + { "ignore", + "?signals ...?", + signal_cmd_ignore, + 0, + -1, + /* Description: Lists ignored signals, or adds to ignored signals */ + }, + { "default", + "?signals ...?", + signal_cmd_default, + 0, + -1, + /* Description: Lists defaulted signals, or adds to defaulted signals */ + }, + { "check", + "?-clear? ?signals ...?", + signal_cmd_check, + 0, + -1, + /* Description: Returns ignored signals which have occurred, and optionally clearing them */ + }, + { "throw", + "?signal?", + signal_cmd_throw, + 0, + 1, + /* Description: Raises the given signal (default SIGINT) */ + }, + { NULL } +}; + +static int Jim_AlarmCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int ret; + + if (argc != 2) { + Jim_WrongNumArgs(interp, 1, argv, "seconds"); + return JIM_ERR; + } + else { +#ifdef HAVE_UALARM + double t; + + ret = Jim_GetDouble(interp, argv[1], &t); + if (ret == JIM_OK) { + if (t < 1) { + ualarm(t * 1e6, 0); + } + else { + alarm(t); + } + } +#else + long t; + + ret = Jim_GetLong(interp, argv[1], &t); + if (ret == JIM_OK) { + alarm(t); + } +#endif + } + + return ret; +} + +static int Jim_SleepCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int ret; + + if (argc != 2) { + Jim_WrongNumArgs(interp, 1, argv, "seconds"); + return JIM_ERR; + } + else { + double t; + + ret = Jim_GetDouble(interp, argv[1], &t); + if (ret == JIM_OK) { +#ifdef HAVE_USLEEP + usleep((int)((t - (int)t) * 1e6)); +#endif + sleep(t); + } + } + + return ret; +} + +static int Jim_KillCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv) +{ + int sig; + long pid; + Jim_Obj *pidObj; + const char *signame; + + if (argc != 2 && argc != 3) { + Jim_WrongNumArgs(interp, 1, argv, "?SIG|-0? pid"); + return JIM_ERR; + } + + if (argc == 2) { + sig = SIGTERM; + pidObj = argv[1]; + } + else { + signame = Jim_String(argv[1]); + pidObj = argv[2]; + + /* Special 'kill -0 pid' to determine if a pid exists */ + if (strcmp(signame, "-0") == 0 || strcmp(signame, "0") == 0) { + sig = 0; + } + else { + sig = find_signal_by_name(interp, signame); + if (sig < 0) { + return JIM_ERR; + } + } + } + + if (Jim_GetLong(interp, pidObj, &pid) != JIM_OK) { + return JIM_ERR; + } + + if (kill(pid, sig) == 0) { + return JIM_OK; + } + + Jim_SetResultString(interp, "kill: Failed to deliver signal", -1); + return JIM_ERR; +} + +int Jim_signalInit(Jim_Interp *interp) +{ + if (Jim_PackageProvide(interp, "signal", "1.0", JIM_ERRMSG)) + return JIM_ERR; + + signal_init_names(); + + /* Teach the jim core how to set a result from a sigmask */ + interp->signal_set_result = signal_set_sigmask_result; + + /* Make sure we know where to store the signals which occur */ + sigloc = &interp->sigmask; + + Jim_CreateCommand(interp, "signal", Jim_SubCmdProc, (void *)signal_command_table, NULL); + Jim_CreateCommand(interp, "alarm", Jim_AlarmCmd, 0, 0); + Jim_CreateCommand(interp, "kill", Jim_KillCmd, 0, 0); + + /* Sleep is slightly dubious here */ + Jim_CreateCommand(interp, "sleep", Jim_SleepCmd, 0, 0); + return JIM_OK; +} |