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