GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/vi/build/../ex/ex_filter.c Lines: 0 116 0.0 %
Date: 2017-11-07 Branches: 0 112 0.0 %

Line Branch Exec Source
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
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
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
}