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