xref: /freebsd/contrib/nvi/ex/ex_filter.c (revision c1d255d3)
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
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
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