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