e1a8c85b86
Reviewed by: Sean Eric Fagan
570 lines
19 KiB
C
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);
|
|
}
|