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