/* * jim-signal.c * */ #include #include #include #include "jimautoconf.h" #ifdef HAVE_UNISTD_H #include #endif #include #include #define MAX_SIGNALS_WIDE (sizeof(jim_wide) * 8) #if defined(NSIG) #define MAX_SIGNALS (int)((NSIG < MAX_SIGNALS_WIDE) ? NSIG : MAX_SIGNALS_WIDE) #else #define MAX_SIGNALS (int)MAX_SIGNALS_WIDE #endif static jim_wide *sigloc; static jim_wide sigsignored; 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) { /* Remember which signals occurred and store in *sigloc. * 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) { /* Remember which signals occurred for access by 'signal check' */ sigsignored |= 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"; } /** * 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_BLOCK -2 #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 if (action == SIGNAL_ACTION_IGNORE) { sa.sa_handler = signal_ignorer; } else { /* SIGNAL_ACTION_BLOCK */ sa.sa_handler = SIG_IGN; } } /* 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_BLOCK: 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_block(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { return do_signal_cmd(interp, SIGNAL_ACTION_BLOCK, 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 ignored; 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 JIM_ERR; } mask |= sig_to_bit(sig); } } else { /* No signals specified, so check/clear all */ mask = ~mask; } /* Be careful we don't have a race condition where signals are cleared but not returned */ ignored = sigsignored & mask; if (clear) { sigsignored &= ~ignored; } /* Set the result */ signal_set_sigmask_result(interp, ignored); 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 ... */ if (siginfo[sig].status == SIGNAL_ACTION_IGNORE) { sigsignored |= 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 */ }, { "block", "?signals ...?", signal_cmd_block, 0, -1, /* Description: Lists blocked signals, or adds to blocked 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 } }; /** * Restore default signal handling. */ static void JimSignalCmdDelete(Jim_Interp *interp, void *privData) { int i; if (sa_old) { for (i = 1; i < MAX_SIGNALS; i++) { if (siginfo[i].status != SIGNAL_ACTION_DEFAULT) { sigaction(i, &sa_old[i], 0); siginfo[i].status = SIGNAL_ACTION_DEFAULT; } } } Jim_Free(sa_old); sa_old = NULL; sigloc = 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) { Jim_PackageProvideCheck(interp, "signal"); 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); /* Teach the jim core how to set a result from a sigmask */ interp->signal_set_result = signal_set_sigmask_result; /* Currently only the top level interp supports signals */ if (!sigloc) { signal_init_names(); /* Make sure we know where to store the signals which occur */ sigloc = &interp->sigmask; Jim_CreateCommand(interp, "signal", Jim_SubCmdProc, (void *)signal_command_table, JimSignalCmdDelete); } return JIM_OK; }