]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/nvi/ex/ex_filter.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / contrib / nvi / ex / ex_filter.c
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
13 static 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
30 static 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  */
41 int
42 ex_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");
101 err:            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          */
286 uwait:  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  */
299 static int
300 filter_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 }