1 /*-
2 * Copyright (c) 1991, 1993, 1994
3 * The Regents of the University of California. All rights reserved.
4 * Copyright (c) 1991, 1993, 1994, 1995, 1996
5 * Keith Bostic. All rights reserved.
6 *
7 * See the LICENSE file for redistribution information.
8 */
9
10 #include "config.h"
11
12 #include <sys/types.h>
13 #include <sys/queue.h>
14 #include <sys/time.h>
15
16 #include <bitstring.h>
17 #include <errno.h>
18 #include <fcntl.h>
19 #include <limits.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <unistd.h>
24
25 #include "../common/common.h"
26
27 static int filter_ldisplay(SCR *, FILE *);
28
29 /*
30 * ex_filter --
31 * Run a range of lines through a filter utility and optionally
32 * replace the original text with the stdout/stderr output of
33 * the utility.
34 *
35 * PUBLIC: int ex_filter(SCR *,
36 * PUBLIC: EXCMD *, MARK *, MARK *, MARK *, CHAR_T *, enum filtertype);
37 */
38 int
ex_filter(SCR * sp,EXCMD * cmdp,MARK * fm,MARK * tm,MARK * rp,CHAR_T * cmd,enum filtertype ftype)39 ex_filter(SCR *sp, EXCMD *cmdp, MARK *fm, MARK *tm, MARK *rp, CHAR_T *cmd, enum filtertype ftype)
40 {
41 FILE *ifp, *ofp;
42 pid_t parent_writer_pid, utility_pid;
43 recno_t nread;
44 int input[2], output[2], rval;
45 char *name;
46 char *np;
47 size_t nlen;
48
49 rval = 0;
50
51 /* Set return cursor position, which is never less than line 1. */
52 *rp = *fm;
53 if (rp->lno == 0)
54 rp->lno = 1;
55
56 /* We're going to need a shell. */
57 if (opts_empty(sp, O_SHELL, 0))
58 return (1);
59
60 /*
61 * There are three different processes running through this code.
62 * They are the utility, the parent-writer and the parent-reader.
63 * The parent-writer is the process that writes from the file to
64 * the utility, the parent reader is the process that reads from
65 * the utility.
66 *
67 * Input and output are named from the utility's point of view.
68 * The utility reads from input[0] and the parent(s) write to
69 * input[1]. The parent(s) read from output[0] and the utility
70 * writes to output[1].
71 *
72 * !!!
73 * Historically, in the FILTER_READ case, the utility reads from
74 * the terminal (e.g. :r! cat works). Otherwise open up utility
75 * input pipe.
76 */
77 ofp = NULL;
78 input[0] = input[1] = output[0] = output[1] = -1;
79 if (ftype != FILTER_READ && pipe(input) < 0) {
80 msgq(sp, M_SYSERR, "pipe");
81 goto err;
82 }
83
84 /* Open up utility output pipe. */
85 if (pipe(output) < 0) {
86 msgq(sp, M_SYSERR, "pipe");
87 goto err;
88 }
89 if ((ofp = fdopen(output[0], "r")) == NULL) {
90 msgq(sp, M_SYSERR, "fdopen");
91 goto err;
92 }
93
94 /* Fork off the utility process. */
95 switch (utility_pid = vfork()) {
96 case -1: /* Error. */
97 msgq(sp, M_SYSERR, "vfork");
98 err: if (input[0] != -1)
99 (void)close(input[0]);
100 if (input[1] != -1)
101 (void)close(input[1]);
102 if (ofp != NULL)
103 (void)fclose(ofp);
104 else if (output[0] != -1)
105 (void)close(output[0]);
106 if (output[1] != -1)
107 (void)close(output[1]);
108 return (1);
109 case 0: /* Utility. */
110 /*
111 * Redirect stdin from the read end of the input pipe, and
112 * redirect stdout/stderr to the write end of the output pipe.
113 *
114 * !!!
115 * Historically, ex only directed stdout into the input pipe,
116 * letting stderr come out on the terminal as usual. Vi did
117 * not, directing both stdout and stderr into the input pipe.
118 * We match that practice in both ex and vi for consistency.
119 */
120 if (input[0] != -1)
121 (void)dup2(input[0], STDIN_FILENO);
122 (void)dup2(output[1], STDOUT_FILENO);
123 (void)dup2(output[1], STDERR_FILENO);
124
125 /* Close the utility's file descriptors. */
126 if (input[0] != -1)
127 (void)close(input[0]);
128 if (input[1] != -1)
129 (void)close(input[1]);
130 (void)close(output[0]);
131 (void)close(output[1]);
132
133 if ((name = strrchr(O_STR(sp, O_SHELL), '/')) == NULL)
134 name = O_STR(sp, O_SHELL);
135 else
136 ++name;
137
138 INT2CHAR(sp, cmd, STRLEN(cmd)+1, np, nlen);
139 execl(O_STR(sp, O_SHELL), name, "-c", np, (char *)NULL);
140 msgq_str(sp, M_SYSERR, O_STR(sp, O_SHELL), "execl: %s");
141 _exit (127);
142 /* NOTREACHED */
143 default: /* Parent-reader, parent-writer. */
144 /* Close the pipe ends neither parent will use. */
145 if (input[0] != -1)
146 (void)close(input[0]);
147 (void)close(output[1]);
148 break;
149 }
150
151 /*
152 * FILTER_RBANG, FILTER_READ:
153 *
154 * Reading is the simple case -- we don't need a parent writer,
155 * so the parent reads the output from the read end of the output
156 * pipe until it finishes, then waits for the child. Ex_readfp
157 * appends to the MARK, and closes ofp.
158 *
159 * For FILTER_RBANG, there is nothing to write to the utility.
160 * Make sure it doesn't wait forever by closing its standard
161 * input.
162 *
163 * !!!
164 * Set the return cursor to the last line read in for FILTER_READ.
165 * Historically, this behaves differently from ":r file" command,
166 * which leaves the cursor at the first line read in. Check to
167 * make sure that it's not past EOF because we were reading into an
168 * empty file.
169 */
170 if (ftype == FILTER_RBANG || ftype == FILTER_READ) {
171 if (ftype == FILTER_RBANG)
172 (void)close(input[1]);
173
174 if (ex_readfp(sp, "filter", ofp, fm, &nread, 1))
175 rval = 1;
176 sp->rptlines[L_ADDED] += nread;
177 if (ftype == FILTER_READ) {
178 if (fm->lno == 0)
179 rp->lno = nread;
180 else
181 rp->lno += nread;
182 }
183 goto uwait;
184 }
185
186 /*
187 * FILTER_BANG, FILTER_WRITE
188 *
189 * Here we need both a reader and a writer. Temporary files are
190 * expensive and we'd like to avoid disk I/O. Using pipes has the
191 * obvious starvation conditions. It's done as follows:
192 *
193 * fork
194 * child
195 * write lines out
196 * exit
197 * parent
198 * FILTER_BANG:
199 * read lines into the file
200 * delete old lines
201 * FILTER_WRITE
202 * read and display lines
203 * wait for child
204 *
205 * XXX
206 * We get away without locking the underlying database because we know
207 * that none of the records that we're reading will be modified until
208 * after we've read them. This depends on the fact that the current
209 * B+tree implementation doesn't balance pages or similar things when
210 * it inserts new records. When the DB code has locking, we should
211 * treat vi as if it were multiple applications sharing a database, and
212 * do the required locking. If necessary a work-around would be to do
213 * explicit locking in the line.c:db_get() code, based on the flag set
214 * here.
215 */
216 F_SET(sp->ep, F_MULTILOCK);
217 switch (parent_writer_pid = fork()) {
218 case -1: /* Error. */
219 msgq(sp, M_SYSERR, "fork");
220 (void)close(input[1]);
221 (void)close(output[0]);
222 rval = 1;
223 break;
224 case 0: /* Parent-writer. */
225 /*
226 * Write the selected lines to the write end of the input
227 * pipe. This instance of ifp is closed by ex_writefp.
228 */
229 (void)close(output[0]);
230 if ((ifp = fdopen(input[1], "w")) == NULL)
231 _exit (1);
232 _exit(ex_writefp(sp, "filter", ifp, fm, tm, NULL, NULL, 1));
233
234 /* NOTREACHED */
235 default: /* Parent-reader. */
236 (void)close(input[1]);
237 if (ftype == FILTER_WRITE) {
238 /*
239 * Read the output from the read end of the output
240 * pipe and display it. Filter_ldisplay closes ofp.
241 */
242 if (filter_ldisplay(sp, ofp))
243 rval = 1;
244 } else {
245 /*
246 * Read the output from the read end of the output
247 * pipe. Ex_readfp appends to the MARK and closes
248 * ofp.
249 */
250 if (ex_readfp(sp, "filter", ofp, tm, &nread, 1))
251 rval = 1;
252 sp->rptlines[L_ADDED] += nread;
253 }
254
255 /* Wait for the parent-writer. */
256 if (proc_wait(sp,
257 (long)parent_writer_pid, "parent-writer", 0, 1))
258 rval = 1;
259
260 /* Delete any lines written to the utility. */
261 if (rval == 0 && ftype == FILTER_BANG &&
262 (cut(sp, NULL, fm, tm, CUT_LINEMODE) ||
263 del(sp, fm, tm, 1))) {
264 rval = 1;
265 break;
266 }
267
268 /*
269 * If the filter had no output, we may have just deleted
270 * the cursor. Don't do any real error correction, we'll
271 * try and recover later.
272 */
273 if (rp->lno > 1 && !db_exist(sp, rp->lno))
274 --rp->lno;
275 break;
276 }
277 F_CLR(sp->ep, F_MULTILOCK);
278
279 /*
280 * !!!
281 * Ignore errors on vi file reads, to make reads prettier. It's
282 * completely inconsistent, and historic practice.
283 */
284 uwait: INT2CHAR(sp, cmd, STRLEN(cmd) + 1, np, nlen);
285 return (proc_wait(sp, (long)utility_pid, np,
286 ftype == FILTER_READ && F_ISSET(sp, SC_VI) ? 1 : 0, 0) || rval);
287 }
288
289 /*
290 * filter_ldisplay --
291 * Display output from a utility.
292 *
293 * !!!
294 * Historically, the characters were passed unmodified to the terminal.
295 * We use the ex print routines to make sure they're printable.
296 */
297 static int
filter_ldisplay(SCR * sp,FILE * fp)298 filter_ldisplay(SCR *sp, FILE *fp)
299 {
300 size_t len;
301 size_t wlen;
302 CHAR_T *wp;
303
304 EX_PRIVATE *exp;
305
306 for (exp = EXP(sp); !ex_getline(sp, fp, &len) && !INTERRUPTED(sp);) {
307 FILE2INT5(sp, exp->ibcw, exp->ibp, len, wp, wlen);
308 if (ex_ldisplay(sp, wp, wlen, 0, 0))
309 break;
310 }
311 if (ferror(fp))
312 msgq(sp, M_SYSERR, "filter read");
313 (void)fclose(fp);
314 return (0);
315 }
316