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