1 /*
2 * Project : tin - a Usenet reader
3 * Module : signal.c
4 * Author : I.Lea
5 * Created : 1991-04-01
6 * Updated : 2019-07-17
7 * Notes : signal handlers for different modes and window resizing
8 *
9 * Copyright (c) 1991-2021 Iain Lea <iain@bricbrac.de>
10 * All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 *
16 * 1. Redistributions of source code must retain the above copyright notice,
17 * this list of conditions and the following disclaimer.
18 *
19 * 2. Redistributions in binary form must reproduce the above copyright
20 * notice, this list of conditions and the following disclaimer in the
21 * documentation and/or other materials provided with the distribution.
22 *
23 * 3. Neither the name of the copyright holder nor the names of its
24 * contributors may be used to endorse or promote products derived from
25 * this software without specific prior written permission.
26 *
27 * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
31 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37 * POSSIBILITY OF SUCH DAMAGE.
38 */
39
40
41 #ifndef TIN_H
42 # include "tin.h"
43 #endif /* !TIN_H */
44 #ifndef TCURSES_H
45 # include "tcurses.h"
46 #endif /* !TCURSES_H */
47 #ifndef included_trace_h
48 # include "trace.h"
49 #endif /* !included_trace_h */
50 #ifndef VERSION_H
51 # include "version.h"
52 #endif /* !VERSION_H */
53
54 /*
55 * Needed for resizing under an xterm
56 */
57 #ifdef HAVE_TERMIOS_H
58 # include <termios.h>
59 #else
60 # ifdef HAVE_TERMIO_H
61 # include <termio.h>
62 # endif /* HAVE_TERMIO_H */
63 #endif /* HAVE_TERMIOS_H */
64
65 #ifdef NEED_PTEM_H
66 # include <sys/stream.h>
67 # include <sys/ptem.h>
68 #endif /* NEED_PTEM_H */
69
70 #if defined(SIGWINCH) && !defined(DONT_HAVE_SIGWINCH)
71 # if !defined(TIOCGWINSZ) && !defined(TIOCGSIZE)
72 # ifdef HAVE_SYS_STREAM_H
73 # include <sys/stream.h>
74 # endif /* HAVE_SYS_STREAM_H */
75 # ifdef HAVE_SYS_PTY_H
76 # if !defined(_h_BSDTYPES) && defined(HAVE_SYS_BSDTYPES_H)
77 # include <sys/bsdtypes.h>
78 # endif /* !_h_BSDTYPES && HAVE_SYS_BSDTYPES_H */
79 # include <sys/pty.h>
80 # endif /* HAVE_SYS_PTY_H */
81 # endif /* !TIOCGWINSZ && !TIOCGSIZE */
82 #endif /* SIGWINCH && !DONT_HAVE_SIGWINCH */
83
84 #ifdef MINIX
85 # undef SIGTSTP
86 #endif /* MINIX */
87
88 /*
89 * local prototypes
90 */
91 static const char *signal_name(int code);
92 #ifdef SIGTSTP
93 static void handle_suspend(void);
94 #endif /* SIGTSTP */
95 static void _CDECL signal_handler(SIG_ARGS);
96
97
98 #ifdef SIGTSTP
99 static t_bool do_sigtstp = FALSE;
100 #endif /* SIGTSTP */
101 #if defined(SIGWINCH) || defined(SIGTSTP)
102 static t_bool redraw_after_suspend;
103 #endif /* SIGWINCH || SIGTSTP */
104
105 int signal_context = cMain;
106 int input_context = cNone;
107 int need_resize = cNo;
108 /*
109 * # lines of non-static data available for display
110 */
111 int NOTESLINES;
112
113
114 #ifndef __LCLINT__ /* lclint doesn't like it */
115 static const struct {
116 int code;
117 const char *name;
118 } signal_list[] = {
119 # ifdef SIGINT
120 { SIGINT, "SIGINT" }, /* ctrl-C */
121 # endif /* SIGINT */
122 # ifdef SIGQUIT
123 { SIGQUIT, "SIGQUIT" }, /* ctrl-\ */
124 # endif /* SIGQUIT */
125 # ifdef SIGILL
126 { SIGILL, "SIGILL" }, /* illegal instruction */
127 # endif /* SIGILL */
128 # ifdef SIGFPE
129 { SIGFPE, "SIGFPE" }, /* floating point exception */
130 # endif /* SIGFPE */
131 # ifdef SIGBUS
132 { SIGBUS, "SIGBUS" }, /* bus error */
133 # endif /* SIGBUS */
134 # ifdef SIGSEGV
135 { SIGSEGV, "SIGSEGV" }, /* segmentation violation */
136 # endif /* SIGSEGV */
137 # ifdef SIGPIPE
138 { SIGPIPE, "SIGPIPE" }, /* broken pipe */
139 # endif /* SIGPIPE */
140 # ifdef SIGALRM
141 { SIGALRM, "SIGALRM" }, /* real-time timer expired */
142 # endif /* SIGALRM */
143 # ifdef SIGCHLD
144 { SIGCHLD, "SIGCHLD" }, /* death of a child process */
145 # endif /* SIGCHLD */
146 # ifdef SIGPWR
147 { SIGPWR, "SIGPWR" }, /* powerfail */
148 # endif /* SIGPWR */
149 # ifdef SIGTSTP
150 { SIGTSTP, "SIGTSTP" }, /* terminal-stop */
151 # endif /* SIGTSTP */
152 # ifdef SIGHUP
153 { SIGHUP, "SIGHUP" }, /* hang up */
154 # endif /* SIGHUP */
155 # ifdef SIGUSR1
156 { SIGUSR1, "SIGUSR1" }, /* User-defined signal 1 */
157 # endif /* SIGUSR1 */
158 # ifdef SIGUSR2
159 { SIGUSR2, "SIGUSR2" }, /* User-defined signal 2 */
160 # endif /* SIGUSR2 */
161 # ifdef SIGTERM
162 { SIGTERM, "SIGTERM" }, /* termination */
163 # endif /* SIGTERM */
164 # if defined(SIGWINCH) && !(defined(USE_CURSES) && defined(KEY_RESIZE))
165 { SIGWINCH, "SIGWINCH" }, /* window-size change */
166 # endif /* SIGWINCH && !(USE_CURSES && KEY_RESIZE) */
167 };
168 #endif /* !__LCLINT__ */
169
170
171 #ifdef HAVE_NESTED_PARAMS
sigdisp(int signum,RETSIGTYPE (_CDECL * func)(SIG_ARGS))172 RETSIGTYPE (*sigdisp(int signum, RETSIGTYPE (_CDECL *func)(SIG_ARGS)))(SIG_ARGS)
173 #else
174 RETSIGTYPE (*sigdisp(signum, func))(SIG_ARGS)
175 int signum;
176 RETSIGTYPE (_CDECL *func)(SIG_ARGS);
177 #endif /* HAVE_NESTED_PARAMS */
178 {
179 #ifdef HAVE_POSIX_JC
180 # define RESTORE_HANDLER(x, y)
181 struct sigaction sa, osa;
182
183 sa.sa_handler = func;
184 sigemptyset(&sa.sa_mask);
185 sa.sa_flags = 0;
186 # ifdef SA_RESTART
187 sa.sa_flags |= SA_RESTART;
188 # endif /* SA_RESTART */
189 if (sigaction(signum, &sa, &osa) < 0)
190 return SIG_ERR;
191 return (osa.sa_handler);
192 #else
193 # define RESTORE_HANDLER(x, y) signal(x, y)
194 return (signal(signum, func));
195 #endif /* HAVE_POSIX_JC */
196 }
197
198
199 /*
200 * Block/unblock SIGWINCH/SIGTSTP restarting syscalls
201 */
202 void
allow_resize(t_bool allow)203 allow_resize(
204 t_bool allow)
205 {
206 #ifdef HAVE_POSIX_JC
207 struct sigaction sa, osa;
208
209 sa.sa_handler = signal_handler;
210 sigemptyset(&sa.sa_mask);
211 sa.sa_flags = 0;
212 # ifdef SA_RESTART
213 if (!allow)
214 sa.sa_flags |= SA_RESTART;
215 # endif /* SA_RESTART */
216 # if defined(SIGWINCH) && !(defined(USE_CURSES) && defined(KEY_RESIZE))
217 sigaction(SIGWINCH, &sa, &osa);
218 # endif /* SIGWINCH && !(USE_CURSES && KEY_RESIZE) */
219 # ifdef SIGTSTP
220 sigaction(SIGTSTP, &sa, &osa);
221 # endif /* SIGTSTP */
222 #endif /* HAVE_POSIX_JC */
223 }
224
225
226 static const char *
signal_name(int code)227 signal_name(
228 int code)
229 {
230 size_t n;
231 const char *name = "unknown";
232
233 for (n = 0; n < ARRAY_SIZE(signal_list); n++) {
234 if (signal_list[n].code == code) {
235 name = signal_list[n].name;
236 break;
237 }
238 }
239 return name;
240 }
241
242
243 /*
244 * Rescale the display buffer and redraw the contents according to
245 * the current context
246 * This should NOT be called from an interrupt context
247 */
248 void
handle_resize(t_bool repaint)249 handle_resize(
250 t_bool repaint)
251 {
252 #if defined(SIGWINCH) || defined(SIGTSTP)
253 # ifdef SIGWINCH
254 repaint |= set_win_size(&cLINES, &cCOLS);
255 # endif /* SIGWINCH */
256
257 if (cLINES < MIN_LINES_ON_TERMINAL || cCOLS < MIN_COLUMNS_ON_TERMINAL) {
258 ring_bell();
259 tin_done(EXIT_FAILURE, _(txt_screen_too_small_exiting), tin_progname);
260 }
261
262 TRACE(("handle_resize(%d:%d)", signal_context, repaint));
263
264 if (!repaint)
265 return;
266
267 # ifdef USE_CURSES
268 # ifdef HAVE_RESIZETERM
269 resizeterm(cLINES + 1, cCOLS);
270 my_retouch(); /* seems necessary if win size unchanged */
271 # else
272 my_retouch();
273 # endif /* HAVE_RESIZETERM */
274 # endif /* USE_CURSES */
275
276 switch (signal_context) {
277 case cArt:
278 ClearScreen();
279 show_art_msg(CURR_GROUP.name);
280 break;
281
282 case cAttrib:
283 case cConfig:
284 refresh_config_page(SIGNAL_HANDLER);
285 break;
286
287 case cFilter:
288 refresh_filter_menu();
289 break;
290
291 case cInfopager:
292 display_info_page(0);
293 break;
294
295 case cAttachment:
296 case cGroup:
297 case cScope:
298 case cSelect:
299 case cThread:
300 case cURL:
301 ClearScreen();
302 currmenu->redraw();
303 break;
304
305 case cPage:
306 resize_article(TRUE, &pgart);
307 draw_page(curr_group->name, 0);
308 break;
309
310 case cPost:
311 case cPostCancel:
312 refresh_post_screen(signal_context);
313 break;
314
315 case cPostFup:
316 resize_article(TRUE, &pgart);
317 draw_page(curr_group->name, 0);
318 /*
319 * Reset signal_context because draw_page()
320 * sets signal_context to cPage.
321 */
322 signal_context = cPostFup;
323 refresh_post_screen(signal_context);
324 break;
325
326 case cReconnect:
327 ClearScreen();
328 show_title(tin_progname);
329 break;
330
331 case cMain:
332 break;
333 }
334 switch (input_context) {
335 case cGetline:
336 gl_redraw();
337 break;
338
339 case cPromptCONT:
340 if (redraw_after_suspend)
341 info_message(_(txt_return_key));
342 break;
343
344 case cPromptSLK:
345 prompt_slk_redraw();
346 break;
347
348 case cPromptYN:
349 prompt_yn_redraw();
350 break;
351
352 default:
353 break;
354 }
355 my_fflush(stdout);
356 redraw_after_suspend = FALSE;
357 #endif /* SIGWINCH || SIGTSTP */
358 }
359
360
361 #ifdef SIGTSTP
362 static void
handle_suspend(void)363 handle_suspend(
364 void)
365 {
366 t_bool save_cmd_line = cmd_line;
367 t_bool save_state = (!batch_mode || !cmd_line);
368
369 TRACE(("handle_suspend(%d)", signal_context));
370
371 set_keypad_off();
372 if (!cmd_line)
373 set_xclick_off();
374
375 if (save_state) {
376 EndWin();
377 Raw(FALSE);
378 }
379
380 wait_message(0, _(txt_suspended_message), tin_progname);
381
382 kill(0, SIGSTOP); /* Put ourselves to sleep */
383
384 RESTORE_HANDLER(SIGTSTP, signal_handler);
385
386 if (save_state) {
387 Raw(TRUE);
388 InitWin();
389 cmd_line = save_cmd_line;
390 if (!cmd_line)
391 my_retouch();
392 need_resize = cRedraw; /* Flag a redraw */
393 redraw_after_suspend = TRUE;
394 }
395 set_keypad_on();
396 if (!cmd_line)
397 set_xclick_on();
398 }
399 #endif /* SIGTSTP */
400
401
402 static void _CDECL
signal_handler(int sig)403 signal_handler(
404 int sig)
405 {
406 #ifdef SIGCHLD
407 # ifdef HAVE_TYPE_UNIONWAIT
408 union wait wait_status;
409 # else
410 int wait_status = 1;
411 # endif /* HAVE_TYPE_UNIONWAIT */
412 #endif /* SIGCHLD */
413
414 /* In this case statement, we handle only the non-fatal signals */
415 switch (sig) {
416 #ifdef SIGINT
417 case SIGINT:
418 RESTORE_HANDLER(sig, signal_handler);
419 return;
420 #endif /* SIGINT */
421
422 /*
423 * fatal error but we don't want the "signal handler caught signal"
424 * message here
425 */
426 #if defined(HAVE_ALARM) && defined(SIGALRM)
427 case SIGALRM:
428 # ifdef DEBUG
429 if ((debug & DEBUG_NNTP) && verbose > 1)
430 debug_print_file("NNTP", "get_server() %d sec elapsed without response", tinrc.nntp_read_timeout_secs);
431 # endif /* DEBUG */
432 tin_done(NNTP_ERROR_EXIT, _("NNTP connection error. Exiting..."));
433 return;
434 #endif /* HAVE_ALARM && SIGALRM */
435
436 #ifdef SIGCHLD
437 case SIGCHLD:
438 wait(&wait_status);
439 RESTORE_HANDLER(sig, signal_handler); /* death of a child */
440 system_status = WIFEXITED(wait_status) ? WEXITSTATUS(wait_status) : 0;
441 return;
442 #endif /* SIGCHLD */
443
444 #ifdef SIGTSTP
445 case SIGTSTP:
446 handle_suspend();
447 return;
448 #endif /* SIGTSTP */
449
450 #ifdef SIGWINCH
451 case SIGWINCH:
452 need_resize = cYes;
453 RESTORE_HANDLER(sig, signal_handler);
454 return;
455 #endif /* SIGWINCH */
456
457 #ifdef SIGUSR2
458 case SIGUSR2:
459 if (!no_write) /* TODO: add more config-files to be saved */
460 write_newsrc();
461 RESTORE_HANDLER(sig, signal_handler);
462 return;
463 #endif /* SIGUSR2 */
464
465 default:
466 break;
467 }
468
469 fprintf(stderr, "\n%s: signal handler caught %s signal (%d).\n", tin_progname, signal_name(sig), sig);
470
471 switch (sig) {
472 #ifdef SIGHUP
473 case SIGHUP:
474 #endif /* SIGHUP */
475 #ifdef SIGUSR1
476 case SIGUSR1:
477 #endif /* SIGUSR1 */
478 #ifdef SIGTERM
479 case SIGTERM:
480 #endif /* SIGTERM */
481 #if defined(SIGHUP) || defined(SIGUSR1) || defined(SIGTERM)
482 dangerous_signal_exit = TRUE;
483 tin_done(-sig, NULL);
484 /* NOTREACHED */
485 break;
486 #endif /* SIGHUP || SIGUSR1 || SIGTERM */
487
488 #ifdef SIGSEGV
489 case SIGSEGV:
490 # if defined(SIGBUS) && (SIGSEGV != SIGBUS) /* on Haiku SIGSEGV == SIGBUS */
491 case SIGBUS:
492 # endif /* SIGBUS && SIGSEGV != SIGBUS */
493 #else
494 # ifdef SIGBUS
495 case SIGBUS:
496 # endif /* SIGBUS */
497 #endif /* SIGSEGV */
498
499 #if defined(SIGBUS) || defined(SIGSEGV)
500 my_fprintf(stderr, _(txt_send_bugreport), tin_progname, VERSION, RELEASEDATE, RELEASENAME, bug_addr);
501 my_fflush(stderr);
502 break;
503 #endif /* SIGBUS || SIGSEGV */
504
505 default:
506 break;
507 }
508
509 cleanup_tmp_files();
510
511 #if 1
512 /* #if defined(apollo) || defined(HAVE_COREFILE) */
513 /* do this so we can get a traceback (doesn't dump core) */
514 abort();
515 #else
516 giveup();
517 #endif /* 1 */ /* apollo || HAVE_COREFILE */
518 }
519
520
521 /*
522 * Turn on (flag != FALSE) our signal handler for TSTP and WINCH
523 * Otherwise revert to the default handler
524 */
525 void
set_signal_catcher(int flag)526 set_signal_catcher(
527 int flag)
528 {
529 #ifdef SIGTSTP
530 if (do_sigtstp)
531 sigdisp(SIGTSTP, flag ? signal_handler : SIG_DFL);
532 #endif /* SIGTSTP */
533
534 #if defined(SIGWINCH) && !(defined(USE_CURSES) && defined(KEY_RESIZE))
535 sigdisp(SIGWINCH, flag ? signal_handler : SIG_DFL);
536 #endif /* SIGWINCH && !(USE_CURSES && KEY_RESIZE) */
537 }
538
539
540 void
set_signal_handlers(void)541 set_signal_handlers(
542 void)
543 {
544 size_t n;
545 int code;
546 #ifdef SIGTSTP
547 RETSIGTYPE (*ptr)(SIG_ARGS);
548 #endif /* SIGTSTP */
549
550 for (n = 0; n < ARRAY_SIZE(signal_list); n++) {
551 switch ((code = signal_list[n].code)) {
552 #ifdef SIGPIPE
553 case SIGPIPE:
554 sigdisp(code, SIG_IGN);
555 break;
556 #endif /* SIGPIPE */
557 #ifdef SIGTSTP
558 case SIGTSTP:
559 ptr = sigdisp(code, SIG_DFL);
560 sigdisp(code, ptr);
561 if (ptr == SIG_IGN)
562 break;
563 /*
564 * SIGTSTP is ignored when starting from shells
565 * without job-control
566 */
567 do_sigtstp = TRUE;
568 /* FALLTHROUGH */
569 #endif /* SIGTSTP */
570
571 default:
572 sigdisp(code, signal_handler);
573 }
574 }
575 }
576
577
578 /*
579 * Size the display at startup or rescale following a SIGWINCH etc.
580 */
581 t_bool
set_win_size(int * num_lines,int * num_cols)582 set_win_size(
583 int *num_lines,
584 int *num_cols)
585 {
586 int old_lines;
587 int old_cols;
588 #ifdef TIOCGSIZE
589 struct ttysize win;
590 #else
591 # ifdef TIOCGWINSZ
592 struct winsize win;
593 # endif /* TIOCGWINSZ */
594 #endif /* TIOCGSIZE */
595
596 old_lines = *num_lines;
597 old_cols = *num_cols;
598
599 #ifdef HAVE_XCURSES
600 *num_lines = LINES - 1; /* FIXME */
601 *num_cols = COLS;
602 #else /* curses/ncurses */
603
604 # ifndef USE_CURSES
605 init_screen_array(FALSE); /* deallocate screen array */
606 # endif /* !USE_CURSES */
607
608 # ifdef TIOCGSIZE
609 if (ioctl(0, TIOCGSIZE, &win) == 0) {
610 if (win.ts_lines != 0)
611 *num_lines = win.ts_lines - 1;
612 if (win.ts_cols != 0)
613 *num_cols = win.ts_cols;
614 }
615 # else
616 # ifdef TIOCGWINSZ
617 if (ioctl(0, TIOCGWINSZ, &win) == 0) {
618 if (win.ws_row != 0)
619 *num_lines = win.ws_row - 1;
620 if (win.ws_col != 0)
621 *num_cols = win.ws_col;
622 }
623 # else
624 # endif /* TIOCGWINSZ */
625 # endif /* TIOCGSIZE */
626
627 # ifndef USE_CURSES
628 init_screen_array(TRUE); /* allocate screen array for resize */
629 # endif /* !USE_CURSES */
630
631 #endif /* HAVE_XCURSES */
632
633 set_noteslines(*num_lines);
634 return (*num_lines != old_lines || *num_cols != old_cols);
635 }
636
637
638 void
set_noteslines(int num_lines)639 set_noteslines(
640 int num_lines)
641 {
642 NOTESLINES = num_lines - INDEX_TOP - (tinrc.beginner_level ? MINI_HELP_LINES : 1);
643 if (NOTESLINES <= 0)
644 NOTESLINES = 1;
645 }
646