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