1 /**************************************************************************** 2 * Copyright 2020 Thomas E. Dickey * 3 * Copyright 1998-2014,2017 Free Software Foundation, Inc. * 4 * * 5 * Permission is hereby granted, free of charge, to any person obtaining a * 6 * copy of this software and associated documentation files (the * 7 * "Software"), to deal in the Software without restriction, including * 8 * without limitation the rights to use, copy, modify, merge, publish, * 9 * distribute, distribute with modifications, sublicense, and/or sell * 10 * copies of the Software, and to permit persons to whom the Software is * 11 * furnished to do so, subject to the following conditions: * 12 * * 13 * The above copyright notice and this permission notice shall be included * 14 * in all copies or substantial portions of the Software. * 15 * * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * 17 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * 19 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * 20 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * 21 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * 22 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 23 * * 24 * Except as contained in this notice, the name(s) of the above copyright * 25 * holders shall not be used in advertising or otherwise to promote the * 26 * sale, use or other dealings in this Software without prior written * 27 * authorization. * 28 ****************************************************************************/ 29 30 /**************************************************************************** 31 * Author: Zeyd M. Ben-Halim <zmbenhal@netcom.com> 1992,1995 * 32 * and: Eric S. Raymond <esr@snark.thyrsus.com> * 33 * and: Thomas E. Dickey 1995-on * 34 ****************************************************************************/ 35 36 /* 37 ** lib_tstp.c 38 ** 39 ** The routine _nc_signal_handler(). 40 ** 41 */ 42 #include <curses.priv.h> 43 44 #include <SigAction.h> 45 46 MODULE_ID("$Id: lib_tstp.c,v 1.50 2020/02/02 23:34:34 tom Exp $") 47 48 #if defined(SIGTSTP) && (HAVE_SIGACTION || HAVE_SIGVEC) 49 #define USE_SIGTSTP 1 50 #else 51 #define USE_SIGTSTP 0 52 #endif 53 54 #ifdef TRACE 55 static const char * 56 signal_name(int sig) 57 { 58 switch (sig) { 59 #ifdef SIGALRM 60 case SIGALRM: 61 return "SIGALRM"; 62 #endif 63 #ifdef SIGCONT 64 case SIGCONT: 65 return "SIGCONT"; 66 #endif 67 case SIGINT: 68 return "SIGINT"; 69 #ifdef SIGQUIT 70 case SIGQUIT: 71 return "SIGQUIT"; 72 #endif 73 case SIGTERM: 74 return "SIGTERM"; 75 #ifdef SIGTSTP 76 case SIGTSTP: 77 return "SIGTSTP"; 78 #endif 79 #ifdef SIGTTOU 80 case SIGTTOU: 81 return "SIGTTOU"; 82 #endif 83 #ifdef SIGWINCH 84 case SIGWINCH: 85 return "SIGWINCH"; 86 #endif 87 default: 88 return "unknown signal"; 89 } 90 } 91 #endif 92 93 /* 94 * Note: This code is fragile! Its problem is that different OSs 95 * handle restart of system calls interrupted by signals differently. 96 * The ncurses code needs signal-call restart to happen -- otherwise, 97 * interrupted wgetch() calls will return FAIL, probably making the 98 * application think the input stream has ended and it should 99 * terminate. In particular, you know you have this problem if, when 100 * you suspend an ncurses-using lynx with ^Z and resume, it dies 101 * immediately. 102 * 103 * Default behavior of POSIX sigaction(2) is not to restart 104 * interrupted system calls, but Linux's sigaction does it anyway (at 105 * least, on and after the 1.1.47 I (esr) use). Thus this code works 106 * OK under Linux. The 4.4BSD sigaction(2) supports a (non-portable) 107 * SA_RESTART flag that forces the right behavior. Thus, this code 108 * should work OK under BSD/OS, NetBSD, and FreeBSD (let us know if it 109 * does not). 110 * 111 * Stock System Vs (and anything else using a strict-POSIX 112 * sigaction(2) without SA_RESTART) may have a problem. Possible 113 * solutions: 114 * 115 * sigvec restarts by default (SV_INTERRUPT flag to not restart) 116 * signal restarts by default in SVr4 (assuming you link with -lucb) 117 * and BSD, but not SVr3. 118 * sigset restarts, but is only available under SVr4/Solaris. 119 * 120 * The signal(3) call is mandated by the ANSI standard, and its 121 * interaction with sigaction(2) is described in the POSIX standard 122 * (3.3.4.2, page 72,line 934). According to section 8.1, page 191, 123 * however, signal(3) itself is not required by POSIX.1. And POSIX is 124 * silent on whether it is required to restart signals. 125 * 126 * So. The present situation is, we use sigaction(2) with no 127 * guarantee of restart anywhere but on Linux and BSD. We could 128 * switch to signal(3) and collar Linux, BSD, and SVr4. Any way 129 * we slice it, System V UNIXes older than SVr4 will probably lose 130 * (this may include XENIX). 131 * 132 * This implementation will probably be changed to use signal(3) in 133 * the future. If nothing else, it's simpler... 134 */ 135 136 #if USE_SIGTSTP 137 static void 138 handle_SIGTSTP(int dummy GCC_UNUSED) 139 { 140 SCREEN *sp = CURRENT_SCREEN; 141 sigset_t mask, omask; 142 sigaction_t act, oact; 143 144 #ifdef SIGTTOU 145 int sigttou_blocked; 146 #endif 147 148 _nc_globals.have_sigtstp = 1; 149 T(("handle_SIGTSTP() called")); 150 151 /* 152 * The user may have changed the prog_mode tty bits, so save them. 153 * 154 * But first try to detect whether we still are in the foreground 155 * process group - if not, an interactive shell may already have 156 * taken ownership of the tty and modified the settings when our 157 * parent was stopped before us, and we would likely pick up the 158 * settings already modified by the shell. 159 * 160 * Don't do this if we're not in curses - 161 */ 162 if (sp != 0 && (sp->_endwin == ewRunning)) 163 #if HAVE_TCGETPGRP 164 if (tcgetpgrp(STDIN_FILENO) == getpgrp()) 165 #endif 166 NCURSES_SP_NAME(def_prog_mode) (NCURSES_SP_ARG); 167 168 /* 169 * Block window change and timer signals. The latter 170 * is because applications use timers to decide when 171 * to repaint the screen. 172 */ 173 (void) sigemptyset(&mask); 174 #ifdef SIGALRM 175 (void) sigaddset(&mask, SIGALRM); 176 #endif 177 #if USE_SIGWINCH 178 (void) sigaddset(&mask, SIGWINCH); 179 #endif 180 (void) sigprocmask(SIG_BLOCK, &mask, &omask); 181 182 #ifdef SIGTTOU 183 sigttou_blocked = sigismember(&omask, SIGTTOU); 184 if (!sigttou_blocked) { 185 (void) sigemptyset(&mask); 186 (void) sigaddset(&mask, SIGTTOU); 187 (void) sigprocmask(SIG_BLOCK, &mask, NULL); 188 } 189 #endif 190 191 /* 192 * End window mode, which also resets the terminal state to the 193 * original (pre-curses) modes. 194 */ 195 NCURSES_SP_NAME(endwin) (NCURSES_SP_ARG); 196 197 /* Unblock SIGTSTP. */ 198 (void) sigemptyset(&mask); 199 (void) sigaddset(&mask, SIGTSTP); 200 #ifdef SIGTTOU 201 if (!sigttou_blocked) { 202 /* Unblock this too if it wasn't blocked on entry */ 203 (void) sigaddset(&mask, SIGTTOU); 204 } 205 #endif 206 (void) sigprocmask(SIG_UNBLOCK, &mask, NULL); 207 208 /* Now we want to resend SIGSTP to this process and suspend it */ 209 act.sa_handler = SIG_DFL; 210 sigemptyset(&act.sa_mask); 211 act.sa_flags = 0; 212 #ifdef SA_RESTART 213 act.sa_flags |= SA_RESTART; 214 #endif /* SA_RESTART */ 215 sigaction(SIGTSTP, &act, &oact); 216 kill(getpid(), SIGTSTP); 217 218 /* Process gets suspended...time passes...process resumes */ 219 220 T(("SIGCONT received")); 221 sigaction(SIGTSTP, &oact, NULL); 222 NCURSES_SP_NAME(flushinp) (NCURSES_SP_ARG); 223 224 /* 225 * If the user modified the tty state while suspended, he wants 226 * those changes to stick. So save the new "default" terminal state. 227 */ 228 NCURSES_SP_NAME(def_shell_mode) (NCURSES_SP_ARG); 229 230 /* 231 * This relies on the fact that doupdate() will restore the 232 * program-mode tty state, and issue enter_ca_mode if need be. 233 */ 234 NCURSES_SP_NAME(doupdate) (NCURSES_SP_ARG); 235 236 /* Reset the signals. */ 237 (void) sigprocmask(SIG_SETMASK, &omask, NULL); 238 } 239 #endif /* USE_SIGTSTP */ 240 241 static void 242 handle_SIGINT(int sig) 243 { 244 SCREEN *sp = CURRENT_SCREEN; 245 246 /* 247 * Much of this is unsafe from a signal handler. But we'll _try_ to clean 248 * up the screen and terminal settings on the way out. 249 * 250 * There are at least the following problems: 251 * 1) Walking the SCREEN list is unsafe, since all list management 252 * is done without any signal blocking. 253 * 2) On systems which have REENTRANT turned on, set_term() uses 254 * _nc_lock_global() which could deadlock or misbehave in other ways. 255 * 3) endwin() calls all sorts of stuff, many of which use stdio or 256 * other library functions which are clearly unsafe. 257 */ 258 if (!_nc_globals.cleanup_nested++ 259 && (sig == SIGINT || sig == SIGTERM)) { 260 #if HAVE_SIGACTION || HAVE_SIGVEC 261 sigaction_t act; 262 sigemptyset(&act.sa_mask); 263 act.sa_flags = 0; 264 act.sa_handler = SIG_IGN; 265 if (sigaction(sig, &act, NULL) == 0) 266 #else 267 if (signal(sig, SIG_IGN) != SIG_ERR) 268 #endif 269 { 270 SCREEN *scan; 271 for (each_screen(scan)) { 272 if (scan->_ofp != 0 273 && NC_ISATTY(fileno(scan->_ofp))) { 274 scan->_outch = NCURSES_SP_NAME(_nc_outch); 275 } 276 set_term(scan); 277 NCURSES_SP_NAME(endwin) (NCURSES_SP_ARG); 278 if (sp) 279 sp->_endwin = ewInitial; /* in case of reuse */ 280 } 281 } 282 } 283 _exit(EXIT_FAILURE); 284 } 285 286 #if USE_SIGWINCH 287 static void 288 handle_SIGWINCH(int sig GCC_UNUSED) 289 { 290 _nc_globals.have_sigwinch = 1; 291 # if USE_PTHREADS_EINTR 292 if (_nc_globals.read_thread) { 293 if (!pthread_equal(pthread_self(), _nc_globals.read_thread)) 294 pthread_kill(_nc_globals.read_thread, SIGWINCH); 295 _nc_globals.read_thread = 0; 296 } 297 # endif 298 } 299 #endif /* USE_SIGWINCH */ 300 301 /* 302 * If the given signal is still in its default state, set it to the given 303 * handler. 304 */ 305 static int 306 CatchIfDefault(int sig, void (*handler) (int)) 307 { 308 int result; 309 #if HAVE_SIGACTION || HAVE_SIGVEC 310 sigaction_t old_act; 311 sigaction_t new_act; 312 313 memset(&new_act, 0, sizeof(new_act)); 314 sigemptyset(&new_act.sa_mask); 315 #ifdef SA_RESTART 316 #ifdef SIGWINCH 317 if (sig != SIGWINCH) 318 #endif 319 new_act.sa_flags |= SA_RESTART; 320 #endif /* SA_RESTART */ 321 new_act.sa_handler = handler; 322 323 if (sigaction(sig, NULL, &old_act) == 0 324 && (old_act.sa_handler == SIG_DFL 325 || old_act.sa_handler == handler 326 #if USE_SIGWINCH 327 || (sig == SIGWINCH && old_act.sa_handler == SIG_IGN) 328 #endif 329 )) { 330 (void) sigaction(sig, &new_act, NULL); 331 result = TRUE; 332 } else { 333 result = FALSE; 334 } 335 #else /* !HAVE_SIGACTION */ 336 void (*ohandler) (int); 337 338 ohandler = signal(sig, SIG_IGN); 339 if (ohandler == SIG_DFL 340 || ohandler == handler 341 #if USE_SIGWINCH 342 || (sig == SIGWINCH && ohandler == SIG_IGN) 343 #endif 344 ) { 345 signal(sig, handler); 346 result = TRUE; 347 } else { 348 signal(sig, ohandler); 349 result = FALSE; 350 } 351 #endif 352 T(("CatchIfDefault - will %scatch %s", 353 result ? "" : "not ", signal_name(sig))); 354 return result; 355 } 356 357 /* 358 * This is invoked once at the beginning (e.g., from 'initscr()'), to 359 * initialize the signal catchers, and thereafter when spawning a shell (and 360 * returning) to disable/enable the SIGTSTP (i.e., ^Z) catcher. 361 * 362 * If the application has already set one of the signals, we'll not modify it 363 * (during initialization). 364 * 365 * The XSI document implies that we shouldn't keep the SIGTSTP handler if 366 * the caller later changes its mind, but that doesn't seem correct. 367 */ 368 NCURSES_EXPORT(void) 369 _nc_signal_handler(int enable) 370 { 371 T((T_CALLED("_nc_signal_handler(%d)"), enable)); 372 #if USE_SIGTSTP /* Xenix 2.x doesn't have SIGTSTP, for example */ 373 { 374 static bool ignore_tstp = FALSE; 375 376 if (!ignore_tstp) { 377 static sigaction_t new_sigaction, old_sigaction; 378 379 if (!enable) { 380 new_sigaction.sa_handler = SIG_IGN; 381 sigaction(SIGTSTP, &new_sigaction, &old_sigaction); 382 } else if (new_sigaction.sa_handler != SIG_DFL) { 383 sigaction(SIGTSTP, &old_sigaction, NULL); 384 } else if (sigaction(SIGTSTP, NULL, &old_sigaction) == 0 385 && (old_sigaction.sa_handler == SIG_DFL)) { 386 sigemptyset(&new_sigaction.sa_mask); 387 #ifdef SA_RESTART 388 new_sigaction.sa_flags |= SA_RESTART; 389 #endif /* SA_RESTART */ 390 new_sigaction.sa_handler = handle_SIGTSTP; 391 (void) sigaction(SIGTSTP, &new_sigaction, NULL); 392 } else { 393 ignore_tstp = TRUE; 394 } 395 } 396 } 397 #endif /* !USE_SIGTSTP */ 398 399 if (!_nc_globals.init_signals) { 400 if (enable) { 401 CatchIfDefault(SIGINT, handle_SIGINT); 402 CatchIfDefault(SIGTERM, handle_SIGINT); 403 #if USE_SIGWINCH 404 CatchIfDefault(SIGWINCH, handle_SIGWINCH); 405 #endif 406 _nc_globals.init_signals = TRUE; 407 } 408 } 409 returnVoid; 410 } 411