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