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