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