/* $OpenBSD: lib_tstp.c,v 1.12 2023/10/17 09:52:09 nicm Exp $ */ /**************************************************************************** * Copyright 2020-2021,2022 Thomas E. Dickey * * Copyright 1998-2014,2017 Free Software Foundation, Inc. * * * * Permission is hereby granted, free of charge, to any person obtaining a * * copy of this software and associated documentation files (the * * "Software"), to deal in the Software without restriction, including * * without limitation the rights to use, copy, modify, merge, publish, * * distribute, distribute with modifications, sublicense, and/or sell * * copies of the Software, and to permit persons to whom the Software is * * furnished to do so, subject to the following conditions: * * * * The above copyright notice and this permission notice shall be included * * in all copies or substantial portions of the Software. * * * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * * * Except as contained in this notice, the name(s) of the above copyright * * holders shall not be used in advertising or otherwise to promote the * * sale, use or other dealings in this Software without prior written * * authorization. * ****************************************************************************/ /**************************************************************************** * Author: Zeyd M. Ben-Halim 1992,1995 * * and: Eric S. Raymond * * and: Thomas E. Dickey 1995-on * ****************************************************************************/ /* ** lib_tstp.c ** ** The routine _nc_signal_handler(). ** */ #include #include MODULE_ID("$Id: lib_tstp.c,v 1.12 2023/10/17 09:52:09 nicm Exp $") #if defined(SIGTSTP) && (HAVE_SIGACTION || HAVE_SIGVEC) #define USE_SIGTSTP 1 #else #define USE_SIGTSTP 0 #endif #ifdef TRACE static const char * signal_name(int sig) { switch (sig) { #ifdef SIGALRM case SIGALRM: return "SIGALRM"; #endif #ifdef SIGCONT case SIGCONT: return "SIGCONT"; #endif case SIGINT: return "SIGINT"; #ifdef SIGQUIT case SIGQUIT: return "SIGQUIT"; #endif case SIGTERM: return "SIGTERM"; #ifdef SIGTSTP case SIGTSTP: return "SIGTSTP"; #endif #ifdef SIGTTOU case SIGTTOU: return "SIGTTOU"; #endif #ifdef SIGWINCH case SIGWINCH: return "SIGWINCH"; #endif default: return "unknown signal"; } } #endif /* * Note: This code is fragile! Its problem is that different OSs * handle restart of system calls interrupted by signals differently. * The ncurses code needs signal-call restart to happen -- otherwise, * interrupted wgetch() calls will return FAIL, probably making the * application think the input stream has ended and it should * terminate. In particular, you know you have this problem if, when * you suspend an ncurses-using lynx with ^Z and resume, it dies * immediately. * * Default behavior of POSIX sigaction(2) is not to restart * interrupted system calls, but Linux's sigaction does it anyway (at * least, on and after the 1.1.47 I (esr) use). Thus this code works * OK under Linux. The 4.4BSD sigaction(2) supports a (non-portable) * SA_RESTART flag that forces the right behavior. Thus, this code * should work OK under BSD/OS, NetBSD, and FreeBSD (let us know if it * does not). * * Stock System Vs (and anything else using a strict-POSIX * sigaction(2) without SA_RESTART) may have a problem. Possible * solutions: * * sigvec restarts by default (SV_INTERRUPT flag to not restart) * signal restarts by default in SVr4 (assuming you link with -lucb) * and BSD, but not SVr3. * sigset restarts, but is only available under SVr4/Solaris. * * The signal(3) call is mandated by the ANSI standard, and its * interaction with sigaction(2) is described in the POSIX standard * (3.3.4.2, page 72,line 934). According to section 8.1, page 191, * however, signal(3) itself is not required by POSIX.1. And POSIX is * silent on whether it is required to restart signals. * * So. The present situation is, we use sigaction(2) with no * guarantee of restart anywhere but on Linux and BSD. We could * switch to signal(3) and collar Linux, BSD, and SVr4. Any way * we slice it, System V UNIXes older than SVr4 will probably lose * (this may include XENIX). * * This implementation will probably be changed to use signal(3) in * the future. If nothing else, it is simpler... */ #if USE_SIGTSTP static void handle_SIGTSTP(int dummy GCC_UNUSED) { SCREEN *sp = CURRENT_SCREEN; sigset_t mask, omask; sigaction_t act, oact; #ifdef SIGTTOU int sigttou_blocked; #endif _nc_globals.have_sigtstp = 1; T(("handle_SIGTSTP() called")); /* * The user may have changed the prog_mode tty bits, so save them. * * But first try to detect whether we still are in the foreground * process group - if not, an interactive shell may already have * taken ownership of the tty and modified the settings when our * parent was stopped before us, and we would likely pick up the * settings already modified by the shell. * * Don't do this if we're not in curses - */ if (sp != 0 && (sp->_endwin == ewRunning)) #if HAVE_TCGETPGRP if (tcgetpgrp(STDIN_FILENO) == getpgrp()) #endif NCURSES_SP_NAME(def_prog_mode) (NCURSES_SP_ARG); /* * Block window change and timer signals. The latter * is because applications use timers to decide when * to repaint the screen. */ (void) sigemptyset(&mask); #ifdef SIGALRM (void) sigaddset(&mask, SIGALRM); #endif #if USE_SIGWINCH (void) sigaddset(&mask, SIGWINCH); #endif (void) sigprocmask(SIG_BLOCK, &mask, &omask); #ifdef SIGTTOU sigttou_blocked = sigismember(&omask, SIGTTOU); if (!sigttou_blocked) { (void) sigemptyset(&mask); (void) sigaddset(&mask, SIGTTOU); (void) sigprocmask(SIG_BLOCK, &mask, NULL); } #endif /* * End window mode, which also resets the terminal state to the * original (pre-curses) modes. */ NCURSES_SP_NAME(endwin) (NCURSES_SP_ARG); /* Unblock SIGTSTP. */ (void) sigemptyset(&mask); (void) sigaddset(&mask, SIGTSTP); #ifdef SIGTTOU if (!sigttou_blocked) { /* Unblock this too if it wasn't blocked on entry */ (void) sigaddset(&mask, SIGTTOU); } #endif (void) sigprocmask(SIG_UNBLOCK, &mask, NULL); /* Now we want to resend SIGSTP to this process and suspend it */ act.sa_handler = SIG_DFL; sigemptyset(&act.sa_mask); act.sa_flags = 0; #ifdef SA_RESTART act.sa_flags |= SA_RESTART; #endif /* SA_RESTART */ sigaction(SIGTSTP, &act, &oact); kill(getpid(), SIGTSTP); /* Process gets suspended...time passes...process resumes */ T(("SIGCONT received")); sigaction(SIGTSTP, &oact, NULL); NCURSES_SP_NAME(flushinp) (NCURSES_SP_ARG); /* * If the user modified the tty state while suspended, he wants * those changes to stick. So save the new "default" terminal state. */ NCURSES_SP_NAME(def_shell_mode) (NCURSES_SP_ARG); /* * This relies on the fact that doupdate() will restore the * program-mode tty state, and issue enter_ca_mode if need be. */ NCURSES_SP_NAME(doupdate) (NCURSES_SP_ARG); /* Reset the signals. */ (void) sigprocmask(SIG_SETMASK, &omask, NULL); } #endif /* USE_SIGTSTP */ static void handle_SIGINT(int sig) { SCREEN *sp = CURRENT_SCREEN; /* * Much of this is unsafe from a signal handler. But we'll _try_ to clean * up the screen and terminal settings on the way out. * * There are at least the following problems: * 1) Walking the SCREEN list is unsafe, since all list management * is done without any signal blocking. * 2) On systems which have REENTRANT turned on, set_term() uses * _nc_lock_global() which could deadlock or misbehave in other ways. * 3) endwin() calls all sorts of stuff, many of which use stdio or * other library functions which are clearly unsafe. */ if (!_nc_globals.cleanup_nested++ && (sig == SIGINT || sig == SIGTERM)) { #if HAVE_SIGACTION || HAVE_SIGVEC sigaction_t act; sigemptyset(&act.sa_mask); act.sa_flags = 0; act.sa_handler = SIG_IGN; if (sigaction(sig, &act, NULL) == 0) #else if (signal(sig, SIG_IGN) != SIG_ERR) #endif { SCREEN *scan; for (each_screen(scan)) { if (scan->_ofp != 0 && NC_ISATTY(fileno(scan->_ofp))) { scan->_outch = NCURSES_SP_NAME(_nc_outch); } set_term(scan); NCURSES_SP_NAME(endwin) (NCURSES_SP_ARG); if (sp) sp->_endwin = ewInitial; /* in case of reuse */ } } } _exit(EXIT_FAILURE); } # ifndef _nc_set_read_thread NCURSES_EXPORT(void) _nc_set_read_thread(bool enable) { _nc_lock_global(curses); if (enable) { # if USE_WEAK_SYMBOLS if ((pthread_self) && (pthread_kill) && (pthread_equal)) # endif _nc_globals.read_thread = pthread_self(); } else { _nc_globals.read_thread = 0; } _nc_unlock_global(curses); } # endif #if USE_SIGWINCH static void handle_SIGWINCH(int sig GCC_UNUSED) { _nc_globals.have_sigwinch = 1; # if USE_PTHREADS_EINTR if (_nc_globals.read_thread) { if (!pthread_equal(pthread_self(), _nc_globals.read_thread)) pthread_kill(_nc_globals.read_thread, SIGWINCH); _nc_globals.read_thread = 0; } # endif } #endif /* USE_SIGWINCH */ /* * If the given signal is still in its default state, set it to the given * handler. */ static int CatchIfDefault(int sig, void (*handler) (int)) { int result; #if HAVE_SIGACTION || HAVE_SIGVEC sigaction_t old_act; sigaction_t new_act; memset(&new_act, 0, sizeof(new_act)); sigemptyset(&new_act.sa_mask); #ifdef SA_RESTART #ifdef SIGWINCH if (sig != SIGWINCH) #endif new_act.sa_flags |= SA_RESTART; #endif /* SA_RESTART */ new_act.sa_handler = handler; if (sigaction(sig, NULL, &old_act) == 0 && (old_act.sa_handler == SIG_DFL || old_act.sa_handler == handler #if USE_SIGWINCH || (sig == SIGWINCH && old_act.sa_handler == SIG_IGN) #endif )) { (void) sigaction(sig, &new_act, NULL); result = TRUE; } else { result = FALSE; } #else /* !HAVE_SIGACTION */ void (*ohandler) (int); ohandler = signal(sig, SIG_IGN); if (ohandler == SIG_DFL || ohandler == handler #if USE_SIGWINCH || (sig == SIGWINCH && ohandler == SIG_IGN) #endif ) { signal(sig, handler); result = TRUE; } else { signal(sig, ohandler); result = FALSE; } #endif T(("CatchIfDefault - will %scatch %s", result ? "" : "not ", signal_name(sig))); return result; } /* * This is invoked once at the beginning (e.g., from 'initscr()'), to * initialize the signal catchers, and thereafter when spawning a shell (and * returning) to disable/enable the SIGTSTP (i.e., ^Z) catcher. * * If the application has already set one of the signals, we'll not modify it * (during initialization). * * The XSI document implies that we shouldn't keep the SIGTSTP handler if * the caller later changes its mind, but that doesn't seem correct. */ NCURSES_EXPORT(void) _nc_signal_handler(int enable) { T((T_CALLED("_nc_signal_handler(%d)"), enable)); #if USE_SIGTSTP /* Xenix 2.x doesn't have SIGTSTP, for example */ { static bool ignore_tstp = FALSE; if (!ignore_tstp) { static sigaction_t new_sigaction, old_sigaction; if (!enable) { new_sigaction.sa_handler = SIG_IGN; sigaction(SIGTSTP, &new_sigaction, &old_sigaction); } else if (new_sigaction.sa_handler != SIG_DFL) { sigaction(SIGTSTP, &old_sigaction, NULL); } else if (sigaction(SIGTSTP, NULL, &old_sigaction) == 0 && (old_sigaction.sa_handler == SIG_DFL)) { sigemptyset(&new_sigaction.sa_mask); #ifdef SA_RESTART new_sigaction.sa_flags |= SA_RESTART; #endif /* SA_RESTART */ new_sigaction.sa_handler = handle_SIGTSTP; (void) sigaction(SIGTSTP, &new_sigaction, NULL); } else { ignore_tstp = TRUE; } } } #endif /* !USE_SIGTSTP */ if (!_nc_globals.init_signals) { if (enable) { CatchIfDefault(SIGINT, handle_SIGINT); CatchIfDefault(SIGTERM, handle_SIGINT); #if USE_SIGWINCH CatchIfDefault(SIGWINCH, handle_SIGWINCH); #endif _nc_globals.init_signals = TRUE; } } returnVoid; }