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