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.26 1994/01/02 17:41:30 bostic Exp $ (Berkeley) $Date: 1994/01/02 17:41:30 $";
12 #include <sys/types.h>
23 #include "pathnames.h"
25 static int filter_ldisplay
__P((SCR
*, FILE *));
29 * Run a range of lines through a filter utility and optionally
30 * replace the original text with the stdout/stderr output of
34 filtercmd(sp
, ep
, fm
, tm
, rp
, cmd
, ftype
)
39 enum filtertype ftype
;
41 struct sigaction act
, oact
;
44 FILE *ifp
, *ofp
; /* GCC: can't be uninitialized. */
45 pid_t parent_writer_pid
, utility_pid
;
47 int input
[2], isig
, output
[2], rval
;
50 /* Set return cursor position; guard against a line number of zero. */
56 * There are three different processes running through this code.
57 * They are the utility, the parent-writer and the parent-reader.
58 * The parent-writer is the process that writes from the file to
59 * the utility, the parent reader is the process that reads from
62 * Input and output are named from the utility's point of view.
63 * The utility reads from input[0] and the parent(s) write to
64 * input[1]. The parent(s) read from output[0] and the utility
65 * writes to output[1].
67 * In the FILTER_READ case, the utility isn't expected to want
68 * input. Redirect its input from /dev/null. Otherwise open
69 * up utility input pipe.
72 input
[0] = input
[1] = output
[0] = output
[1] = -1;
73 if (ftype
== FILTER_READ
) {
74 if ((input
[0] = open(_PATH_DEVNULL
, O_RDONLY
, 0)) < 0) {
76 "filter: %s: %s", _PATH_DEVNULL
, strerror(errno
));
80 if (pipe(input
) < 0) {
81 msgq(sp
, M_SYSERR
, "pipe");
84 if ((ifp
= fdopen(input
[1], "w")) == NULL
) {
85 msgq(sp
, M_SYSERR
, "fdopen");
90 /* Open up utility output pipe. */
91 if (pipe(output
) < 0) {
92 msgq(sp
, M_SYSERR
, "pipe");
95 if ((ofp
= fdopen(output
[0], "r")) == NULL
) {
96 msgq(sp
, M_SYSERR
, "fdopen");
101 * Save ex/vi terminal settings, and restore the original ones.
102 * Restoration so that users can do things like ":r! cat /dev/tty".
104 EX_LEAVE(sp
, isig
, act
, oact
, sb
, osb
, term
);
106 /* Fork off the utility process. */
107 switch (utility_pid
= vfork()) {
108 case -1: /* Error. */
109 msgq(sp
, M_SYSERR
, "vfork");
110 err
: if (input
[0] != -1)
111 (void)close(input
[0]);
114 else if (input
[1] != -1)
115 (void)close(input
[1]);
118 else if (output
[0] != -1)
119 (void)close(output
[0]);
121 (void)close(output
[1]);
124 case 0: /* Utility. */
126 * The utility has default signal behavior. Don't bother
127 * using sigaction(2) 'cause we want the default behavior.
129 (void)signal(SIGINT
, SIG_DFL
);
130 (void)signal(SIGQUIT
, SIG_DFL
);
133 * Redirect stdin from the read end of the input pipe,
134 * and redirect stdout/stderr to the write end of the
137 (void)dup2(input
[0], STDIN_FILENO
);
138 (void)dup2(output
[1], STDOUT_FILENO
);
139 (void)dup2(output
[1], STDERR_FILENO
);
141 /* Close the utility's file descriptors. */
142 (void)close(input
[0]);
143 (void)close(input
[1]);
144 (void)close(output
[0]);
145 (void)close(output
[1]);
147 if ((name
= strrchr(O_STR(sp
, O_SHELL
), '/')) == NULL
)
148 name
= O_STR(sp
, O_SHELL
);
152 execl(O_STR(sp
, O_SHELL
), name
, "-c", cmd
, NULL
);
153 msgq(sp
, M_ERR
, "Error: execl: %s: %s",
154 O_STR(sp
, O_SHELL
), strerror(errno
));
157 default: /* Parent-reader, parent-writer. */
158 /* Close the pipe ends neither parent will use. */
159 (void)close(input
[0]);
160 (void)close(output
[1]);
167 * Reading is the simple case -- we don't need a parent writer,
168 * so the parent reads the output from the read end of the output
169 * pipe until it finishes, then waits for the child. Ex_readfp
170 * appends to the MARK, and closes ofp.
173 * Set the return cursor to the last line read in. Historically,
174 * this behaves differently from ":r file" command, which leaves
175 * the cursor at the first line read in. Check to make sure that
176 * it's not past EOF because we were reading into an empty file.
178 if (ftype
== FILTER_READ
) {
179 rval
= ex_readfp(sp
, ep
, "filter", ofp
, fm
, &nread
, 0);
180 sp
->rptlines
[L_ADDED
] += nread
;
189 * FILTER, FILTER_WRITE
191 * Here we need both a reader and a writer. Temporary files are
192 * expensive and we'd like to avoid disk I/O. Using pipes has the
193 * obvious starvation conditions. It's done as follows:
201 * read lines into the file
204 * read and display lines
208 * We get away without locking the underlying database because we know
209 * that none of the records that we're reading will be modified until
210 * after we've read them. This depends on the fact that the current
211 * B+tree implementation doesn't balance pages or similar things when
212 * it inserts new records. When the DB code has locking, we should
213 * treat vi as if it were multiple applications sharing a database, and
214 * do the required locking. If necessary a work-around would be to do
215 * explicit locking in the line.c:file_gline() code, based on the flag
219 F_SET(ep
, F_MULTILOCK
);
220 switch (parent_writer_pid
= fork()) {
221 case -1: /* Error. */
223 msgq(sp
, M_SYSERR
, "fork");
224 (void)close(input
[1]);
225 (void)close(output
[0]);
227 case 0: /* Parent-writer. */
229 * Write the selected lines to the write end of the
230 * input pipe. Ifp is closed by ex_writefp.
232 (void)close(output
[0]);
233 _exit(ex_writefp(sp
, ep
, "filter", ifp
, fm
, tm
, NULL
, NULL
));
236 default: /* Parent-reader. */
237 (void)close(input
[1]);
238 if (ftype
== FILTER_WRITE
)
240 * Read the output from the read end of the output
241 * pipe and display it. Filter_ldisplay closes ofp.
243 rval
= filter_ldisplay(sp
, ofp
);
246 * Read the output from the read end of the output
247 * pipe. Ex_readfp appends to the MARK and closes
250 rval
= ex_readfp(sp
, ep
, "filter", ofp
, tm
, &nread
, 0);
251 sp
->rptlines
[L_ADDED
] += nread
;
254 /* Wait for the parent-writer. */
255 rval
|= proc_wait(sp
,
256 (long)parent_writer_pid
, "parent-writer", 1);
258 /* Delete any lines written to the utility. */
259 if (ftype
== FILTER
&& rval
== 0) {
260 for (lno
= tm
->lno
; lno
>= fm
->lno
; --lno
)
261 if (file_dline(sp
, ep
, lno
)) {
266 sp
->rptlines
[L_DELETED
] +=
267 (tm
->lno
- fm
->lno
) + 1;
270 * If the filter had no output, we may have just deleted
271 * the cursor. Don't do any real error correction, we'll
272 * try and recover later.
274 if (rp
->lno
> 1 && file_gline(sp
, ep
, rp
->lno
, NULL
) == NULL
)
278 F_CLR(ep
, F_MULTILOCK
);
280 uwait
: rval
|= proc_wait(sp
, (long)utility_pid
, cmd
, 0);
282 /* Restore ex/vi terminal settings. */
283 ret
: EX_RETURN(sp
, isig
, act
, oact
, sb
, osb
, term
);
290 * Wait for one of the processes.
293 * The pid_t type varies in size from a short to a long depending on the
294 * system. It has to be cast into something or the standard promotion
295 * rules get you. I'm using a long based on the belief that nobody is
296 * going to make it unsigned and it's unlikely to be a quad.
299 proc_wait(sp
, pid
, cmd
, okpipe
)
305 extern const char *const sys_siglist
[];
309 /* Wait for the utility to finish. */
310 (void)waitpid((pid_t
)pid
, &pstat
, 0);
313 * Display the utility's exit status. Ignore SIGPIPE from the
314 * parent-writer, as that only means that the utility chose to
315 * exit before reading all of its input.
317 if (WIFSIGNALED(pstat
) && (!okpipe
|| WTERMSIG(pstat
) != SIGPIPE
)) {
318 for (; isblank(*cmd
); ++cmd
);
320 msgq(sp
, M_ERR
, "%.*s%s: received signal: %s%s.",
321 MIN(len
, 20), cmd
, len
> 20 ? "..." : "",
322 sys_siglist
[WTERMSIG(pstat
)],
323 WCOREDUMP(pstat
) ? "; core dumped" : "");
326 if (WIFEXITED(pstat
) && WEXITSTATUS(pstat
)) {
327 for (; isblank(*cmd
); ++cmd
);
329 msgq(sp
, M_ERR
, "%.*s%s: exited with status %d",
330 MIN(len
, 20), cmd
, len
> 20 ? "..." : "",
339 * Display a line output from a utility.
342 * This should probably be combined with some of the ex_print()
343 * routines into a single display routine.
346 filter_ldisplay(sp
, fp
)
354 while (!ex_getline(sp
, fp
, &len
)) {
355 (void)ex_printf(EXCOOKIE
, "%.*s\n", (int)len
, exp
->ibp
);
356 if (ferror(sp
->stdfp
)) {
357 msgq(sp
, M_SYSERR
, NULL
);
363 msgq(sp
, M_SYSERR
, NULL
);