1 /* exp_trap.c - Expect's trap command
3 Written by: Don Libes, NIST, 9/1/93
5 Design and implementation of this program was paid for by U.S. tax
6 dollars. Therefore it is public domain. However, the author and NIST
7 would appreciate credit if this program or parts of it are used.
11 #include "expect_cf.h"
15 #include <sys/types.h>
18 #ifdef HAVE_SYS_WAIT_H
25 #if defined(SIGCLD) && !defined(SIGCHLD)
26 #define SIGCHLD SIGCLD
31 #include "exp_rename.h"
33 #include "exp_command.h"
43 char *action; /* Tcl command to execute upon sig */
44 /* Each is handled by the eval_trap_action */
45 int mark; /* TRUE if signal has occurred */
46 Tcl_Interp *interp; /* interp to use or 0 if we should use the */
47 /* interpreter active at the time the sig */
49 int code; /* return our new code instead of code */
50 /* available when signal is processed */
51 CONST char *name; /* name of signal */
52 int reserved; /* if unavailable for trapping */
55 int sigchld_count = 0; /* # of sigchlds caught but not yet processed */
57 static int eval_trap_action();
59 static int got_sig; /* this records the last signal received */
60 /* it is only a hint and can be wiped out */
61 /* by multiple signals, but it will always */
62 /* be left with a valid signal that is */
65 static Tcl_AsyncHandler async_handler;
71 if (sig <= 0 || sig > NSIG) return("SIGNAL OUT OF RANGE");
72 return(traps[sig].name);
75 /* current sig being processed by user sig handler */
76 static int current_sig = NO_SIG;
78 int exp_nostack_dump = FALSE; /* TRUE if user has requested unrolling of */
79 /* stack with no trace */
85 tophalf(clientData,interp,code)
86 ClientData clientData;
90 struct trap *trap; /* last trap processed */
93 Tcl_Interp *sig_interp;
95 expDiagLog("sighandler: handling signal(%d)\r\n",got_sig);
97 if (got_sig <= 0 || got_sig >= NSIG) {
98 expErrorLog("caught impossible signal %d\r\n",got_sig);
102 /* start to work on this sig. got_sig can now be overwritten */
103 /* and it won't cause a problem */
104 current_sig = got_sig;
105 trap = &traps[current_sig];
109 /* decrement below looks dangerous */
110 /* Don't we need to temporarily block bottomhalf? */
111 if (current_sig == SIGCHLD) {
113 expDiagLog("sigchld_count-- == %d\n",sigchld_count);
117 /* In this one case, we let ourselves be called when no */
118 /* signaler predefined, since we are calling explicitly */
119 /* from another part of the program, and it is just simpler */
120 if (current_sig == 0) return code;
121 expErrorLog("caught unexpected signal: %s (%d)\r\n",
122 signal_to_string(current_sig),current_sig);
127 /* if trap requested original interp, use it */
128 sig_interp = trap->interp;
130 /* else if another interp is available, use it */
133 /* fall back to exp_interp */
134 sig_interp = exp_interp;
137 rc = eval_trap_action(sig_interp,current_sig,trap,code);
138 current_sig = NO_SIG;
141 * scan for more signals to process
144 /* first check for additional SIGCHLDs */
147 traps[SIGCHLD].mark = TRUE;
148 Tcl_AsyncMark(async_handler);
151 for (i=1;i<NSIG;i++) {
154 Tcl_AsyncMark(async_handler);
164 static int rearm_sigchld = FALSE; /* TRUE if sigchld needs to be */
165 /* rearmed (i.e., because it has */
167 static int rearming_sigchld = FALSE;
170 /* called upon receipt of a user-declared signal */
177 * tiny window of death if same signal should arrive here
178 * before we've reinstalled it
181 /* In SV, sigchld must be rearmed after wait to avoid recursion */
182 if (sig != SIGCHLD) {
183 signal(sig,bottomhalf);
186 rearm_sigchld = TRUE;
187 if (rearming_sigchld) sigchld_sleep = TRUE;
191 traps[sig].mark = TRUE;
192 got_sig = sig; /* just a hint - can be wiped out by another */
193 Tcl_AsyncMark(async_handler);
195 /* if we are called while this particular async is being processed */
196 /* original async_proc will turn off "mark" so that when async_proc */
197 /* is recalled, it will see that nothing was left to do */
199 /* In case of SIGCHLD though, we must recall it as many times as
200 * we have received it.
202 if (sig == SIGCHLD) {
206 /* if we are doing an i_read, restart it */
207 #ifdef HAVE_SIGLONGJMP
208 if (env_valid && (sig != 0)) siglongjmp(env,2);
210 if (env_valid && (sig != 0)) longjmp(env,2);
211 #endif /* HAVE_SIGLONGJMP */
217 exp_rearm_sigchld(interp)
222 rearm_sigchld = FALSE;
223 rearming_sigchld = TRUE;
224 signal(SIGCHLD,bottomhalf);
227 rearming_sigchld = FALSE;
229 /* if the rearming immediately caused another SIGCHLD, slow down */
230 /* It's probably one of Tcl's intermediary pipeline processes that */
231 /* Tcl hasn't caught up with yet. */
233 exp_dsleep(interp,0.2);
234 sigchld_sleep = FALSE;
245 for (i=1;i<NSIG;i++) {
246 traps[i].name = Tcl_SignalId(i);
248 traps[i].reserved = FALSE;
252 * fix up any special cases
256 /* Tcl names it SIGCLD, not good for portable scripts */
257 traps[SIGCLD].name = "SIGCHLD";
260 traps[SIGALRM].reserved = TRUE;
263 traps[SIGKILL].reserved = TRUE;
266 traps[SIGSTOP].reserved = TRUE;
269 async_handler = Tcl_AsyncCreate(tophalf,(ClientData)0);
273 /* given signal index or name as string, */
274 /* returns signal index or -1 if bad arg */
276 exp_string_to_signal(interp,s)
283 /* try interpreting as an integer */
284 if (1 == sscanf(s,"%d",&sig)) {
285 if (sig > 0 && sig < NSIG) return sig;
287 /* try interpreting as a string */
288 for (sig=1;sig<NSIG;sig++) {
289 name = traps[sig].name;
290 if (streq(s,name) || streq(s,name+3)) return(sig);
294 exp_error(interp,"invalid signal %s",s);
301 Exp_TrapObjCmd(clientData, interp, objc, objv)
302 ClientData clientData;
305 Tcl_Obj *CONST objv[];
308 int n; /* number of signals in list */
309 Tcl_Obj **list; /* list of signals */
311 int len; /* length of action */
313 int show_name = FALSE; /* if user asked for current sig by name */
314 int show_number = FALSE;/* if user asked for current sig by number */
315 int show_max = FALSE; /* if user asked for NSIG-1 */
317 int new_code = FALSE; /* if action result should overwrite orig */
318 Tcl_Interp *new_interp = interp;/* interp in which to evaluate */
319 /* action when signal occurs */
324 arg = Tcl_GetString(*objv);
326 if (streq(arg,"-code")) {
329 } else if (streq(arg,"-interp")) {
332 } else if (streq(arg,"-name")) {
335 } else if (streq(arg,"-number")) {
338 } else if (streq(arg,"-max")) {
344 if (show_name || show_number || show_max) {
345 if (objc > 0) goto usage_error;
347 Tcl_SetObjResult(interp,Tcl_NewIntObj(NSIG-1));
350 if (current_sig == NO_SIG) {
351 Tcl_SetResult(interp,"no signal in progress",TCL_STATIC);
355 /* skip over "SIG" */
356 /* TIP 27: Casting away the CONST should be ok because of TCL_STATIC
358 Tcl_SetResult(interp,(char*)signal_to_string(current_sig) + 3,TCL_STATIC);
360 Tcl_SetObjResult(interp,Tcl_NewIntObj(current_sig));
365 if (objc == 0 || objc > 2) goto usage_error;
368 int sig = exp_string_to_signal(interp,arg);
369 if (sig == -1) return TCL_ERROR;
371 if (traps[sig].action) {
372 Tcl_SetResult(interp,traps[sig].action,TCL_STATIC);
374 Tcl_SetResult(interp,"SIG_DFL",TCL_STATIC);
381 /* objv[1] is the list of signals - crack it open */
382 if (TCL_OK != Tcl_ListObjGetElements(interp,objv[1],&n,&list)) {
390 s = Tcl_GetString(list[i]);
392 sig = exp_string_to_signal(interp,s);
398 if (traps[sig].reserved) {
399 exp_error(interp,"cannot trap %s",signal_to_string(sig));
404 expDiagLog("trap: setting up signal %d (\"%s\")\r\n",sig,s);
405 if (traps[sig].action) ckfree(traps[sig].action);
406 if (streq(action,"SIG_DFL")) {
407 /* should've been free'd by now if nec. */
408 traps[sig].action = 0;
412 rearm_sigchld = FALSE;
415 len = 1 + strlen(action);
416 traps[sig].action = ckalloc(len);
417 memcpy(traps[sig].action,action,len);
418 traps[sig].interp = new_interp;
419 traps[sig].code = new_code;
420 if (streq(action,"SIG_IGN")) {
422 } else signal(sig,bottomhalf);
425 /* It is no longer necessary to free the split list since it */
426 /* is still owned by Tcl, yes? */
427 /* ckfree((char *)list); */
430 exp_error(interp,"usage: trap [command or SIG_DFL or SIG_IGN] {list of signals}");
434 /* called by tophalf() to process the given signal */
436 eval_trap_action(interp,sig,trap,oldcode)
444 Tcl_Obj *eip; /* errorInfo */
445 Tcl_Obj *ecp; /* errorCode */
446 Tcl_Obj *irp; /* interp's result */
448 expDiagLogU("async event handler: Tcl_Eval(");
449 expDiagLogU(trap->action);
450 expDiagLogU(")\r\n");
452 /* save to prevent user from redefining trap->code while trap */
454 code_flag = trap->code;
461 eip = Tcl_GetVar2Ex(interp,"errorInfo","",TCL_GLOBAL_ONLY);
462 if (eip) eip = Tcl_DuplicateObj(eip);
463 ecp = Tcl_GetVar2Ex(interp,"errorCode","",TCL_GLOBAL_ONLY);
464 if (ecp) ecp = Tcl_DuplicateObj(ecp);
465 irp = Tcl_GetObjResult(interp);
466 if (irp) irp = Tcl_DuplicateObj(irp);
469 newcode = Tcl_GlobalEval(interp,trap->action);
472 * if new code is to be ignored (usual case - see "else" below)
473 * allow only OK/RETURN from trap, otherwise complain
477 expDiagLog("return value = %d for trap %s, action ",newcode,signal_to_string(sig));
478 expDiagLogU(trap->action);
480 if (0 != strcmp(Tcl_GetStringResult(interp),"")) {
483 * Check errorinfo and see if it contains -nostack.
484 * This shouldn't be necessary, but John changed the
485 * top level interp so that it distorts arbitrary
486 * return values into TCL_ERROR, so by the time we
487 * get back, we'll have lost the value of errorInfo
490 eip = Tcl_GetVar2Ex(interp,"errorInfo","",TCL_GLOBAL_ONLY);
492 exp_nostack_dump = (0 == strncmp("-nostack",Tcl_GetString(eip),8));
495 } else if (newcode != TCL_OK && newcode != TCL_RETURN) {
496 if (newcode != TCL_ERROR) {
497 exp_error(interp,"return value = %d for trap %s, action %s\r\n",newcode,signal_to_string(sig),trap->action);
499 Tcl_BackgroundError(interp);
506 Tcl_ResetResult(interp); /* turns off Tcl's internal */
507 /* flags: ERR_IN_PROGRESS, ERROR_CODE_SET */
508 /* This also wipes clean errorInfo/Code/result which is why */
509 /* all the calls to Tcl_Dup earlier */
512 /* odd that Tcl doesn't have a call that does all this at once */
514 char *s = Tcl_GetStringFromObj(eip,&len);
515 Tcl_AddObjErrorInfo(interp,s,len);
516 Tcl_DecrRefCount(eip);
517 /* we never incr'd it, but the code allows this */
519 Tcl_UnsetVar(interp,"errorInfo",0);
522 /* restore errorCode. Note that Tcl_AddErrorInfo (above) */
523 /* resets it to NONE. If the previous value is NONE, it's */
524 /* important to avoid calling Tcl_SetErrorCode since this */
525 /* with cause Tcl to set its internal ERROR_CODE_SET flag. */
527 if (!streq("NONE",Tcl_GetString(ecp)))
528 Tcl_SetErrorCode(interp,ecp);
529 /* we're just passing on the errorcode obj */
530 /* presumably, Tcl will incr ref count */
532 Tcl_UnsetVar(interp,"errorCode",0);
537 /* note that since newcode gets overwritten here by old code */
538 /* it is possible to return in the middle of a trap by using */
539 /* "return" (or "continue" for that matter)! */
544 static struct exp_cmd_data
546 {"trap", Exp_TrapObjCmd, 0, (ClientData)EXP_SPAWN_ID_BAD, 0},
550 exp_init_trap_cmds(interp)
553 exp_create_commands(interp,cmd_data);