/* $OpenBSD: lib_tstp.c,v 1.5 1997/12/03 05:21:38 millert Exp $ */ /*************************************************************************** * COPYRIGHT NOTICE * **************************************************************************** * ncurses is copyright (C) 1992-1995 * * Zeyd M. Ben-Halim * * zmbenhal@netcom.com * * Eric S. Raymond * * esr@snark.thyrsus.com * * * * Permission is hereby granted to reproduce and distribute ncurses * * by any means and for any fee, whether alone or as part of a * * larger distribution, in source or in binary form, PROVIDED * * this notice is included with any such distribution, and is not * * removed from any of its header files. Mention of ncurses in any * * applications linked with it is highly appreciated. * * * * ncurses comes AS IS with no warranty, implied or expressed. * * * ***************************************************************************/ /* ** lib_tstp.c ** ** The routine _nc_signal_handler(). ** */ #include #include #if !HAVE_SIGACTION #include #elif !HAVE_TYPE_SIGACTION typedef struct sigaction sigaction_t; #endif #if defined(SVR4_ACTION) && !defined(_POSIX_SOURCE) #define _POSIX_SOURCE #endif #ifndef SIGWINCH #undef USE_SIGWINCH /* FIXME: configure should resolve this */ #endif MODULE_ID("Id: lib_tstp.c,v 1.13 1997/11/01 19:05:13 tom Exp $") /* * 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's simpler... */ #ifdef SIGTSTP static void tstp(int dummy GCC_UNUSED) { sigset_t mask, omask; sigaction_t act, oact; T(("tstp() called")); /* * The user may have changed the prog_mode tty bits, so save them. */ def_prog_mode(); /* * Block window change and timer signals. The latter * is because applications use timers to decide when * to repaint the screen. */ (void)sigemptyset(&mask); (void)sigaddset(&mask, SIGALRM); #if USE_SIGWINCH (void)sigaddset(&mask, SIGWINCH); #endif (void)sigprocmask(SIG_BLOCK, &mask, &omask); /* * End window mode, which also resets the terminal state to the * original (pre-curses) modes. */ endwin(); /* Unblock SIGTSTP. */ (void)sigemptyset(&mask); (void)sigaddset(&mask, SIGTSTP); (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); flushinp(); /* * If the user modified the tty state while suspended, he wants * those changes to stick. So save the new "default" terminal state. */ def_shell_mode(); /* * This relies on the fact that doupdate() will restore the * program-mode tty state, and issue enter_ca_mode if need be. */ doupdate(); /* Reset the signals. */ (void)sigprocmask(SIG_SETMASK, &omask, NULL); } #endif /* defined(SIGTSTP) */ static void cleanup(int sig) { /* * Actually, doing any sort of I/O from within an signal handler is * "unsafe". But we'll _try_ to clean up the screen and terminal * settings on the way out. */ if (sig == SIGINT || sig == SIGQUIT) { #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, (sigaction_t *)0) == 0) #else if (signal(sig, SIG_IGN) != SIG_ERR) #endif { SCREEN *scan = _nc_screen_chain; while(scan) { set_term(scan); endwin(); SP->_endwin = FALSE; /* in case we have an atexit! */ scan = scan->_next_screen; } } } exit(EXIT_FAILURE); } #if USE_SIGWINCH static void sigwinch(int sig GCC_UNUSED) { SCREEN *scan = _nc_screen_chain; while(scan) { scan->_sig_winch = TRUE; scan = scan->_next_screen; } } #endif /* USE_SIGWINCH */ /* * If the given signal is still in its default state, set it to the given * handler. */ #if HAVE_SIGACTION || HAVE_SIGVEC static int CatchIfDefault(int sig, sigaction_t *act) { sigaction_t old_act; if (sigaction(sig, (sigaction_t *)0, &old_act) == 0 && (old_act.sa_handler == SIG_DFL #if USE_SIGWINCH || (sig == SIGWINCH && old_act.sa_handler == SIG_IGN) #endif )) { (void)sigaction(sig, act, (sigaction_t *)0); return TRUE; } return FALSE; } #else static int CatchIfDefault(int sig, RETSIGTYPE (*handler)()) { void (*ohandler)(); ohandler = signal(sig, SIG_IGN); if (ohandler == SIG_DFL #if USE_SIGWINCH || (sig == SIGWINCH && ohandler == SIG_IGN) #endif ) { signal(sig, handler); return TRUE; } else { signal(sig, ohandler); return FALSE; } } #endif /* * 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. */ void _nc_signal_handler(bool enable) { #ifdef SIGTSTP /* Xenix 2.x doesn't have this */ static sigaction_t act, oact; static int ignore; if (!ignore) { if (!enable) { act.sa_handler = SIG_IGN; sigaction(SIGTSTP, &act, &oact); } else if (act.sa_handler) { sigaction(SIGTSTP, &oact, NULL); } else /*initialize */ { sigemptyset(&act.sa_mask); act.sa_flags = 0; #if USE_SIGWINCH act.sa_handler = sigwinch; CatchIfDefault(SIGWINCH, &act); #endif #ifdef SA_RESTART act.sa_flags |= SA_RESTART; #endif /* SA_RESTART */ act.sa_handler = cleanup; CatchIfDefault(SIGINT, &act); CatchIfDefault(SIGTERM, &act); act.sa_handler = tstp; if (!CatchIfDefault(SIGTSTP, &act)) ignore = TRUE; } } #else /* !SIGTSTP */ if (enable) { #if HAVE_SIGACTION || HAVE_SIGVEC static sigaction_t act; sigemptyset(&act.sa_mask); #if USE_SIGWINCH act.sa_handler = sigwinch; CatchIfDefault(SIGWINCH, &act); #endif #ifdef SA_RESTART act.sa_flags |= SA_RESTART; #endif /* SA_RESTART */ act.sa_handler = cleanup; CatchIfDefault(SIGINT, &act); CatchIfDefault(SIGTERM, &act); #else /* !(HAVE_SIGACTION || HAVE_SIGVEC) */ CatchIfDefault(SIGINT, cleanup); CatchIfDefault(SIGTERM, cleanup); #if USE_SIGWINCH CatchIfDefault(SIGWINCH, sigwinch); #endif #endif /* !(HAVE_SIGACTION || HAVE_SIGVEC) */ } #endif /* !SIGTSTP */ }