1
2/*
3 * jim-signal.c
4 *
5 */
6
7#include <signal.h>
8#include <string.h>
9#include <ctype.h>
10#include <unistd.h>
11
12#include "jim.h"
13#include "jimautoconf.h"
14#include "jim-subcmd.h"
15#include "jim-signal.h"
16
17#define MAX_SIGNALS (sizeof(jim_wide) * 8)
18
19static jim_wide *sigloc;
20static jim_wide sigsblocked;
21static struct sigaction *sa_old;
22static int signal_handling[MAX_SIGNALS];
23
24/* Make sure to do this as a wide, not int */
25#define sig_to_bit(SIG) ((jim_wide)1 << (SIG))
26
27static void signal_handler(int sig)
28{
29    /* We just remember which signals occurred. Jim_Eval() will
30     * notice this as soon as it can and throw an error
31     */
32    *sigloc |= sig_to_bit(sig);
33}
34
35static void signal_ignorer(int sig)
36{
37    /* We just remember which signals occurred */
38    sigsblocked |= sig_to_bit(sig);
39}
40
41/*
42 *----------------------------------------------------------------------
43 *
44 * Tcl_SignalId --
45 *
46 *      Return a textual identifier for a signal number.
47 *
48 * Results:
49 *      This procedure returns a machine-readable textual identifier
50 *      that corresponds to sig.  The identifier is the same as the
51 *      #define name in signal.h.
52 *
53 * Side effects:
54 *      None.
55 *
56 *----------------------------------------------------------------------
57 */
58#define CHECK_SIG(NAME) if (sig == NAME) return #NAME
59
60const char *Jim_SignalId(int sig)
61{
62    CHECK_SIG(SIGABRT);
63    CHECK_SIG(SIGALRM);
64    CHECK_SIG(SIGBUS);
65    CHECK_SIG(SIGCHLD);
66    CHECK_SIG(SIGCONT);
67    CHECK_SIG(SIGFPE);
68    CHECK_SIG(SIGHUP);
69    CHECK_SIG(SIGILL);
70    CHECK_SIG(SIGINT);
71#ifdef SIGIO
72    CHECK_SIG(SIGIO);
73#endif
74    CHECK_SIG(SIGKILL);
75    CHECK_SIG(SIGPIPE);
76    CHECK_SIG(SIGPROF);
77    CHECK_SIG(SIGQUIT);
78    CHECK_SIG(SIGSEGV);
79    CHECK_SIG(SIGSTOP);
80    CHECK_SIG(SIGSYS);
81    CHECK_SIG(SIGTERM);
82    CHECK_SIG(SIGTRAP);
83    CHECK_SIG(SIGTSTP);
84    CHECK_SIG(SIGTTIN);
85    CHECK_SIG(SIGTTOU);
86    CHECK_SIG(SIGURG);
87    CHECK_SIG(SIGUSR1);
88    CHECK_SIG(SIGUSR2);
89    CHECK_SIG(SIGVTALRM);
90    CHECK_SIG(SIGWINCH);
91    CHECK_SIG(SIGXCPU);
92    CHECK_SIG(SIGXFSZ);
93#ifdef SIGPWR
94    CHECK_SIG(SIGPWR);
95#endif
96#ifdef SIGCLD
97    CHECK_SIG(SIGCLD);
98#endif
99#ifdef SIGEMT
100    CHECK_SIG(SIGEMT);
101#endif
102#ifdef SIGLOST
103    CHECK_SIG(SIGLOST);
104#endif
105#ifdef SIGPOLL
106    CHECK_SIG(SIGPOLL);
107#endif
108#ifdef SIGINFO
109    CHECK_SIG(SIGINFO);
110#endif
111    return "unknown signal";
112}
113
114const char *Jim_SignalName(int sig)
115{
116#ifdef HAVE_SYS_SIGLIST
117    if (sig >= 0 && sig < NSIG) {
118        return sys_siglist[sig];
119    }
120#endif
121    return Jim_SignalId(sig);
122}
123
124/**
125 * Given the name of a signal, returns the signal value if found,
126 * or returns -1 (and sets an error) if not found.
127 * We accept -SIGINT, SIGINT, INT or any lowercase version or a number,
128 * either positive or negative.
129 */
130static int find_signal_by_name(Jim_Interp *interp, const char *name)
131{
132    int i;
133    const char *pt = name;
134
135    /* Remove optional - and SIG from the front of the name */
136    if (*pt == '-') {
137        pt++;
138    }
139    if (strncasecmp(name, "sig", 3) == 0) {
140        pt += 3;
141    }
142    if (isdigit(UCHAR(pt[0]))) {
143        i = atoi(pt);
144        if (i > 0 && i < MAX_SIGNALS) {
145            return i;
146        }
147    }
148    else {
149        for (i = 1; i < MAX_SIGNALS; i++) {
150            /* Jim_SignalId() returns names such as SIGINT, and
151             * returns "unknown signal id" if unknown, so this will work
152             */
153            if (strcasecmp(Jim_SignalId(i) + 3, pt) == 0) {
154                return i;
155            }
156        }
157    }
158    Jim_SetResultString(interp, "unknown signal ", -1);
159    Jim_AppendString(interp, Jim_GetResult(interp), name, -1);
160
161    return -1;
162}
163
164#define SIGNAL_ACTION_HANDLE 1
165#define SIGNAL_ACTION_IGNORE -1
166#define SIGNAL_ACTION_DEFAULT 0
167
168static int do_signal_cmd(Jim_Interp *interp, int action, int argc, Jim_Obj *const *argv)
169{
170    struct sigaction sa;
171    int i;
172
173    if (argc == 0) {
174        Jim_SetResult(interp, Jim_NewListObj(interp, NULL, 0));
175        for (i = 1; i < MAX_SIGNALS; i++) {
176            if (signal_handling[i] == action) {
177                /* Add signal name to the list  */
178                Jim_ListAppendElement(interp, Jim_GetResult(interp),
179                    Jim_NewStringObj(interp, Jim_SignalId(i), -1));
180            }
181        }
182        return JIM_OK;
183    }
184
185    /* Catch all the signals we care about */
186    if (action != SIGNAL_ACTION_DEFAULT) {
187        sa.sa_flags = 0;
188        sigemptyset(&sa.sa_mask);
189        if (action == SIGNAL_ACTION_HANDLE) {
190            sa.sa_handler = signal_handler;
191        }
192        else {
193            sa.sa_handler = signal_ignorer;
194        }
195    }
196
197    /* Iterate through the provided signals */
198    for (i = 0; i < argc; i++) {
199        int sig = find_signal_by_name(interp, Jim_String(argv[i]));
200
201        if (sig < 0) {
202            return JIM_ERR;
203        }
204        if (action != signal_handling[sig]) {
205            /* Need to change the action for this signal */
206            switch (action) {
207                case SIGNAL_ACTION_HANDLE:
208                case SIGNAL_ACTION_IGNORE:
209                    if (signal_handling[sig] == SIGNAL_ACTION_DEFAULT) {
210                        if (!sa_old) {
211                            /* Allocate the structure the first time through */
212                            sa_old = Jim_Alloc(sizeof(*sa_old) * MAX_SIGNALS);
213                        }
214                        sigaction(sig, &sa, &sa_old[sig]);
215                    }
216                    else {
217                        sigaction(sig, &sa, 0);
218                    }
219                    break;
220
221                case SIGNAL_ACTION_DEFAULT:
222                    /* Restore old handler */
223                    if (sa_old) {
224                        sigaction(sig, &sa_old[sig], 0);
225                    }
226            }
227            signal_handling[sig] = action;
228        }
229    }
230
231    return JIM_OK;
232}
233
234static int signal_cmd_handle(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
235{
236    return do_signal_cmd(interp, SIGNAL_ACTION_HANDLE, argc, argv);
237}
238
239static int signal_cmd_ignore(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
240{
241    return do_signal_cmd(interp, SIGNAL_ACTION_IGNORE, argc, argv);
242}
243
244static int signal_cmd_default(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
245{
246    return do_signal_cmd(interp, SIGNAL_ACTION_DEFAULT, argc, argv);
247}
248
249static int signal_set_sigmask_result(Jim_Interp *interp, jim_wide sigmask)
250{
251    int i;
252    Jim_Obj *listObj = Jim_NewListObj(interp, NULL, 0);
253
254    for (i = 0; i < MAX_SIGNALS; i++) {
255        if (sigmask & sig_to_bit(i)) {
256            Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, Jim_SignalId(i), -1));
257        }
258    }
259    Jim_SetResult(interp, listObj);
260    return JIM_OK;
261}
262
263static int signal_cmd_check(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
264{
265    int clear = 0;
266    jim_wide mask = 0;
267    jim_wide blocked;
268
269    if (argc > 0 && Jim_CompareStringImmediate(interp, argv[0], "-clear")) {
270        clear++;
271    }
272    if (argc > clear) {
273        int i;
274
275        /* Signals specified */
276        for (i = clear; i < argc; i++) {
277            int sig = find_signal_by_name(interp, Jim_String(argv[i]));
278
279            if (sig < 0 || sig >= MAX_SIGNALS) {
280                return -1;
281            }
282            mask |= sig_to_bit(sig);
283        }
284    }
285    else {
286        /* No signals specified, so check/clear all */
287        mask = ~mask;
288    }
289
290    if ((sigsblocked & mask) == 0) {
291        /* No matching signals, so empty result and nothing to do */
292        return JIM_OK;
293    }
294    /* Be careful we don't have a race condition where signals are cleared but not returned */
295    blocked = sigsblocked & mask;
296    if (clear) {
297        sigsblocked &= ~blocked;
298    }
299    /* Set the result */
300    signal_set_sigmask_result(interp, blocked);
301    return JIM_OK;
302}
303
304static int signal_cmd_throw(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
305{
306    int sig = SIGINT;
307
308    if (argc == 1) {
309        if ((sig = find_signal_by_name(interp, Jim_String(argv[0]))) < 0) {
310            return JIM_ERR;
311        }
312    }
313
314    /* If the signal is ignored (blocked) ... */
315    if (signal_handling[sig] == SIGNAL_ACTION_IGNORE) {
316        sigsblocked |= sig_to_bit(sig);
317        return JIM_OK;
318    }
319
320    /* Just set the signal */
321    interp->sigmask |= sig_to_bit(sig);
322
323    /* Set the canonical name of the signal as the result */
324    Jim_SetResultString(interp, Jim_SignalId(sig), -1);
325
326    /* And simply say we caught the signal */
327    return JIM_SIGNAL;
328}
329
330/*
331 *-----------------------------------------------------------------------------
332 *
333 * Jim_SignalCmd --
334 *     Implements the TCL signal command:
335 *         signal handle|ignore|default|throw ?signals ...?
336 *         signal throw signal
337 *
338 *     Specifies which signals are handled by Tcl code.
339 *     If the one of the given signals is caught, it causes a JIM_SIGNAL
340 *     exception to be thrown which can be caught by catch.
341 *
342 *     Use 'signal ignore' to ignore the signal(s)
343 *     Use 'signal default' to go back to the default behaviour
344 *     Use 'signal throw signal' to raise the given signal
345 *
346 *     If no arguments are given, returns the list of signals which are being handled
347 *
348 * Results:
349 *      Standard TCL results.
350 *
351 *-----------------------------------------------------------------------------
352 */
353static const jim_subcmd_type signal_command_table[] = {
354    {   .cmd = "handle",
355        .args = "?signals ...?",
356        .function = signal_cmd_handle,
357        .minargs = 0,
358        .maxargs = -1,
359        .description = "Lists handled signals, or adds to handled signals"
360    },
361    {   .cmd = "ignore",
362        .args = "?signals ...?",
363        .function = signal_cmd_ignore,
364        .minargs = 0,
365        .maxargs = -1,
366        .description = "Lists ignored signals, or adds to ignored signals"
367    },
368    {   .cmd = "default",
369        .args = "?signals ...?",
370        .function = signal_cmd_default,
371        .minargs = 0,
372        .maxargs = -1,
373        .description = "Lists defaulted signals, or adds to defaulted signals"
374    },
375    {   .cmd = "check",
376        .args = "?-clear? ?signals ...?",
377        .function = signal_cmd_check,
378        .minargs = 0,
379        .maxargs = -1,
380        .description = "Returns ignored signals which have occurred, and optionally clearing them"
381    },
382    {   .cmd = "throw",
383        .args = "?signal?",
384        .function = signal_cmd_throw,
385        .minargs = 0,
386        .maxargs = 1,
387        .description = "Raises the given signal (default SIGINT)"
388    },
389    { 0 }
390};
391
392static int Jim_AlarmCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
393{
394    int ret;
395
396    if (argc != 2) {
397        Jim_WrongNumArgs(interp, 1, argv, "seconds");
398        return JIM_ERR;
399    }
400    else {
401#ifdef HAVE_UALARM
402        double t;
403
404        ret = Jim_GetDouble(interp, argv[1], &t);
405        if (ret == JIM_OK) {
406            if (t < 1) {
407                ualarm(t * 1e6, 0);
408            }
409            else {
410                alarm(t);
411            }
412        }
413#else
414        long t;
415
416        ret = Jim_GetLong(interp, argv[1], &t);
417        if (ret == JIM_OK) {
418            alarm(t);
419        }
420#endif
421    }
422
423    return ret;
424}
425
426static int Jim_SleepCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
427{
428    int ret;
429
430    if (argc != 2) {
431        Jim_WrongNumArgs(interp, 1, argv, "seconds");
432        return JIM_ERR;
433    }
434    else {
435        double t;
436
437        ret = Jim_GetDouble(interp, argv[1], &t);
438        if (ret == JIM_OK) {
439#ifdef HAVE_USLEEP
440            if (t < 1) {
441                usleep(t * 1e6);
442            }
443            else
444#endif
445                sleep(t);
446        }
447    }
448
449    return ret;
450}
451
452static int Jim_KillCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
453{
454    int sig;
455    long pid;
456    Jim_Obj *pidObj;
457    const char *signame;
458
459    if (argc != 2 && argc != 3) {
460        Jim_WrongNumArgs(interp, 1, argv, "?SIG|-0? pid");
461        return JIM_ERR;
462    }
463
464    if (argc == 2) {
465        signame = "SIGTERM";
466        pidObj = argv[1];
467    }
468    else {
469        signame = Jim_String(argv[1]);
470        pidObj = argv[2];
471    }
472
473    /* Special 'kill -0 pid' to determine if a pid exists */
474    if (strcmp(signame, "-0") == 0 || strcmp(signame, "0") == 0) {
475        sig = 0;
476    }
477    else {
478        sig = find_signal_by_name(interp, signame);
479        if (sig < 0) {
480            return JIM_ERR;
481        }
482    }
483
484    if (Jim_GetLong(interp, pidObj, &pid) != JIM_OK) {
485        return JIM_ERR;
486    }
487
488    if (kill(pid, sig) == 0) {
489        return JIM_OK;
490    }
491
492    Jim_SetResultString(interp, "kill: Failed to deliver signal", -1);
493    return JIM_ERR;
494}
495
496int Jim_signalInit(Jim_Interp *interp)
497{
498    if (Jim_PackageProvide(interp, "signal", "1.0", JIM_ERRMSG))
499        return JIM_ERR;
500
501    /* Teach the jim core how to set a result from a sigmask */
502    interp->signal_set_result = signal_set_sigmask_result;
503
504    /* Make sure we know where to store the signals which occur */
505    sigloc = &interp->sigmask;
506
507    Jim_CreateCommand(interp, "signal", Jim_SubCmdProc, (void *)signal_command_table, NULL);
508    Jim_CreateCommand(interp, "alarm", Jim_AlarmCmd, 0, 0);
509    Jim_CreateCommand(interp, "kill", Jim_KillCmd, 0, 0);
510
511    /* Sleep is slightly dubious here */
512    Jim_CreateCommand(interp, "sleep", Jim_SleepCmd, 0, 0);
513    return JIM_OK;
514}
515