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