NetBSD/usr.bin/vi/common/signal.c
1994-08-17 19:19:35 +00:00

570 lines
19 KiB
C

/*-
* Copyright (c) 1993, 1994
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef lint
static char sccsid[] = "@(#)signal.c 8.34 (Berkeley) 8/17/94";
#endif /* not lint */
#include <sys/queue.h>
#include <sys/time.h>
#include <bitstring.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include "compat.h"
#include <db.h>
#include <regex.h>
#include "vi.h"
static void h_alrm __P((int));
static void h_hup __P((int));
static void h_int __P((int));
static void h_term __P((int));
static void h_winch __P((int));
static void sig_sync __P((int, u_int));
/*
* There are seven normally asynchronous actions about which vi cares:
* SIGALRM, SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGTSTP and SIGWINCH.
*
* The assumptions:
* 1: The DB routines are not reentrant.
* 2: The curses routines may not be reentrant.
*
* SIGALRM, SIGHUP, SIGTERM
* Used for file recovery. The DB routines can't be reentered, so
* the vi routines that call DB block all three signals (see line.c).
* This means that DB routines can be called at interrupt time.
*
* SIGALRM
* Used to paint busy messages on the screen. The curses routines
* can't be reentered, so this function of SIGALRM can only be used
* in sections of code that do not use any curses functions (see
* busy_on, busy_off in signal.c). This means that curses can be
* called at interrupt time.
*
* SIGQUIT
* Disabled by the signal initialization routines. Historically,
* ^\ switched vi into ex mode, and we continue that practice.
*
* SIGWINCH:
* The interrupt routine sets a global bit which is checked by the
* key-read routine, so there are no reentrancy issues. This means
* that the screen will not resize until vi runs out of keys, but
* that doesn't seem like a problem.
*
* SIGINT and SIGTSTP are a much more difficult issue to resolve. Vi has
* to permit the user to interrupt long-running operations. Generally, a
* search, substitution or read/write is done on a large file, or, the user
* creates a key mapping with an infinite loop. This problem will become
* worse as more complex semantics are added to vi. There are four major
* solutions on the table, each of which have minor permutations.
*
* 1: Run in raw mode.
*
* The up side is that there's no asynchronous behavior to worry about,
* and obviously no reentrancy problems. The down side is that it's easy
* to misinterpret characters (e.g. :w big_file^Mi^V^C is going to look
* like an interrupt) and it's easy to get into places where we won't see
* interrupt characters (e.g. ":map a ixx^[hxxaXXX" infinitely loops in
* historic implementations of vi). Periodically reading the terminal
* input buffer might solve the latter problem, but it's not going to be
* pretty.
*
* Also, we're going to be checking for ^C's and ^Z's both, all over
* the place -- I hate to litter the source code with that. For example,
* the historic version of vi didn't permit you to suspend the screen if
* you were on the colon command line. This isn't right. ^Z isn't a vi
* command, it's a terminal event. (Dammit.)
*
* 2: Run in cbreak mode. There are two problems in this area. First, the
* current curses implementations (both System V and Berkeley) don't give
* you clean cbreak modes. For example, the IEXTEN bit is left on, turning
* on DISCARD and LNEXT. To clarify, what vi WANTS is 8-bit clean, with
* the exception that flow control and signals are turned on, and curses
* cbreak mode doesn't give you this.
*
* We can either set raw mode and twiddle the tty, or cbreak mode and
* twiddle the tty. I chose to use raw mode, on the grounds that raw
* mode is better defined and I'm less likely to be surprised by a curses
* implementation down the road. The twiddling consists of setting ISIG,
* IXON/IXOFF, and disabling some of the interrupt characters (see the
* comments in svi/svi_screen.c). This is all found in historic System
* V (SVID 3) and POSIX 1003.1-1992, so it should be fairly portable.
*
* The second problem is that vi permits you to enter literal signal
* characters, e.g. ^V^C. There are two possible solutions. First, you
* can turn off signals when you get a ^V, but that means that a network
* packet containing ^V and ^C will lose, since the ^C may take effect
* before vi reads the ^V. (This is particularly problematic if you're
* talking over a protocol that recognizes signals locally and sends OOB
* packets when it sees them.) Second, you can turn the ^C into a literal
* character in vi, but that means that there's a race between entering
* ^V<character>^C, i.e. the sequence may end up being ^V^C<character>.
* Also, the second solution doesn't work for flow control characters, as
* they aren't delivered to the program as signals.
*
* Generally, this is what historic vi did. (It didn't have the curses
* problems because it didn't use curses.) It entered signals following
* ^V characters into the input stream, (which is why there's no way to
* enter a literal flow control character).
*
* 3: Run in mostly raw mode; turn signals on when doing an operation the
* user might want to interrupt, but leave them off most of the time.
*
* This works well for things like file reads and writes. This doesn't
* work well for trying to detect infinite maps. The problem is that
* you can write the code so that you don't have to turn on interrupts
* per keystroke, but the code isn't pretty and it's hard to make sure
* that an optimization doesn't cover up an infinite loop. This also
* requires interaction or state between the vi parser and the key
* reading routines, as an infinite loop may still be returning keys
* to the parser.
*
* Also, if the user inserts an interrupt into the tty queue while the
* interrupts are turned off, the key won't be treated as an interrupt,
* and requiring the user to pound the keyboard to catch an interrupt
* window is nasty.
*
* 4: Run in mostly raw mode, leaving signals on all of the time. Done
* by setting raw mode, and twiddling the tty's termios ISIG bit.
*
* This works well for the interrupt cases, because the code only has
* to check to see if the interrupt flag has been set, and can otherwise
* ignore signals. It's also less likely that we'll miss a case, and we
* don't have to worry about synchronizing between the vi parser and the
* key read routines.
*
* The down side is that we have to turn signals off if the user wants
* to enter a literal character (e.g. ^V^C). If the user enters the
* combination fast enough, or as part of a single network packet,
* the text input routines will treat it as a signal instead of as a
* literal character. To some extent, we have this problem already,
* since we turn off flow control so that the user can enter literal
* XON/XOFF characters.
*
* This is probably the easiest to code, and provides the smoothest
* programming interface.
*
* There are a couple of other problems to consider.
*
* First, System V's curses doesn't handle SIGTSTP correctly. If you use the
* newterm() interface, the TSTP signal will leave you in raw mode, and the
* final endwin() will leave you in the correct shell mode. If you use the
* initscr() interface, the TSTP signal will return you to the correct shell
* mode, but the final endwin() will leave you in raw mode. There you have
* it: proof that drug testing is not making any significant headway in the
* computer industry. The 4BSD curses is deficient in that it does not have
* an interface to the terminal keypad. So, regardless, we have to do our
* own SIGTSTP handling.
*
* The problem with this is that if we do our own SIGTSTP handling, in either
* models #3 or #4, we're going to have to call curses routines at interrupt
* time, which means that we might be reentering curses, which is something we
* don't want to do.
*
* Second, SIGTSTP has its own little problems. It's broadcast to the entire
* process group, not sent to a single process. The scenario goes something
* like this: the shell execs the mail program, which execs vi. The user hits
* ^Z, and all three programs get the signal, in some random order. The mail
* program goes to sleep immediately (since it probably didn't have a SIGTSTP
* handler in place). The shell gets a SIGCHLD, does a wait, and finds out
* that the only child in its foreground process group (of which it's aware)
* is asleep. It then optionally resets the terminal (because the modes aren't
* how it left them), and starts prompting the user for input. The problem is
* that somewhere in the middle of all of this, vi is resetting the terminal,
* and getting ready to send a SIGTSTP to the process group in order to put
* itself to sleep. There's a solution to all of this: when vi starts, it puts
* itself into its own process group, and then only it (and possible child
* processes) receive the SIGTSTP. This permits it to clean up the terminal
* and switch back to the original process group, where it sends that process
* group a SIGTSTP, putting everyone to sleep and waking the shell.
*
* Third, handing SIGTSTP asynchronously is further complicated by the child
* processes vi may fork off. If vi calls ex, ex resets the terminal and
* starts running some filter, and SIGTSTP stops them both, vi has to know
* when it restarts that it can't repaint the screen until ex's child has
* finished running. This is solveable, but it's annoying.
*
* Well, somebody had to make a decision, and this is the way it's going to be
* (unless I get talked out of it). SIGINT is handled asynchronously, so
* that we can pretty much guarantee that the user can interrupt any operation
* at any time. SIGTSTP is handled synchronously, so that we don't have to
* reenter curses and so that we don't have to play the process group games.
* ^Z is recognized in the standard text input and command modes. (^Z should
* also be recognized during operations that may potentially take a long time.
* The simplest solution is probably to twiddle the tty, install a handler for
* SIGTSTP, and then restore normal tty modes when the operation is complete.)
*/
/*
* sig_init --
* Initialize signals.
*/
int
sig_init(sp)
SCR *sp;
{
GS *gp;
struct sigaction act;
/* Initialize the signals. */
gp = sp->gp;
(void)sigemptyset(&gp->blockset);
/*
* Use sigaction(2), not signal(3), since we don't always want to
* restart system calls. The example is when waiting for a command
* mode keystroke and SIGWINCH arrives. Try to set the restart bit
* (SA_RESTART) on SIGALRM anyway, it should result in a lot fewer
* interruptions. We also block every other signal that we can block
* when a signal arrives. This is because the signal functions call
* other nvi functions, which aren't guaranteed to be reentrant.
*/
#ifndef SA_RESTART
#define SA_RESTART 0
#endif
#define SETSIG(signal, flags, handler) { \
if (sigaddset(&gp->blockset, signal)) \
goto err; \
act.sa_handler = handler; \
sigfillset(&act.sa_mask); \
act.sa_flags = flags; \
if (sigaction(signal, &act, NULL)) \
goto err; \
}
SETSIG(SIGALRM, SA_RESTART, h_alrm);
SETSIG(SIGHUP, 0, h_hup);
SETSIG(SIGINT, 0, h_int);
SETSIG(SIGTERM, 0, h_term);
SETSIG(SIGWINCH, 0, h_winch);
return (0);
err: msgq(sp, M_SYSERR, "signal init");
return (1);
}
/*
* sig_end --
* End signal setup.
*/
void
sig_end()
{
/*
* POSIX 1003.1-1990 requires that fork (and, presumably, vfork) clear
* pending alarms, and that the exec functions clear pending signals.
* In addition, after an exec, the child continues to ignore signals
* ignored in the parent, and the child's action for signals caught in
* the parent is set to the default action. So, as we currently don't
* ignore any signals, there's no cleanup to be done. This routine is
* left here as a stub function.
*/
return;
}
/*
* busy_on --
* Set a busy message timer.
*/
int
busy_on(sp, msg)
SCR *sp;
char const *msg;
{
struct itimerval value;
struct timeval tod;
/*
* Give the oldest busy message precedence, since it's
* the longer running operation.
*/
if (sp->busy_msg != NULL)
return (1);
/* Get the current time of day, and create a target time. */
if (gettimeofday(&tod, NULL))
return (1);
#define USER_PATIENCE_USECS (8 * 100000L)
sp->busy_tod.tv_sec = tod.tv_sec;
sp->busy_tod.tv_usec = tod.tv_usec + USER_PATIENCE_USECS;
/* We depend on this being an atomic instruction. */
sp->busy_msg = msg;
/*
* Busy messages turn around fast. Reset the timer regardless
* of its current state.
*/
value.it_value.tv_sec = 0;
value.it_value.tv_usec = USER_PATIENCE_USECS;
value.it_interval.tv_sec = 0;
value.it_interval.tv_usec = 0;
if (setitimer(ITIMER_REAL, &value, NULL))
msgq(sp, M_SYSERR, "timer: setitimer");
return (0);
}
/*
* busy_off --
* Turn off a busy message timer.
*/
void
busy_off(sp)
SCR *sp;
{
/* We depend on this being an atomic instruction. */
sp->busy_msg = NULL;
}
/*
* rcv_on --
* Turn on recovery timer.
*/
int
rcv_on(sp, ep)
SCR *sp;
EXF *ep;
{
struct itimerval value;
struct timeval tod;
/* Get the current time of day. */
if (gettimeofday(&tod, NULL))
return (1);
/* Create target time of day. */
ep->rcv_tod.tv_sec = tod.tv_sec + RCV_PERIOD;
ep->rcv_tod.tv_usec = 0;
/*
* If there's a busy message happening, we're done, the
* interrupt handler will start our timer as necessary.
*/
if (sp->busy_msg != NULL)
return (0);
value.it_value.tv_sec = RCV_PERIOD;
value.it_value.tv_usec = 0;
value.it_interval.tv_sec = 0;
value.it_interval.tv_usec = 0;
if (setitimer(ITIMER_REAL, &value, NULL)) {
msgq(sp, M_SYSERR, "timer: setitimer");
return (1);
}
return (0);
}
/*
* h_alrm --
* Handle SIGALRM.
*
* There are two uses of the ITIMER_REAL timer (SIGALRM) in nvi. The first
* is to push the recovery information out to disk at periodic intervals.
* The second is to display a "busy" message if an operation takes more time
* that users are willing to wait before seeing something happen. The SCR
* structure has a wall clock timer structure for each of these. Since the
* busy timer has a much faster timeout than the recovery timer, most of the
* code ignores the recovery timer unless it's the only thing running.
*
* XXX
* It would be nice to reimplement this with two timers, a la POSIX 1003.1,
* but not many systems offer them yet.
*/
static void
h_alrm(signo)
int signo;
{
struct itimerval value;
struct timeval ntod, tod;
SCR *sp;
EXF *ep;
int sverrno;
sverrno = errno;
/* XXX: Get the current time of day; if this fails, we're dead. */
if (gettimeofday(&tod, NULL))
goto ret;
/*
* Fire any timers that are past due, or any that are due
* in a tenth of a second or less.
*/
for (ntod.tv_sec = 0, sp = __global_list->dq.cqh_first;
sp != (void *)&__global_list->dq; sp = sp->q.cqe_next) {
/* Check the busy timer if the msg pointer is set. */
if (sp->busy_msg == NULL)
goto skip_busy;
if (sp->busy_tod.tv_sec > tod.tv_sec ||
sp->busy_tod.tv_sec == tod.tv_sec &&
sp->busy_tod.tv_usec > tod.tv_usec &&
sp->busy_tod.tv_usec - tod.tv_usec > 100000L) {
if (ntod.tv_sec == 0 ||
ntod.tv_sec > sp->busy_tod.tv_sec ||
ntod.tv_sec == sp->busy_tod.tv_sec &&
ntod.tv_usec > sp->busy_tod.tv_usec)
ntod = sp->busy_tod;
} else {
(void)sp->s_busy(sp, sp->busy_msg);
sp->busy_msg = NULL;
}
/*
* Sync the file if the recovery timer has fired. If
* the sync fails, we don't reschedule future sync's.
*/
skip_busy: ep = sp->ep;
if (ep->rcv_tod.tv_sec < tod.tv_sec ||
ep->rcv_tod.tv_sec == tod.tv_sec &&
ep->rcv_tod.tv_usec < tod.tv_usec + 100000L) {
if (rcv_sync(sp, ep, 0))
continue;
ep->rcv_tod = tod;
ep->rcv_tod.tv_sec += RCV_PERIOD;
}
if (ntod.tv_sec == 0 ||
ntod.tv_sec > ep->rcv_tod.tv_sec ||
ntod.tv_sec == ep->rcv_tod.tv_sec &&
ntod.tv_usec > ep->rcv_tod.tv_usec)
ntod = ep->rcv_tod;
}
if (ntod.tv_sec == 0)
goto ret;
/* XXX: Set the timer; if this fails, we're dead. */
value.it_value.tv_sec = ntod.tv_sec - tod.tv_sec;
value.it_value.tv_usec = ntod.tv_usec - tod.tv_usec;
value.it_interval.tv_sec = 0;
value.it_interval.tv_usec = 0;
(void)setitimer(ITIMER_REAL, &value, NULL);
ret: errno = sverrno;
}
/*
* h_hup --
* Handle SIGHUP.
*/
static void
h_hup(signo)
int signo;
{
sig_sync(SIGHUP, RCV_EMAIL);
/* NOTREACHED */
}
/*
* h_int --
* Handle SIGINT.
*
* XXX
* This isn't right if windows are independent of each other.
*/
static void
h_int(signo)
int signo;
{
F_SET(__global_list, G_SIGINT);
}
/*
* h_term --
* Handle SIGTERM.
*/
static void
h_term(signo)
int signo;
{
sig_sync(SIGTERM, 0);
/* NOTREACHED */
}
/*
* h_winch --
* Handle SIGWINCH.
*
* XXX
* This isn't right if windows are independent of each other.
*/
static void
h_winch(signo)
int signo;
{
F_SET(__global_list, G_SIGWINCH);
}
/*
* sig_sync --
*
* Sync the files based on a signal.
*/
static void
sig_sync(signo, flags)
int signo;
u_int flags;
{
SCR *sp;
/*
* Walk the lists of screens, sync'ing the files; only sync
* each file once.
*/
for (sp = __global_list->dq.cqh_first;
sp != (void *)&__global_list->dq; sp = sp->q.cqe_next)
rcv_sync(sp, sp->ep, RCV_ENDSESSION | RCV_PRESERVE | flags);
for (sp = __global_list->hq.cqh_first;
sp != (void *)&__global_list->hq; sp = sp->q.cqe_next)
rcv_sync(sp, sp->ep, RCV_ENDSESSION | RCV_PRESERVE | flags);
/*
* Die with the proper exit status. Don't bother using
* sigaction(2) 'cause we want the default behavior.
*/
(void)signal(signo, SIG_DFL);
(void)kill(getpid(), signo);
/* NOTREACHED */
exit (1);
}