update to 1.01
[nvi.git] / ex / ex_filter.c
blob060f09d5222af422f2b70c5483981eae42e3ce91
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.26 1994/01/02 17:41:30 bostic Exp $ (Berkeley) $Date: 1994/01/02 17:41:30 $";
10 #endif /* not lint */
12 #include <sys/types.h>
13 #include <sys/stat.h>
14 #include <sys/wait.h>
16 #include <errno.h>
17 #include <fcntl.h>
18 #include <string.h>
19 #include <unistd.h>
21 #include "vi.h"
22 #include "excmd.h"
23 #include "pathnames.h"
25 static int filter_ldisplay __P((SCR *, FILE *));
28 * filtercmd --
29 * Run a range of lines through a filter utility and optionally
30 * replace the original text with the stdout/stderr output of
31 * the utility.
33 int
34 filtercmd(sp, ep, fm, tm, rp, cmd, ftype)
35 SCR *sp;
36 EXF *ep;
37 MARK *fm, *tm, *rp;
38 char *cmd;
39 enum filtertype ftype;
41 struct sigaction act, oact;
42 struct stat osb, sb;
43 struct termios term;
44 FILE *ifp, *ofp; /* GCC: can't be uninitialized. */
45 pid_t parent_writer_pid, utility_pid;
46 recno_t lno, nread;
47 int input[2], isig, output[2], rval;
48 char *name;
50 /* Set return cursor position; guard against a line number of zero. */
51 *rp = *fm;
52 if (fm->lno == 0)
53 rp->lno = 1;
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
60 * the utility.
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.
71 ifp = ofp = NULL;
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) {
75 msgq(sp, M_ERR,
76 "filter: %s: %s", _PATH_DEVNULL, strerror(errno));
77 return (1);
79 } else {
80 if (pipe(input) < 0) {
81 msgq(sp, M_SYSERR, "pipe");
82 goto err;
84 if ((ifp = fdopen(input[1], "w")) == NULL) {
85 msgq(sp, M_SYSERR, "fdopen");
86 goto err;
90 /* Open up utility output pipe. */
91 if (pipe(output) < 0) {
92 msgq(sp, M_SYSERR, "pipe");
93 goto err;
95 if ((ofp = fdopen(output[0], "r")) == NULL) {
96 msgq(sp, M_SYSERR, "fdopen");
97 goto err;
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]);
112 if (ifp != NULL)
113 (void)fclose(ifp);
114 else if (input[1] != -1)
115 (void)close(input[1]);
116 if (ofp != NULL)
117 (void)fclose(ofp);
118 else if (output[0] != -1)
119 (void)close(output[0]);
120 if (output[1] != -1)
121 (void)close(output[1]);
122 rval = 1;
123 goto ret;
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
135 * output pipe.
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);
149 else
150 ++name;
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));
155 _exit (127);
156 /* NOTREACHED */
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]);
161 break;
165 * FILTER_READ:
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.
172 * !!!
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;
181 if (fm->lno == 0)
182 rp->lno = nread;
183 else
184 rp->lno += nread;
185 goto uwait;
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:
195 * fork
196 * child
197 * write lines out
198 * exit
199 * parent
200 * FILTER:
201 * read lines into the file
202 * delete old lines
203 * FILTER_WRITE
204 * read and display lines
205 * wait for child
207 * XXX
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
216 * set here.
218 rval = 0;
219 F_SET(ep, F_MULTILOCK);
220 switch (parent_writer_pid = fork()) {
221 case -1: /* Error. */
222 rval = 1;
223 msgq(sp, M_SYSERR, "fork");
224 (void)close(input[1]);
225 (void)close(output[0]);
226 break;
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));
235 /* NOTREACHED */
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);
244 else {
246 * Read the output from the read end of the output
247 * pipe. Ex_readfp appends to the MARK and closes
248 * ofp.
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)) {
262 rval = 1;
263 break;
265 if (rval == 0)
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)
275 --rp->lno;
276 break;
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);
285 return (rval);
289 * proc_wait --
290 * Wait for one of the processes.
292 * !!!
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)
300 SCR *sp;
301 long pid;
302 const char *cmd;
303 int okpipe;
305 extern const char *const sys_siglist[];
306 size_t len;
307 int pstat;
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);
319 len = strlen(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" : "");
324 return (1);
326 if (WIFEXITED(pstat) && WEXITSTATUS(pstat)) {
327 for (; isblank(*cmd); ++cmd);
328 len = strlen(cmd);
329 msgq(sp, M_ERR, "%.*s%s: exited with status %d",
330 MIN(len, 20), cmd, len > 20 ? "..." : "",
331 WEXITSTATUS(pstat));
332 return (1);
334 return (0);
338 * filter_ldisplay --
339 * Display a line output from a utility.
341 * XXX
342 * This should probably be combined with some of the ex_print()
343 * routines into a single display routine.
345 static int
346 filter_ldisplay(sp, fp)
347 SCR *sp;
348 FILE *fp;
350 EX_PRIVATE *exp;
351 size_t len;
353 exp = EXP(sp);
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);
358 (void)fclose(fp);
359 return (1);
362 if (fclose(fp)) {
363 msgq(sp, M_SYSERR, NULL);
364 return (1);
366 return (0);