1 /*************************************************************************
2  *  TinyFugue - programmable mud client
3  *  Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 Ken Keys
4  *
5  *  TinyFugue (aka "tf") is protected under the terms of the GNU
6  *  General Public License.  See the file "COPYING" for details.
7  ************************************************************************/
8 static const char RCSid[] = "$Id: signals.c,v 35004.70 2007/01/14 19:28:36 kkeys Exp $";
9 
10 /* Signal handling, core dumps, job control, and interactive shells */
11 
12 #include "tfconfig.h"
13 #include <signal.h>
14 #include <setjmp.h>
15 #include "port.h"
16 #if DISABLE_CORE
17 # include <sys/time.h>
18 # include <sys/resource.h>
19 #endif
20 #include <sys/stat.h>   /* for debugger_dump() */
21 #include "tf.h"
22 #include "util.h"
23 #include "pattern.h"	/* for tfio.h */
24 #include "search.h"	/* for tfio.h */
25 #include "tfio.h"
26 #include "world.h" /* for process.h */
27 #include "process.h"
28 #include "tty.h"
29 #include "output.h"
30 #include "signals.h"
31 #include "variable.h"
32 #include "expand.h" /* current_command */
33 
34 #ifdef TF_AIX_DECLS
35 struct rusage *dummy_struct_rusage;
36 union wait *dummy_union_wait;
37 #endif
38 
39 /* POSIX.1 systems should define WIFEXITED and WEXITSTATUS, taking an |int|
40  * parameter, in <sys/wait.h>.  For posix systems, we use them.  For non-posix
41  * systems, we use our own.  For systems which falsely claim to be posix,
42  * but do not define the wait macros, we use our own.  We can not detect
43  * systems which falsely claim to be posix and incorrectly define the wait
44  * macros as taking a |union wait| parameter.  The workaround for such systems
45  * is to change "#ifdef _POSIX_VERSION" to "#if 0" below.
46  */
47 
48 #include <sys/types.h>
49 #if HAVE_SYS_WAIT_H
50 # include <sys/wait.h>
51 #else
52 # undef WIFEXITED
53 # undef WEXITSTATUS
54 #endif
55 #ifdef sequent          /* the wait macros are known to be broken on Dynix */
56 # undef WIFEXITED
57 # undef WEXITSTATUS
58 #endif
59 
60 /* These macros can take an |int| or |union wait| parameter, but the posix
61  * macros are preferred because these require specific knowledge of the
62  * bit layout, which may not be correct on some systems (although most
63  * unix-like systems do use this layout).
64  */
65 #ifndef WIFEXITED
66 # define WIFEXITED(w)  (((*(int *)&(w)) & 0xFF) == 0)   /* works most places */
67 #endif
68 #ifndef WEXITSTATUS
69 # define WEXITSTATUS(w)  (((*(int *)&(w)) >> 8) & 0xFF) /* works most places */
70 #endif
71 
72 typedef RETSIGTYPE (SigHandler)(int sig);
73 
74 #if !HAVE_RAISE
75 # if HAVE_KILL
76 #  define raise(sig) kill(getpid(), sig)
77 # endif
78 #endif
79 
80 #ifdef SIGABRT
81 # define ABORT SIGABRT
82 #else
83 # ifdef SIGQUIT
84 #  define ABORT SIGQUIT
85 # else
86 #  define ABORT SIGTERM
87 # endif
88 #endif
89 
90 /* Zero out undefined signals, so we don't have to #ifdef everything later. */
91 #ifndef SIGHUP
92 # define SIGHUP 0
93 #endif
94 #ifndef SIGTRAP
95 # define SIGTRAP 0
96 #endif
97 #ifndef SIGABRT
98 # define SIGABRT 0
99 #endif
100 #ifndef SIGBUS /* not defined in Linux */
101 # define SIGBUS 0
102 #endif
103 #ifndef SIGPIPE
104 # define SIGPIPE 0
105 #endif
106 #ifndef SIGUSR1
107 # define SIGUSR1 0
108 #endif
109 #ifndef SIGUSR2
110 # define SIGUSR2 0
111 #endif
112 #ifndef SIGTSTP
113 # define SIGTSTP 0
114 #endif
115 #ifndef SIGWINCH
116 # define SIGWINCH 0
117 #endif
118 
119 #ifndef NSIG
120   /* Find an upper bound of the signals we use */
121 # define NSIG \
122    ((SIGHUP  | SIGINT  | SIGQUIT | SIGILL  | SIGTRAP | SIGABRT | SIGFPE  | \
123      SIGBUS  | SIGSEGV | SIGPIPE | SIGTERM | SIGUSR1 | SIGUSR2 | SIGTSTP | \
124      SIGWINCH) + 1)
125 #endif
126 
127 VEC_TYPEDEF(sig_set, (NSIG-1));
128 const int feature_core = 1 - DISABLE_CORE;
129 
130 static const char *argv0 = NULL;
131 static int have_pending_signals = 0;
132 static sig_set pending_signals;
133 static RETSIGTYPE (*parent_tstp_handler)(int sig);
134 
135 static void   handle_interrupt(void);
136 static void   terminate(int sig);
137 static void   coremsg(FILE *dumpfile);
138 static int    debugger_dump(void);
139 static FILE  *get_dumpfile(void);
140 static RETSIGTYPE core_handler(int sig);
141 static RETSIGTYPE signal_scheduler(int sig);
142 static RETSIGTYPE signal_jumper(int sig);
143 #ifndef SIG_IGN
144 static RETSIGTYPE SIG_IGN(int sig);
145 #endif
146 
147 
148 static SigHandler *old_sighup_handler;
149 static SigHandler *setsighandler(int sig, SigHandler *func);
150 
151 static jmp_buf jumpenv;
152 static int fatal_signal = 0;
153 
154 /* HAVE_SIGACTION doesn't mean we NEED_sigaction.  On some systems that have
155  * it, struct sigaction will not get defined unless _POSIX_SOURCE or similar
156  * is defined, so it's best to avoid it if we don't need it.
157  */
158 #ifdef SA_RESTART
159 # define NEED_sigaction
160 #endif
161 #ifdef SA_ACK
162 # define NEED_sigaction
163 #endif
164 
setsighandler(int sig,SigHandler * func)165 static SigHandler *setsighandler(int sig, SigHandler *func)
166 {
167     if (!sig) return NULL;
168 #ifndef NEED_sigaction
169     return signal(sig, func);
170 #else
171     {
172         struct sigaction act;
173         SigHandler *oldfunc;
174 
175         sigaction(sig, NULL, &act);
176         oldfunc = act.sa_handler;
177 # ifdef SA_RESTART
178         /* Disable system call restarting, so select() is interruptable. */
179         act.sa_flags &= ~SA_RESTART;
180 # endif
181 # ifdef SA_ACK
182         /* Disable OS2 SA_ACK, so signals can be re-installed POSIX-style. */
183         act.sa_flags &= ~SA_ACK;
184 # endif
185         act.sa_handler = func;
186         sigaction(sig, &act, NULL);
187         return oldfunc;
188     }
189 #endif /* HAVE_SIGACTION */
190 }
191 
192 /* Returns s, unless s is NULL, accessing s would cause a SIGBUS or SIGSEGV,
193  * or s is too long, in which case it returns another valid string describing
194  * the problem. */
checkstring(const char * s)195 const char *checkstring(const char *s) {
196     SigHandler *old_sigsegv_handler, *old_sigbus_handler;
197     const char *p;
198 
199     if (!s) return "";
200     fatal_signal = 0;
201 
202     old_sigsegv_handler = setsighandler(SIGSEGV, signal_jumper);
203     old_sigbus_handler = setsighandler(SIGBUS, signal_jumper);
204 
205     if (setjmp(jumpenv)) {
206 	if (fatal_signal == SIGSEGV)
207 	    s = "(invalid string: segmentation violation)";
208 	else if (fatal_signal == SIGBUS)
209 	    s = "(invalid string: bus error)";
210 	else
211 	    s = "(invalid string)";
212 	goto exit;
213     }
214 
215     for (p = s; *p; p++) {
216 	if (p - s > 255) {
217 	    s = "(invalid string: too long)";
218 	    break;
219 	}
220     }
221 
222 exit:
223     setsighandler(SIGBUS, old_sigbus_handler);
224     setsighandler(SIGSEGV, old_sigsegv_handler);
225     return s;
226 }
227 
init_signals(void)228 void init_signals(void)
229 {
230     VEC_ZERO(&pending_signals);
231     have_pending_signals = 0;
232 
233     old_sighup_handler = setsighandler(SIGHUP  , signal_scheduler);
234     setsighandler(SIGINT  , signal_scheduler);
235     setsighandler(SIGQUIT , core_handler);
236     setsighandler(SIGILL  , core_handler);
237     setsighandler(SIGTRAP , core_handler);
238     setsighandler(SIGABRT , core_handler);
239     setsighandler(SIGFPE  , SIG_IGN);
240     setsighandler(SIGBUS  , core_handler);
241     setsighandler(SIGSEGV , core_handler);
242     setsighandler(SIGPIPE , SIG_IGN);
243     setsighandler(SIGTERM , signal_scheduler);
244     setsighandler(SIGUSR1 , signal_scheduler);
245     setsighandler(SIGUSR2 , signal_scheduler);
246     parent_tstp_handler = setsighandler(SIGTSTP , signal_scheduler);
247     setsighandler(SIGWINCH, signal_scheduler);
248 
249 #if DISABLE_CORE
250     {
251 	struct rlimit rlim;
252 	rlim.rlim_cur = rlim.rlim_max = 0;
253 	setrlimit(RLIMIT_CORE, &rlim);
254     }
255 #endif
256 }
257 
258 #ifndef SIG_IGN
SIG_IGN(int sig)259 static RETSIGTYPE SIG_IGN(int sig)
260 {
261     setsighandler(sig, SIG_IGN);  /* restore handler (POSIX) */
262 }
263 #endif
264 
handle_interrupt(void)265 static void handle_interrupt(void)
266 {
267     int c;
268 
269     VEC_CLR(SIGINT, &pending_signals);
270     /* so status line macros in setup_screen() aren't gratuitously killed */
271 
272     if (!interactive)
273         die("Interrupt, exiting.", 0);
274     reset_kbnum();
275     fix_screen();
276     puts("C) continue tf; X) exit; T) disable triggers; P) kill processes\r");
277     fflush(stdout);
278     c = igetchar();
279     if (ucase(c) == 'X')
280         die("Interrupt, exiting.", 0);
281     if (ucase(c) == 'T') {
282         set_var_by_id(VAR_borg, 0);
283         oputs("% Cyborg triggers disabled.");
284     } else if (ucase(c) == 'P') {
285         kill_procs();
286         oputs("% All processes killed.");
287     }
288     redraw();
289 }
290 
suspend(void)291 int suspend(void)
292 {
293 #if SIGTSTP
294     if (argv0[0] != '-' &&              /* not a login shell */
295 	parent_tstp_handler == SIG_DFL) /* parent process does job-control */
296     {
297         check_mail();
298         fix_screen();
299         reset_tty();
300         raise(SIGSTOP);
301         cbreak_noecho_mode();
302         get_window_size();
303         redraw();
304         check_mail();
305         return 1;
306     }
307 #endif
308     oputs("% Job control not available.");
309     return 0;
310 }
311 
312 
core_handler(int sig)313 static RETSIGTYPE core_handler(int sig)
314 {
315     FILE *dumpfile;
316     setsighandler(sig, core_handler);  /* restore handler (POSIX) */
317 
318     if (sig == SIGQUIT) {
319 	if (interactive) {
320 	    fix_screen();
321 #if DISABLE_CORE
322 	    puts("SIGQUIT received.  Exit?  (y/n)\r");
323 #else
324 	    puts("SIGQUIT received.  Dump core and exit?  (y/n)\r");
325 #endif
326 	    fflush(stdout);
327 	    if (igetchar() != 'y') {
328 		redraw();
329 		return;
330 	    }
331 	}
332         fputs("Abnormal termination - SIGQUIT\r\n", stderr);
333     }
334     setsighandler(sig, SIG_DFL);
335     if (sig != SIGQUIT) {
336         minimal_fix_screen();
337 	dumpfile = get_dumpfile();
338         coremsg(dumpfile);
339         fprintf(stderr, "> Abnormal termination - signal %d\r\n\n", sig);
340 	if (dumpfile != stderr)
341 	    fprintf(dumpfile, "> Abnormal termination - signal %d\r\n\n", sig);
342 	if (dumpfile != stderr)
343 	    fclose(dumpfile);
344 
345 	if (!debugger_dump()) {
346 #if DISABLE_CORE
347 	    fputs("Also, if you can, reinstall tf with --enable-core, "
348 		"attempt to reproduce the\r\n", stderr);
349 	    fputs("error, get a stack trace and send it to the author.\r\n",
350 		stderr);
351 #else /* cores are enabled */
352 	    fputs("Also, if you can, include a stack trace in your email.\r\n",
353 		stderr);
354 # ifdef PLATFORM_UNIX
355 	    fputs("To get a stack trace, do this:\r\n", stderr);
356 	    fputs("cd src\r\n", stderr);
357 	    fputs("script\r\n", stderr);
358 	    fputs("gdb -q tf   ;# if gdb is unavailable, use 'dbx tf' "
359 		"instead.\r\n", stderr);
360 	    fputs("run\r\n", stderr);
361 	    fputs("(do whatever is needed to reproduce the core dump)\r\n",
362 		stderr);
363 	    fputs("where\r\n", stderr);
364 	    fputs("quit\r\n", stderr);
365 	    fputs("exit\r\n", stderr);
366 	    fputs("\r\n", stderr);
367 	    fputs("Then include the \"typescript\" file in your email.\r\n",
368 		stderr);
369 	    fputs("\n", stderr);
370 # endif /* PLATFORM_UNIX */
371 #endif /* DISABLE_CORE */
372 	}
373     }
374 
375     if (interactive) {
376 	close_all();
377         fputs("\nPress any key.\r\n", stderr);
378         fflush(stderr);
379         igetchar();
380     }
381     reset_tty();
382 
383     raise(sig);
384 }
385 
crash(int internal,const char * fmt,const char * file,int line,long n)386 void crash(int internal, const char *fmt, const char *file, int line, long n)
387 {
388     FILE *dumpfile;
389     setsighandler(SIGQUIT, SIG_DFL);
390     minimal_fix_screen();
391     reset_tty();
392     dumpfile = get_dumpfile();
393     if (internal) coremsg(dumpfile);
394     fprintf(dumpfile, "> %s:  %s, line %d\r\n",
395         internal ? "Internal error" : "Aborting", file, line);
396     fputs("> ", dumpfile);
397     fprintf(dumpfile, fmt, n);
398     fputs("\r\n\n", dumpfile);
399     if (dumpfile != stderr)
400 	fclose(dumpfile);
401     debugger_dump();
402     raise(SIGQUIT);
403 }
404 
405 static char dumpname[32] = "................................";
406 static char exebuf[PATH_MAX+1];
407 static const char *initial_path = NULL;
408 static char initial_dir[PATH_MAX+1] = "."; /* default: many users never chdir */
409 
coremsg(FILE * dumpfile)410 static void coremsg(FILE *dumpfile)
411 {
412     fputs("Also describe what you were doing in tf when this\r\n", stderr);
413     fputs("occured, and whether you can repeat it.\r\n\n", stderr);
414     fprintf(dumpfile, "> %.512s\r\n", version);
415     if (*sysname) fprintf(dumpfile, "> %.256s\r\n", sysname);
416     fprintf(dumpfile, "> %.256s\r\n", featurestr->data);
417     fprintf(dumpfile,"> virtscreen=%ld, visual=%ld, expnonvis=%ld, "
418 	"emulation=%ld, lp=%ld, sub=%ld\r\n",
419         virtscreen, visual, expnonvis, emulation, lpflag, sub);
420 #if SOCKS
421     fprintf(dumpfile,"> SOCKS %d\r\n", SOCKS);
422 #endif
423     fprintf(dumpfile,"> TERM=\"%.32s\"\r\n", TERM ? TERM : "(NULL)");
424     fprintf(dumpfile,"> cmd=\"%.32s\"\r\n",
425 	current_command ? current_command : "(NULL)");
426     if (loadfile) {
427 	fprintf(dumpfile,"> line %d-%d of file \"%.32s\"\r\n",
428 	    loadstart, loadline,
429 	    loadfile->name ? loadfile->name : "(NULL)");
430     }
431 }
432 
init_exename(char * name)433 void init_exename(char *name)
434 {
435     argv0 = name;
436 #if HAVE_GETCWD
437     getcwd(initial_dir, PATH_MAX);
438 #elif HAVE_GETWD
439     getwd(initial_dir);
440 #endif
441     initial_path = getenv("PATH");
442 }
443 
get_dumpfile(void)444 static FILE *get_dumpfile(void)
445 {
446     FILE *file;
447     sprintf(dumpname, "tf.dump.%d.txt", getpid());
448     file = fopen(dumpname, "w");
449     if (!file) {
450 	fputs("\r\n\nPlease report the following message to the bug reporting "
451 	    "system at http://tinyfugue.sourceforge.net/\r\n"
452 	    "or by email to kenkeys@users.sourceforge.net.\r\n", stderr);
453 	return stderr;
454     } else {
455 	fprintf(stderr, "\r\n\nDumped debugging information to file '%s'.\r\n"
456 	    "Please submit this file to the bug reporting system at\r\n"
457 	    "http://tinyfugue.sourceforge.net/ or by email to kenkeys@users.sourceforge.net.\r\n",
458 	    dumpname);
459 	fputs("# TinyFugue debugging information\n\n", file);
460 	return file;
461     }
462 }
463 
464 #if defined(PLATFORM_UNIX) && HAVE_WAITPID
test_exename(const char * template,pid_t pid)465 static const char *test_exename(const char *template, pid_t pid)
466 {
467     struct stat statbuf;
468     sprintf(exebuf, template, pid);
469     return (stat(exebuf, &statbuf) == 0) ? exebuf : NULL;
470 }
471 
get_exename(pid_t pid)472 static const char *get_exename(pid_t pid)
473 {
474     const char *exename;
475     const char *dir;
476     size_t len;
477     struct stat statbuf;
478     /* a /proc entry is most reliable, if one exists */
479     if ((exename = test_exename("/proc/%d/file", pid)) ||       /* *BSD */
480 	(exename = test_exename("/proc/%d/exe", pid)) ||        /* Linux */
481 	(exename = test_exename("/proc/%d/object/a.out", pid))) /* Solaris */
482     {
483 	return exename;
484     }
485     /* else use argv[0]:
486 	if it starts with "/", use it directly;
487 	else if it contains "/", it's relative to initial working dir;
488 	else, search for it in initial $PATH
489     */
490     if (!argv0) {
491 	return NULL;
492     }
493     if (argv0[0] == '/') {
494 	return argv0;
495     }
496     if (strchr(argv0, '/')) {
497 	sprintf(exebuf, "%s/%s", initial_dir, argv0);
498 	return exebuf;
499     }
500     if (!initial_path || !*initial_path)
501 	return NULL;
502     dir = initial_path;
503     while (1) {
504 	len = strcspn(dir, ":\0");
505 	if (*dir == '/')
506 	    sprintf(exebuf, "%.*s/%s", len, dir, argv0);
507 	else
508 	    sprintf(exebuf, "%s/%.*s/%s", initial_dir, len, dir, argv0);
509 	if (stat(exebuf, &statbuf) == 0)
510 	    return exebuf;
511 	if (!dir[len])
512 	    break;
513 	dir += len + 1;
514     }
515 
516     return NULL;
517 }
518 
519 /* Inspired by Jeff Brown */
debugger_dump(void)520 static int debugger_dump(void)
521 {
522     pid_t tf_pid = getpid();
523 
524     const char *exename;
525 
526     if ((exename = get_exename(tf_pid))) {
527 	pid_t child_pid;
528 	child_pid = fork();
529 	if (child_pid < 0) {
530 	    /* error */
531 	    fprintf(stderr, "fork: %s\r\n", strerror(errno));
532 	} else if (child_pid > 0) {
533 	    /* parent */
534 	    pid_t wait_pid = 0;
535 	    int status = 0;
536 	    wait_pid = waitpid(child_pid, &status, 0);
537 	    if (shell_status(status) == 0) {
538 		return 1;
539 	    } else {
540 		unlink(dumpname);
541 	    }
542 	} else {
543 	    /* child */
544 	    char inname[1024];
545 	    char cmd[2048];
546 	    int retval;
547 	    sprintf(inname, "%.1000s/tf.gdb", TFLIBDIR);
548 	    sprintf(cmd, "chmod go-rwx %s; gdb -n -batch -x %s '%s' %d "
549 		">>%s 2>&1", dumpname, inname, exename, tf_pid, dumpname);
550 	    retval = system(cmd);
551 	    exit(shell_status(retval) == 0 ? 0 : 1);
552 	}
553     }
554     return 0;
555 }
556 
557 #else /* !PLATFORM_UNIX */
debugger_dump(void)558 static int debugger_dump(void) { return 0; }
559 #endif /* PLATFORM_UNIX */
560 
terminate(int sig)561 static void terminate(int sig)
562 {
563     setsighandler(sig, SIG_DFL);
564     fix_screen();
565     reset_tty();
566     fprintf(stderr, "Terminating - signal %d\r\n", sig);
567     raise(sig);
568 }
569 
signal_scheduler(int sig)570 static RETSIGTYPE signal_scheduler(int sig)
571 {
572     setsighandler(sig, signal_scheduler);  /* restore handler (POSIX) */
573     VEC_SET(sig, &pending_signals);        /* set flag to deal with it later */
574     have_pending_signals++;
575 }
576 
signal_jumper(int sig)577 static RETSIGTYPE signal_jumper(int sig)
578 {
579     fatal_signal = sig;
580     longjmp(jumpenv, 1);
581     /* don't need to restore handler */
582 }
583 
process_signals(void)584 void process_signals(void)
585 {
586     if (!have_pending_signals) return;
587     if (VEC_ISSET(SIGINT, &pending_signals))   handle_interrupt();
588     if (VEC_ISSET(SIGTSTP, &pending_signals))  suspend();
589     if (VEC_ISSET(SIGWINCH, &pending_signals))
590         if (!get_window_size()) operror("TIOCGWINSZ ioctl");
591     if (VEC_ISSET(SIGHUP, &pending_signals))   do_hook(H_SIGHUP, NULL, "");
592     if (VEC_ISSET(SIGTERM, &pending_signals))  do_hook(H_SIGTERM, NULL, "");
593     if (VEC_ISSET(SIGUSR1, &pending_signals))  do_hook(H_SIGUSR1, NULL, "");
594     if (VEC_ISSET(SIGUSR2, &pending_signals))  do_hook(H_SIGUSR2, NULL, "");
595 
596     if (VEC_ISSET(SIGHUP, &pending_signals) && old_sighup_handler == SIG_DFL)
597 	terminate(SIGHUP);
598     if (VEC_ISSET(SIGTERM, &pending_signals))
599 	terminate(SIGTERM);
600 
601     have_pending_signals = 0;
602     VEC_ZERO(&pending_signals);
603 }
604 
interrupted(void)605 int interrupted(void)
606 {
607     return VEC_ISSET(SIGINT, &pending_signals);
608 }
609 
shell_status(int result)610 int shell_status(int result)
611 {
612     /* If the next line causes errors like "request for member `w_S' in
613      * something not a structure or union", then <sys/wait.h> must have
614      * defined WIFEXITED and WEXITSTATUS incorrectly (violating Posix.1).
615      * The workaround is to not #include <sys/wait.h> at the top of this
616      * file, so we can use our own definitions.
617      */
618     return (WIFEXITED(result)) ? WEXITSTATUS(result) : -1;
619 }
620 
shell(const char * cmd)621 int shell(const char *cmd)
622 {
623     int result;
624 
625     check_mail();
626     fix_screen();
627     reset_tty();
628     setsighandler(SIGTSTP, parent_tstp_handler);
629     result = system(cmd);
630     setsighandler(SIGTSTP, signal_scheduler);
631     cbreak_noecho_mode();
632     if (result == -1) {
633         eprintf("%s", strerror(errno));
634     } else if (shpause && interactive) {
635         puts("\r\n% Press any key to continue tf.\r");
636         igetchar();
637     }
638     get_window_size();
639     redraw();
640     if (result == -1) return result;
641     check_mail();
642 #ifdef PLATFORM_OS2
643     return result;
644 #else /* UNIX */
645     return shell_status(result);
646 #endif
647 }
648