2 * Copyright (c) 1991, 1993
3 * The Regents of the University of California. All rights reserved.
5 * %sccs.include.redist.c%
9 static char sccsid
[] = "$Id: ex_filter.c,v 8.24 1993/11/28 12:36:30 bostic Exp $ (Berkeley) $Date: 1993/11/28 12:36:30 $";
12 #include <sys/types.h>
24 static int filter_ldisplay
__P((SCR
*, FILE *));
28 * Run a range of lines through a filter utility and optionally
29 * replace the original text with the stdout/stderr output of
33 filtercmd(sp
, ep
, fm
, tm
, rp
, cmd
, ftype
)
38 enum filtertype ftype
;
40 struct sigaction act
, oact
;
41 struct termios nterm
, term
;
42 FILE *ifp
, *ofp
; /* GCC: can't be uninitialized. */
43 pid_t parent_writer_pid
, utility_pid
;
45 int input
[2], isig
, output
[2], rval
;
48 /* Set return cursor position; guard against a line number of zero. */
54 * There are three different processes running through this code.
55 * They are the utility, the parent-writer and the parent-reader.
56 * The parent-writer is the process that writes from the file to
57 * the utility, the parent reader is the process that reads from
60 * Input and output are named from the utility's point of view.
61 * The utility reads from input[0] and the parent(s) write to
62 * input[1]. The parent(s) read from output[0] and the utility
63 * writes to output[1].
65 * In the FILTER_READ case, the utility isn't expected to want
66 * input. Redirect its input from /dev/null. Otherwise open
67 * up utility input pipe.
70 input
[0] = input
[1] = output
[0] = output
[1] = -1;
71 if (ftype
== FILTER_READ
) {
72 if ((input
[0] = open(_PATH_DEVNULL
, O_RDONLY
, 0)) < 0) {
74 "filter: %s: %s", _PATH_DEVNULL
, strerror(errno
));
78 if (pipe(input
) < 0) {
79 msgq(sp
, M_SYSERR
, "pipe");
82 if ((ifp
= fdopen(input
[1], "w")) == NULL
) {
83 msgq(sp
, M_SYSERR
, "fdopen");
88 /* Open up utility output pipe. */
89 if (pipe(output
) < 0) {
90 msgq(sp
, M_SYSERR
, "pipe");
93 if ((ofp
= fdopen(output
[0], "r")) == NULL
) {
94 msgq(sp
, M_SYSERR
, "fdopen");
99 * Save ex/vi terminal settings, and set new ones.
101 * ISIG turns on VINTR, VQUIT and VSUSP. We don't want to interrupt
102 * the parent(s), so we ignore VINTR. VQUIT is ignored by main()
103 * because nvi never wants to catch it. A handler for VSUSP should
104 * have been installed by the screen code.
106 if (F_ISSET(sp
->gp
, G_ISFROMTTY
)) {
107 act
.sa_handler
= SIG_IGN
;
108 sigemptyset(&act
.sa_mask
);
110 if (isig
= !sigaction(SIGINT
, &act
, &oact
)) {
111 if (tcgetattr(STDIN_FILENO
, &term
)) {
112 msgq(sp
, M_SYSERR
, "tcgetattr");
116 nterm
.c_lflag
|= ISIG
;
117 if (tcsetattr(STDIN_FILENO
,
118 TCSANOW
| TCSASOFT
, &nterm
)) {
119 msgq(sp
, M_SYSERR
, "tcsetattr");
125 /* Fork off the utility process. */
126 switch (utility_pid
= vfork()) {
127 case -1: /* Error. */
128 msgq(sp
, M_SYSERR
, "vfork");
129 err
: if (input
[0] != -1)
130 (void)close(input
[0]);
133 else if (input
[1] != -1)
134 (void)close(input
[1]);
137 else if (output
[0] != -1)
138 (void)close(output
[0]);
140 (void)close(output
[1]);
143 case 0: /* Utility. */
145 * The utility has default signal behavior. Don't bother
146 * using sigaction(2) 'cause we want the default behavior.
148 (void)signal(SIGINT
, SIG_DFL
);
149 (void)signal(SIGQUIT
, SIG_DFL
);
152 * Redirect stdin from the read end of the input pipe,
153 * and redirect stdout/stderr to the write end of the
156 (void)dup2(input
[0], STDIN_FILENO
);
157 (void)dup2(output
[1], STDOUT_FILENO
);
158 (void)dup2(output
[1], STDERR_FILENO
);
160 /* Close the utility's file descriptors. */
161 (void)close(input
[0]);
162 (void)close(input
[1]);
163 (void)close(output
[0]);
164 (void)close(output
[1]);
166 if ((name
= strrchr(O_STR(sp
, O_SHELL
), '/')) == NULL
)
167 name
= O_STR(sp
, O_SHELL
);
171 execl(O_STR(sp
, O_SHELL
), name
, "-c", cmd
, NULL
);
172 msgq(sp
, M_ERR
, "Error: execl: %s: %s",
173 O_STR(sp
, O_SHELL
), strerror(errno
));
176 default: /* Parent-reader, parent-writer. */
177 /* Close the pipe ends neither parent will use. */
178 (void)close(input
[0]);
179 (void)close(output
[1]);
186 * Reading is the simple case -- we don't need a parent writer,
187 * so the parent reads the output from the read end of the output
188 * pipe until it finishes, then waits for the child. Ex_readfp
189 * appends to the MARK, and closes ofp.
192 * Set the return cursor to the last line read in. Historically,
193 * this behaves differently from ":r file" command, which leaves
194 * the cursor at the first line read in. Check to make sure that
195 * it's not past EOF because we were reading into an empty file.
197 if (ftype
== FILTER_READ
) {
198 rval
= ex_readfp(sp
, ep
, "filter", ofp
, fm
, &nread
, 0);
199 sp
->rptlines
[L_ADDED
] += nread
;
208 * FILTER, FILTER_WRITE
210 * Here we need both a reader and a writer. Temporary files are
211 * expensive and we'd like to avoid disk I/O. Using pipes has the
212 * obvious starvation conditions. It's done as follows:
220 * read lines into the file
223 * read and display lines
227 * We get away without locking the underlying database because we know
228 * that none of the records that we're reading will be modified until
229 * after we've read them. This depends on the fact that the current
230 * B+tree implementation doesn't balance pages or similar things when
231 * it inserts new records. When the DB code has locking, we should
232 * treat vi as if it were multiple applications sharing a database, and
233 * do the required locking. If necessary a work-around would be to do
234 * explicit locking in the line.c:file_gline() code, based on the flag
238 F_SET(ep
, F_MULTILOCK
);
239 switch (parent_writer_pid
= fork()) {
240 case -1: /* Error. */
242 msgq(sp
, M_SYSERR
, "fork");
243 (void)close(input
[1]);
244 (void)close(output
[0]);
246 case 0: /* Parent-writer. */
248 * Write the selected lines to the write end of the
249 * input pipe. Ifp is closed by ex_writefp.
251 (void)close(output
[0]);
252 _exit(ex_writefp(sp
, ep
, "filter", ifp
, fm
, tm
, NULL
, NULL
));
255 default: /* Parent-reader. */
256 (void)close(input
[1]);
257 if (ftype
== FILTER_WRITE
)
259 * Read the output from the read end of the output
260 * pipe and display it. Filter_ldisplay closes ofp.
262 rval
= filter_ldisplay(sp
, ofp
);
265 * Read the output from the read end of the output
266 * pipe. Ex_readfp appends to the MARK and closes
269 rval
= ex_readfp(sp
, ep
, "filter", ofp
, tm
, &nread
, 0);
270 sp
->rptlines
[L_ADDED
] += nread
;
273 /* Wait for the parent-writer. */
274 rval
|= proc_wait(sp
,
275 (long)parent_writer_pid
, "parent-writer", 1);
277 /* Delete any lines written to the utility. */
278 if (ftype
== FILTER
&& rval
== 0) {
279 for (lno
= tm
->lno
; lno
>= fm
->lno
; --lno
)
280 if (file_dline(sp
, ep
, lno
)) {
285 sp
->rptlines
[L_DELETED
] +=
286 (tm
->lno
- fm
->lno
) + 1;
289 * If the filter had no output, we may have just deleted
290 * the cursor. Don't do any real error correction, we'll
291 * try and recover later.
293 if (rp
->lno
> 1 && file_gline(sp
, ep
, rp
->lno
, NULL
) == NULL
)
297 F_CLR(ep
, F_MULTILOCK
);
299 uwait
: rval
|= proc_wait(sp
, (long)utility_pid
, cmd
, 0);
301 ret
: if (F_ISSET(sp
->gp
, G_ISFROMTTY
) && isig
) {
302 if (sigaction(SIGINT
, &oact
, NULL
)) {
303 msgq(sp
, M_SYSERR
, "signal");
306 if (tcsetattr(STDIN_FILENO
, TCSANOW
| TCSASOFT
, &term
)) {
307 msgq(sp
, M_SYSERR
, "tcsetattr");
316 * Wait for one of the processes.
319 * The pid_t type varies in size from a short to a long depending on the
320 * system. It has to be cast into something or the standard promotion
321 * rules get you. I'm using a long based on the belief that nobody is
322 * going to make it unsigned and it's unlikely to be a quad.
325 proc_wait(sp
, pid
, cmd
, okpipe
)
331 extern const char *const sys_siglist
[];
335 /* Wait for the utility to finish. */
336 (void)waitpid((pid_t
)pid
, &pstat
, 0);
339 * Display the utility's exit status. Ignore SIGPIPE from the
340 * parent-writer, as that only means that the utility chose to
341 * exit before reading all of its input.
343 if (WIFSIGNALED(pstat
) && (!okpipe
|| WTERMSIG(pstat
) != SIGPIPE
)) {
344 for (; isblank(*cmd
); ++cmd
);
346 msgq(sp
, M_ERR
, "%.*s%s: received signal: %s%s.",
347 MIN(len
, 20), cmd
, len
> 20 ? "..." : "",
348 sys_siglist
[WTERMSIG(pstat
)],
349 WCOREDUMP(pstat
) ? "; core dumped" : "");
352 if (WIFEXITED(pstat
) && WEXITSTATUS(pstat
)) {
353 for (; isblank(*cmd
); ++cmd
);
355 msgq(sp
, M_ERR
, "%.*s%s: exited with status %d",
356 MIN(len
, 20), cmd
, len
> 20 ? "..." : "",
365 * Display a line output from a utility.
368 * This should probably be combined with some of the ex_print()
369 * routines into a single display routine.
372 filter_ldisplay(sp
, fp
)
380 while (!ex_getline(sp
, fp
, &len
)) {
381 (void)ex_printf(EXCOOKIE
, "%.*s\n", (int)len
, exp
->ibp
);
382 if (ferror(sp
->stdfp
)) {
383 msgq(sp
, M_SYSERR
, NULL
);
389 msgq(sp
, M_SYSERR
, NULL
);