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[] = "$Id: ex_filter.c,v 10.44 2003/11/05 17:11:54 skimo Exp $";
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_T *, enum filtertype));
40 */
41int
42ex_filter(SCR *sp, EXCMD *cmdp, MARK *fm, MARK *tm, MARK *rp, CHAR_T *cmd, 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], rval;
48	char *name;
49	char *np;
50	size_t nlen;
51
52	rval = 0;
53
54	/* Set return cursor position, which is never less than line 1. */
55	*rp = *fm;
56	if (rp->lno == 0)
57		rp->lno = 1;
58
59	/* We're going to need a shell. */
60	if (opts_empty(sp, O_SHELL, 0))
61		return (1);
62
63	/*
64	 * There are three different processes running through this code.
65	 * They are the utility, the parent-writer and the parent-reader.
66	 * The parent-writer is the process that writes from the file to
67	 * the utility, the parent reader is the process that reads from
68	 * the utility.
69	 *
70	 * Input and output are named from the utility's point of view.
71	 * The utility reads from input[0] and the parent(s) write to
72	 * input[1].  The parent(s) read from output[0] and the utility
73	 * writes to output[1].
74	 *
75	 * !!!
76	 * Historically, in the FILTER_READ case, the utility reads from
77	 * the terminal (e.g. :r! cat works).  Otherwise open up utility
78	 * input pipe.
79	 */
80	ofp = NULL;
81	input[0] = input[1] = output[0] = output[1] = -1;
82	if (ftype != FILTER_READ && pipe(input) < 0) {
83		msgq(sp, M_SYSERR, "pipe");
84		goto err;
85	}
86
87	/* Open up utility output pipe. */
88	if (pipe(output) < 0) {
89		msgq(sp, M_SYSERR, "pipe");
90		goto err;
91	}
92	if ((ofp = fdopen(output[0], "r")) == NULL) {
93		msgq(sp, M_SYSERR, "fdopen");
94		goto err;
95	}
96
97	/* Fork off the utility process. */
98	switch (utility_pid = vfork()) {
99	case -1:			/* Error. */
100		msgq(sp, M_SYSERR, "vfork");
101err:		if (input[0] != -1)
102			(void)close(input[0]);
103		if (input[1] != -1)
104			(void)close(input[1]);
105		if (ofp != NULL)
106			(void)fclose(ofp);
107		else if (output[0] != -1)
108			(void)close(output[0]);
109		if (output[1] != -1)
110			(void)close(output[1]);
111		return (1);
112	case 0:				/* Utility. */
113		/*
114		 * Redirect stdin from the read end of the input pipe, and
115		 * redirect stdout/stderr to the write end of the output pipe.
116		 *
117		 * !!!
118		 * Historically, ex only directed stdout into the input pipe,
119		 * letting stderr come out on the terminal as usual.  Vi did
120		 * not, directing both stdout and stderr into the input pipe.
121		 * We match that practice in both ex and vi for consistency.
122		 */
123		if (input[0] != -1)
124			(void)dup2(input[0], STDIN_FILENO);
125		(void)dup2(output[1], STDOUT_FILENO);
126		(void)dup2(output[1], STDERR_FILENO);
127
128		/* Close the utility's file descriptors. */
129		if (input[0] != -1)
130			(void)close(input[0]);
131		if (input[1] != -1)
132			(void)close(input[1]);
133		(void)close(output[0]);
134		(void)close(output[1]);
135
136		if ((name = strrchr(O_STR(sp, O_SHELL), '/')) == NULL)
137			name = O_STR(sp, O_SHELL);
138		else
139			++name;
140
141		INT2CHAR(sp, cmd, STRLEN(cmd)+1, np, nlen);
142		execl(O_STR(sp, O_SHELL), name, "-c", np, (char *)NULL);
143		msgq_str(sp, M_SYSERR, O_STR(sp, O_SHELL), "execl: %s");
144		_exit (127);
145		/* NOTREACHED */
146	default:			/* Parent-reader, parent-writer. */
147		/* Close the pipe ends neither parent will use. */
148		if (input[0] != -1)
149			(void)close(input[0]);
150		(void)close(output[1]);
151		break;
152	}
153
154	/*
155	 * FILTER_RBANG, FILTER_READ:
156	 *
157	 * Reading is the simple case -- we don't need a parent writer,
158	 * so the parent reads the output from the read end of the output
159	 * pipe until it finishes, then waits for the child.  Ex_readfp
160	 * appends to the MARK, and closes ofp.
161	 *
162	 * For FILTER_RBANG, there is nothing to write to the utility.
163	 * Make sure it doesn't wait forever by closing its standard
164	 * input.
165	 *
166	 * !!!
167	 * Set the return cursor to the last line read in for FILTER_READ.
168	 * Historically, this behaves differently from ":r file" command,
169	 * which leaves the cursor at the first line read in.  Check to
170	 * make sure that it's not past EOF because we were reading into an
171	 * empty file.
172	 */
173	if (ftype == FILTER_RBANG || ftype == FILTER_READ) {
174		if (ftype == FILTER_RBANG)
175			(void)close(input[1]);
176
177		if (ex_readfp(sp, "filter", ofp, fm, &nread, 1))
178			rval = 1;
179		sp->rptlines[L_ADDED] += nread;
180		if (ftype == FILTER_READ)
181			if (fm->lno == 0)
182				rp->lno = nread;
183			else
184				rp->lno += nread;
185		goto uwait;
186	}
187
188	/*
189	 * FILTER_BANG, FILTER_WRITE
190	 *
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:
194	 *
195	 *	fork
196	 *	child
197	 *		write lines out
198	 *		exit
199	 *	parent
200	 *		FILTER_BANG:
201	 *			read lines into the file
202	 *			delete old lines
203	 *		FILTER_WRITE
204	 *			read and display lines
205	 *		wait for child
206	 *
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:db_get() code, based on the flag set
216	 * here.
217	 */
218	F_SET(sp->ep, F_MULTILOCK);
219	switch (parent_writer_pid = fork()) {
220	case -1:			/* Error. */
221		msgq(sp, M_SYSERR, "fork");
222		(void)close(input[1]);
223		(void)close(output[0]);
224		rval = 1;
225		break;
226	case 0:				/* Parent-writer. */
227		/*
228		 * Write the selected lines to the write end of the input
229		 * pipe.  This instance of ifp is closed by ex_writefp.
230		 */
231		(void)close(output[0]);
232		if ((ifp = fdopen(input[1], "w")) == NULL)
233			_exit (1);
234		_exit(ex_writefp(sp, "filter", ifp, fm, tm, NULL, NULL, 1));
235
236		/* NOTREACHED */
237	default:			/* Parent-reader. */
238		(void)close(input[1]);
239		if (ftype == FILTER_WRITE) {
240			/*
241			 * Read the output from the read end of the output
242			 * pipe and display it.  Filter_ldisplay closes ofp.
243			 */
244			if (filter_ldisplay(sp, ofp))
245				rval = 1;
246		} else {
247			/*
248			 * Read the output from the read end of the output
249			 * pipe.  Ex_readfp appends to the MARK and closes
250			 * ofp.
251			 */
252			if (ex_readfp(sp, "filter", ofp, tm, &nread, 1))
253				rval = 1;
254			sp->rptlines[L_ADDED] += nread;
255		}
256
257		/* Wait for the parent-writer. */
258		if (proc_wait(sp,
259		    (long)parent_writer_pid, "parent-writer", 0, 1))
260			rval = 1;
261
262		/* Delete any lines written to the utility. */
263		if (rval == 0 && ftype == FILTER_BANG &&
264		    (cut(sp, NULL, fm, tm, CUT_LINEMODE) ||
265		    del(sp, fm, tm, 1))) {
266			rval = 1;
267			break;
268		}
269
270		/*
271		 * If the filter had no output, we may have just deleted
272		 * the cursor.  Don't do any real error correction, we'll
273		 * try and recover later.
274		 */
275		 if (rp->lno > 1 && !db_exist(sp, rp->lno))
276			--rp->lno;
277		break;
278	}
279	F_CLR(sp->ep, F_MULTILOCK);
280
281	/*
282	 * !!!
283	 * Ignore errors on vi file reads, to make reads prettier.  It's
284	 * completely inconsistent, and historic practice.
285	 */
286uwait:	INT2CHAR(sp, cmd, STRLEN(cmd) + 1, np, nlen);
287	return (proc_wait(sp, (long)utility_pid, np,
288	    ftype == FILTER_READ && F_ISSET(sp, SC_VI) ? 1 : 0, 0) || rval);
289}
290
291/*
292 * filter_ldisplay --
293 *	Display output from a utility.
294 *
295 * !!!
296 * Historically, the characters were passed unmodified to the terminal.
297 * We use the ex print routines to make sure they're printable.
298 */
299static int
300filter_ldisplay(SCR *sp, FILE *fp)
301{
302	size_t len;
303	size_t wlen;
304	CHAR_T *wp;
305
306	EX_PRIVATE *exp;
307
308	for (exp = EXP(sp); !ex_getline(sp, fp, &len) && !INTERRUPTED(sp);) {
309		FILE2INT5(sp, exp->ibcw, exp->ibp, len, wp, wlen);
310		if (ex_ldisplay(sp, wp, wlen, 0, 0))
311			break;
312	}
313	if (ferror(fp))
314		msgq(sp, M_SYSERR, "filter read");
315	(void)fclose(fp);
316	return (0);
317}
318