tests: jimsh.tests additional tests
[jimtcl.git] / jim-signal.c
blob866119d506078ec5f39bde7f759fcdf6c142c467
1 /*
2 * jim-signal.c
4 */
6 #include <signal.h>
7 #include <string.h>
8 #include <ctype.h>
10 #include "jimautoconf.h"
11 #ifdef HAVE_UNISTD_H
12 #include <unistd.h>
13 #endif
14 #include <jim-subcmd.h>
15 #include <jim-signal.h>
17 #define MAX_SIGNALS_WIDE (sizeof(jim_wide) * 8)
18 #if defined(NSIG)
19 #define MAX_SIGNALS (int)((NSIG < MAX_SIGNALS_WIDE) ? NSIG : MAX_SIGNALS_WIDE)
20 #else
21 #define MAX_SIGNALS (int)MAX_SIGNALS_WIDE
22 #endif
24 static jim_wide *sigloc;
25 static jim_wide sigsignored;
26 static struct sigaction *sa_old;
27 static struct {
28 int status;
29 const char *name;
30 } siginfo[MAX_SIGNALS];
32 /* Make sure to do this as a wide, not int */
33 #define sig_to_bit(SIG) ((jim_wide)1 << (SIG))
35 static void signal_handler(int sig)
37 /* Remember which signals occurred and store in *sigloc.
38 * Jim_Eval() will notice this as soon as it can and throw an error
40 *sigloc |= sig_to_bit(sig);
43 static void signal_ignorer(int sig)
45 /* Remember which signals occurred for access by 'signal check' */
46 sigsignored |= sig_to_bit(sig);
49 static void signal_init_names(void)
51 #define SET_SIG_NAME(SIG) siginfo[SIG].name = #SIG
53 SET_SIG_NAME(SIGABRT);
54 SET_SIG_NAME(SIGALRM);
55 SET_SIG_NAME(SIGBUS);
56 SET_SIG_NAME(SIGCHLD);
57 SET_SIG_NAME(SIGCONT);
58 SET_SIG_NAME(SIGFPE);
59 SET_SIG_NAME(SIGHUP);
60 SET_SIG_NAME(SIGILL);
61 SET_SIG_NAME(SIGINT);
62 #ifdef SIGIO
63 SET_SIG_NAME(SIGIO);
64 #endif
65 SET_SIG_NAME(SIGKILL);
66 SET_SIG_NAME(SIGPIPE);
67 SET_SIG_NAME(SIGPROF);
68 SET_SIG_NAME(SIGQUIT);
69 SET_SIG_NAME(SIGSEGV);
70 SET_SIG_NAME(SIGSTOP);
71 SET_SIG_NAME(SIGSYS);
72 SET_SIG_NAME(SIGTERM);
73 SET_SIG_NAME(SIGTRAP);
74 SET_SIG_NAME(SIGTSTP);
75 SET_SIG_NAME(SIGTTIN);
76 SET_SIG_NAME(SIGTTOU);
77 SET_SIG_NAME(SIGURG);
78 SET_SIG_NAME(SIGUSR1);
79 SET_SIG_NAME(SIGUSR2);
80 SET_SIG_NAME(SIGVTALRM);
81 SET_SIG_NAME(SIGWINCH);
82 SET_SIG_NAME(SIGXCPU);
83 SET_SIG_NAME(SIGXFSZ);
84 #ifdef SIGPWR
85 SET_SIG_NAME(SIGPWR);
86 #endif
87 #ifdef SIGCLD
88 SET_SIG_NAME(SIGCLD);
89 #endif
90 #ifdef SIGEMT
91 SET_SIG_NAME(SIGEMT);
92 #endif
93 #ifdef SIGLOST
94 SET_SIG_NAME(SIGLOST);
95 #endif
96 #ifdef SIGPOLL
97 SET_SIG_NAME(SIGPOLL);
98 #endif
99 #ifdef SIGINFO
100 SET_SIG_NAME(SIGINFO);
101 #endif
105 *----------------------------------------------------------------------
107 * Tcl_SignalId --
109 * Return a textual identifier for a signal number.
111 * Results:
112 * This procedure returns a machine-readable textual identifier
113 * that corresponds to sig. The identifier is the same as the
114 * #define name in signal.h.
116 * Side effects:
117 * None.
119 *----------------------------------------------------------------------
121 const char *Jim_SignalId(int sig)
123 if (sig >=0 && sig < MAX_SIGNALS) {
124 if (siginfo[sig].name) {
125 return siginfo[sig].name;
128 return "unknown signal";
132 * Given the name of a signal, returns the signal value if found,
133 * or returns -1 (and sets an error) if not found.
134 * We accept -SIGINT, SIGINT, INT or any lowercase version or a number,
135 * either positive or negative.
137 static int find_signal_by_name(Jim_Interp *interp, const char *name)
139 int i;
140 const char *pt = name;
142 /* Remove optional - and SIG from the front of the name */
143 if (*pt == '-') {
144 pt++;
146 if (strncasecmp(name, "sig", 3) == 0) {
147 pt += 3;
149 if (isdigit(UCHAR(pt[0]))) {
150 i = atoi(pt);
151 if (i > 0 && i < MAX_SIGNALS) {
152 return i;
155 else {
156 for (i = 1; i < MAX_SIGNALS; i++) {
157 /* Jim_SignalId() returns names such as SIGINT, and
158 * returns "unknown signal" if unknown, so this will work
160 if (strcasecmp(Jim_SignalId(i) + 3, pt) == 0) {
161 return i;
165 Jim_SetResultFormatted(interp, "unknown signal %s", name);
167 return -1;
170 #define SIGNAL_ACTION_HANDLE 1
171 #define SIGNAL_ACTION_IGNORE -1
172 #define SIGNAL_ACTION_BLOCK -2
173 #define SIGNAL_ACTION_DEFAULT 0
175 static int do_signal_cmd(Jim_Interp *interp, int action, int argc, Jim_Obj *const *argv)
177 struct sigaction sa;
178 int i;
180 if (argc == 0) {
181 Jim_SetResult(interp, Jim_NewListObj(interp, NULL, 0));
182 for (i = 1; i < MAX_SIGNALS; i++) {
183 if (siginfo[i].status == action) {
184 /* Add signal name to the list */
185 Jim_ListAppendElement(interp, Jim_GetResult(interp),
186 Jim_NewStringObj(interp, Jim_SignalId(i), -1));
189 return JIM_OK;
192 /* Catch all the signals we care about */
193 if (action != SIGNAL_ACTION_DEFAULT) {
194 memset(&sa, 0, sizeof(sa));
195 if (action == SIGNAL_ACTION_HANDLE) {
196 sa.sa_handler = signal_handler;
198 else if (action == SIGNAL_ACTION_IGNORE) {
199 sa.sa_handler = signal_ignorer;
201 else {
202 /* SIGNAL_ACTION_BLOCK */
203 sa.sa_handler = SIG_IGN;
207 /* Iterate through the provided signals */
208 for (i = 0; i < argc; i++) {
209 int sig = find_signal_by_name(interp, Jim_String(argv[i]));
211 if (sig < 0) {
212 return JIM_ERR;
214 if (action != siginfo[sig].status) {
215 /* Need to change the action for this signal */
216 switch (action) {
217 case SIGNAL_ACTION_BLOCK:
218 case SIGNAL_ACTION_HANDLE:
219 case SIGNAL_ACTION_IGNORE:
220 if (siginfo[sig].status == SIGNAL_ACTION_DEFAULT) {
221 if (!sa_old) {
222 /* Allocate the structure the first time through */
223 sa_old = Jim_Alloc(sizeof(*sa_old) * MAX_SIGNALS);
225 sigaction(sig, &sa, &sa_old[sig]);
227 else {
228 sigaction(sig, &sa, 0);
230 break;
232 case SIGNAL_ACTION_DEFAULT:
233 /* Restore old handler */
234 if (sa_old) {
235 sigaction(sig, &sa_old[sig], 0);
238 siginfo[sig].status = action;
242 return JIM_OK;
245 static int signal_cmd_handle(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
247 return do_signal_cmd(interp, SIGNAL_ACTION_HANDLE, argc, argv);
250 static int signal_cmd_ignore(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
252 return do_signal_cmd(interp, SIGNAL_ACTION_IGNORE, argc, argv);
255 static int signal_cmd_block(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
257 return do_signal_cmd(interp, SIGNAL_ACTION_BLOCK, argc, argv);
260 static int signal_cmd_default(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
262 return do_signal_cmd(interp, SIGNAL_ACTION_DEFAULT, argc, argv);
265 static int signal_set_sigmask_result(Jim_Interp *interp, jim_wide sigmask)
267 int i;
268 Jim_Obj *listObj = Jim_NewListObj(interp, NULL, 0);
270 for (i = 0; i < MAX_SIGNALS; i++) {
271 if (sigmask & sig_to_bit(i)) {
272 Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp, Jim_SignalId(i), -1));
275 Jim_SetResult(interp, listObj);
276 return JIM_OK;
279 static int signal_cmd_check(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
281 int clear = 0;
282 jim_wide mask = 0;
283 jim_wide ignored;
285 if (argc > 0 && Jim_CompareStringImmediate(interp, argv[0], "-clear")) {
286 clear++;
288 if (argc > clear) {
289 int i;
291 /* Signals specified */
292 for (i = clear; i < argc; i++) {
293 int sig = find_signal_by_name(interp, Jim_String(argv[i]));
295 if (sig < 0 || sig >= MAX_SIGNALS) {
296 return JIM_ERR;
298 mask |= sig_to_bit(sig);
301 else {
302 /* No signals specified, so check/clear all */
303 mask = ~mask;
306 /* Be careful we don't have a race condition where signals are cleared but not returned */
307 ignored = sigsignored & mask;
308 if (clear) {
309 sigsignored &= ~ignored;
311 /* Set the result */
312 signal_set_sigmask_result(interp, ignored);
313 return JIM_OK;
316 static int signal_cmd_throw(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
318 int sig = SIGINT;
320 if (argc == 1) {
321 if ((sig = find_signal_by_name(interp, Jim_String(argv[0]))) < 0) {
322 return JIM_ERR;
326 /* If the signal is ignored ... */
327 if (siginfo[sig].status == SIGNAL_ACTION_IGNORE) {
328 sigsignored |= sig_to_bit(sig);
329 return JIM_OK;
332 /* Just set the signal */
333 interp->sigmask |= sig_to_bit(sig);
335 /* Set the canonical name of the signal as the result */
336 Jim_SetResultString(interp, Jim_SignalId(sig), -1);
338 /* And simply say we caught the signal */
339 return JIM_SIGNAL;
343 *-----------------------------------------------------------------------------
345 * Jim_SignalCmd --
346 * Implements the TCL signal command:
347 * signal handle|ignore|default|throw ?signals ...?
348 * signal throw signal
350 * Specifies which signals are handled by Tcl code.
351 * If the one of the given signals is caught, it causes a JIM_SIGNAL
352 * exception to be thrown which can be caught by catch.
354 * Use 'signal ignore' to ignore the signal(s)
355 * Use 'signal default' to go back to the default behaviour
356 * Use 'signal throw signal' to raise the given signal
358 * If no arguments are given, returns the list of signals which are being handled
360 * Results:
361 * Standard TCL results.
363 *-----------------------------------------------------------------------------
365 static const jim_subcmd_type signal_command_table[] = {
366 { "handle",
367 "?signals ...?",
368 signal_cmd_handle,
371 /* Description: Lists handled signals, or adds to handled signals */
373 { "ignore",
374 "?signals ...?",
375 signal_cmd_ignore,
378 /* Description: Lists ignored signals, or adds to ignored signals */
380 { "block",
381 "?signals ...?",
382 signal_cmd_block,
385 /* Description: Lists blocked signals, or adds to blocked signals */
387 { "default",
388 "?signals ...?",
389 signal_cmd_default,
392 /* Description: Lists defaulted signals, or adds to defaulted signals */
394 { "check",
395 "?-clear? ?signals ...?",
396 signal_cmd_check,
399 /* Description: Returns ignored signals which have occurred, and optionally clearing them */
401 { "throw",
402 "?signal?",
403 signal_cmd_throw,
406 /* Description: Raises the given signal (default SIGINT) */
408 { NULL }
412 * Restore default signal handling.
414 static void JimSignalCmdDelete(Jim_Interp *interp, void *privData)
416 int i;
417 if (sa_old) {
418 for (i = 1; i < MAX_SIGNALS; i++) {
419 if (siginfo[i].status != SIGNAL_ACTION_DEFAULT) {
420 sigaction(i, &sa_old[i], 0);
421 siginfo[i].status = SIGNAL_ACTION_DEFAULT;
425 Jim_Free(sa_old);
426 sa_old = NULL;
427 sigloc = NULL;
430 static int Jim_AlarmCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
432 int ret;
434 if (argc != 2) {
435 Jim_WrongNumArgs(interp, 1, argv, "seconds");
436 return JIM_ERR;
438 else {
439 #ifdef HAVE_UALARM
440 double t;
442 ret = Jim_GetDouble(interp, argv[1], &t);
443 if (ret == JIM_OK) {
444 if (t < 1) {
445 ualarm(t * 1e6, 0);
447 else {
448 alarm(t);
451 #else
452 long t;
454 ret = Jim_GetLong(interp, argv[1], &t);
455 if (ret == JIM_OK) {
456 alarm(t);
458 #endif
461 return ret;
464 static int Jim_SleepCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
466 int ret;
468 if (argc != 2) {
469 Jim_WrongNumArgs(interp, 1, argv, "seconds");
470 return JIM_ERR;
472 else {
473 double t;
475 ret = Jim_GetDouble(interp, argv[1], &t);
476 if (ret == JIM_OK) {
477 #ifdef HAVE_USLEEP
478 usleep((int)((t - (int)t) * 1e6));
479 #endif
480 sleep(t);
484 return ret;
487 static int Jim_KillCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
489 int sig;
490 long pid;
491 Jim_Obj *pidObj;
492 const char *signame;
494 if (argc != 2 && argc != 3) {
495 Jim_WrongNumArgs(interp, 1, argv, "?SIG|-0? pid");
496 return JIM_ERR;
499 if (argc == 2) {
500 sig = SIGTERM;
501 pidObj = argv[1];
503 else {
504 signame = Jim_String(argv[1]);
505 pidObj = argv[2];
507 /* Special 'kill -0 pid' to determine if a pid exists */
508 if (strcmp(signame, "-0") == 0 || strcmp(signame, "0") == 0) {
509 sig = 0;
511 else {
512 sig = find_signal_by_name(interp, signame);
513 if (sig < 0) {
514 return JIM_ERR;
519 if (Jim_GetLong(interp, pidObj, &pid) != JIM_OK) {
520 return JIM_ERR;
523 if (kill(pid, sig) == 0) {
524 return JIM_OK;
527 Jim_SetResultString(interp, "kill: Failed to deliver signal", -1);
528 return JIM_ERR;
531 int Jim_signalInit(Jim_Interp *interp)
533 if (Jim_PackageProvide(interp, "signal", "1.0", JIM_ERRMSG))
534 return JIM_ERR;
536 Jim_CreateCommand(interp, "alarm", Jim_AlarmCmd, 0, 0);
537 Jim_CreateCommand(interp, "kill", Jim_KillCmd, 0, 0);
538 /* Sleep is slightly dubious here */
539 Jim_CreateCommand(interp, "sleep", Jim_SleepCmd, 0, 0);
541 /* Teach the jim core how to set a result from a sigmask */
542 interp->signal_set_result = signal_set_sigmask_result;
544 /* Currently only the top level interp supports signals */
545 if (!sigloc) {
546 signal_init_names();
548 /* Make sure we know where to store the signals which occur */
549 sigloc = &interp->sigmask;
551 Jim_CreateCommand(interp, "signal", Jim_SubCmdProc, (void *)signal_command_table, JimSignalCmdDelete);
554 return JIM_OK;