add S_EXSILENT flag, for ex batch scripts
[nvi.git] / ex / ex_filter.c
blob88e96083b534a6604ad8be83c48e10185029fe9a
1 /*-
2 * Copyright (c) 1991, 1993
3 * The Regents of the University of California. All rights reserved.
5 * %sccs.include.redist.c%
6 */
8 #ifndef lint
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 $";
10 #endif /* not lint */
12 #include <sys/types.h>
13 #include <sys/wait.h>
15 #include <errno.h>
16 #include <fcntl.h>
17 #include <paths.h>
18 #include <string.h>
19 #include <unistd.h>
21 #include "vi.h"
22 #include "excmd.h"
24 static int filter_ldisplay __P((SCR *, FILE *));
27 * filtercmd --
28 * Run a range of lines through a filter utility and optionally
29 * replace the original text with the stdout/stderr output of
30 * the utility.
32 int
33 filtercmd(sp, ep, fm, tm, rp, cmd, ftype)
34 SCR *sp;
35 EXF *ep;
36 MARK *fm, *tm, *rp;
37 char *cmd;
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;
44 recno_t lno, nread;
45 int input[2], isig, output[2], rval;
46 char *name;
48 /* Set return cursor position; guard against a line number of zero. */
49 *rp = *fm;
50 if (fm->lno == 0)
51 rp->lno = 1;
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
58 * the utility.
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.
69 ifp = ofp = NULL;
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) {
73 msgq(sp, M_ERR,
74 "filter: %s: %s", _PATH_DEVNULL, strerror(errno));
75 return (1);
77 } else {
78 if (pipe(input) < 0) {
79 msgq(sp, M_SYSERR, "pipe");
80 goto err;
82 if ((ifp = fdopen(input[1], "w")) == NULL) {
83 msgq(sp, M_SYSERR, "fdopen");
84 goto err;
88 /* Open up utility output pipe. */
89 if (pipe(output) < 0) {
90 msgq(sp, M_SYSERR, "pipe");
91 goto err;
93 if ((ofp = fdopen(output[0], "r")) == NULL) {
94 msgq(sp, M_SYSERR, "fdopen");
95 goto err;
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);
109 act.sa_flags = 0;
110 if (isig = !sigaction(SIGINT, &act, &oact)) {
111 if (tcgetattr(STDIN_FILENO, &term)) {
112 msgq(sp, M_SYSERR, "tcgetattr");
113 goto err;
115 nterm = term;
116 nterm.c_lflag |= ISIG;
117 if (tcsetattr(STDIN_FILENO,
118 TCSANOW | TCSASOFT, &nterm)) {
119 msgq(sp, M_SYSERR, "tcsetattr");
120 goto err;
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]);
131 if (ifp != NULL)
132 (void)fclose(ifp);
133 else if (input[1] != -1)
134 (void)close(input[1]);
135 if (ofp != NULL)
136 (void)fclose(ofp);
137 else if (output[0] != -1)
138 (void)close(output[0]);
139 if (output[1] != -1)
140 (void)close(output[1]);
141 rval = 1;
142 goto ret;
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
154 * output pipe.
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);
168 else
169 ++name;
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));
174 _exit (127);
175 /* NOTREACHED */
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]);
180 break;
184 * FILTER_READ:
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.
191 * !!!
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;
200 if (fm->lno == 0)
201 rp->lno = nread;
202 else
203 rp->lno += nread;
204 goto uwait;
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:
214 * fork
215 * child
216 * write lines out
217 * exit
218 * parent
219 * FILTER:
220 * read lines into the file
221 * delete old lines
222 * FILTER_WRITE
223 * read and display lines
224 * wait for child
226 * XXX
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
235 * set here.
237 rval = 0;
238 F_SET(ep, F_MULTILOCK);
239 switch (parent_writer_pid = fork()) {
240 case -1: /* Error. */
241 rval = 1;
242 msgq(sp, M_SYSERR, "fork");
243 (void)close(input[1]);
244 (void)close(output[0]);
245 break;
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));
254 /* NOTREACHED */
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);
263 else {
265 * Read the output from the read end of the output
266 * pipe. Ex_readfp appends to the MARK and closes
267 * ofp.
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)) {
281 rval = 1;
282 break;
284 if (rval == 0)
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)
294 --rp->lno;
295 break;
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");
304 rval = 1;
306 if (tcsetattr(STDIN_FILENO, TCSANOW | TCSASOFT, &term)) {
307 msgq(sp, M_SYSERR, "tcsetattr");
308 rval = 1;
311 return (rval);
315 * proc_wait --
316 * Wait for one of the processes.
318 * !!!
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)
326 SCR *sp;
327 long pid;
328 const char *cmd;
329 int okpipe;
331 extern const char *const sys_siglist[];
332 size_t len;
333 int pstat;
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);
345 len = strlen(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" : "");
350 return (1);
352 if (WIFEXITED(pstat) && WEXITSTATUS(pstat)) {
353 for (; isblank(*cmd); ++cmd);
354 len = strlen(cmd);
355 msgq(sp, M_ERR, "%.*s%s: exited with status %d",
356 MIN(len, 20), cmd, len > 20 ? "..." : "",
357 WEXITSTATUS(pstat));
358 return (1);
360 return (0);
364 * filter_ldisplay --
365 * Display a line output from a utility.
367 * XXX
368 * This should probably be combined with some of the ex_print()
369 * routines into a single display routine.
371 static int
372 filter_ldisplay(sp, fp)
373 SCR *sp;
374 FILE *fp;
376 EX_PRIVATE *exp;
377 size_t len;
379 exp = EXP(sp);
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);
384 (void)fclose(fp);
385 return (1);
388 if (fclose(fp)) {
389 msgq(sp, M_SYSERR, NULL);
390 return (1);
392 return (0);