xref: /dragonfly/contrib/nvi2/ex/ex_filter.c (revision 07bc39c2f4bbca56f12568e06d89da17f2eeb965)
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
ex_filter(SCR * sp,EXCMD * cmdp,MARK * fm,MARK * tm,MARK * rp,CHAR_T * cmd,enum filtertype ftype)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                     goto uwait;
183           }
184 
185           /*
186            * FILTER_BANG, FILTER_WRITE
187            *
188            * Here we need both a reader and a writer.  Temporary files are
189            * expensive and we'd like to avoid disk I/O.  Using pipes has the
190            * obvious starvation conditions.  It's done as follows:
191            *
192            *        fork
193            *        child
194            *                  write lines out
195            *                  exit
196            *        parent
197            *                  FILTER_BANG:
198            *                            read lines into the file
199            *                            delete old lines
200            *                  FILTER_WRITE
201            *                            read and display lines
202            *                  wait for child
203            *
204            * XXX
205            * We get away without locking the underlying database because we know
206            * that none of the records that we're reading will be modified until
207            * after we've read them.  This depends on the fact that the current
208            * B+tree implementation doesn't balance pages or similar things when
209            * it inserts new records.  When the DB code has locking, we should
210            * treat vi as if it were multiple applications sharing a database, and
211            * do the required locking.  If necessary a work-around would be to do
212            * explicit locking in the line.c:db_get() code, based on the flag set
213            * here.
214            */
215           F_SET(sp->ep, F_MULTILOCK);
216           switch (parent_writer_pid = fork()) {
217           case -1:                      /* Error. */
218                     msgq(sp, M_SYSERR, "fork");
219                     (void)close(input[1]);
220                     (void)close(output[0]);
221                     rval = 1;
222                     break;
223           case 0:                                 /* Parent-writer. */
224                     /*
225                      * Write the selected lines to the write end of the input
226                      * pipe.  This instance of ifp is closed by ex_writefp.
227                      */
228                     (void)close(output[0]);
229                     if ((ifp = fdopen(input[1], "w")) == NULL)
230                               _exit (1);
231                     _exit(ex_writefp(sp, "filter", ifp, fm, tm, NULL, NULL, 1));
232 
233                     /* NOTREACHED */
234           default:                      /* Parent-reader. */
235                     (void)close(input[1]);
236                     if (ftype == FILTER_WRITE) {
237                               /*
238                                * Read the output from the read end of the output
239                                * pipe and display it.  Filter_ldisplay closes ofp.
240                                */
241                               if (filter_ldisplay(sp, ofp))
242                                         rval = 1;
243                     } else {
244                               /*
245                                * Read the output from the read end of the output
246                                * pipe.  Ex_readfp appends to the MARK and closes
247                                * ofp.
248                                */
249                               if (ex_readfp(sp, "filter", ofp, tm, &nread, 1))
250                                         rval = 1;
251                               sp->rptlines[L_ADDED] += nread;
252                     }
253 
254                     /* Wait for the parent-writer. */
255                     if (proc_wait(sp,
256                         (long)parent_writer_pid, "parent-writer", 0, 1))
257                               rval = 1;
258 
259                     /* Delete any lines written to the utility. */
260                     if (rval == 0 && ftype == FILTER_BANG &&
261                         (cut(sp, NULL, fm, tm, CUT_LINEMODE) ||
262                         del(sp, fm, tm, 1))) {
263                               rval = 1;
264                               break;
265                     }
266 
267                     /*
268                      * If the filter had no output, we may have just deleted
269                      * the cursor.  Don't do any real error correction, we'll
270                      * try and recover later.
271                      */
272                      if (rp->lno > 1 && !db_exist(sp, rp->lno))
273                               --rp->lno;
274                     break;
275           }
276           F_CLR(sp->ep, F_MULTILOCK);
277 
278           /*
279            * !!!
280            * Ignore errors on vi file reads, to make reads prettier.  It's
281            * completely inconsistent, and historic practice.
282            */
283 uwait:    INT2CHAR(sp, cmd, STRLEN(cmd) + 1, np, nlen);
284           return (proc_wait(sp, (long)utility_pid, np,
285               ftype == FILTER_READ && F_ISSET(sp, SC_VI) ? 1 : 0, 0) || rval);
286 }
287 
288 /*
289  * filter_ldisplay --
290  *        Display output from a utility.
291  *
292  * !!!
293  * Historically, the characters were passed unmodified to the terminal.
294  * We use the ex print routines to make sure they're printable.
295  */
296 static int
filter_ldisplay(SCR * sp,FILE * fp)297 filter_ldisplay(SCR *sp, FILE *fp)
298 {
299           size_t len;
300           size_t wlen;
301           CHAR_T *wp;
302 
303           EX_PRIVATE *exp;
304 
305           for (exp = EXP(sp); !ex_getline(sp, fp, &len) && !INTERRUPTED(sp);) {
306                     FILE2INT5(sp, exp->ibcw, exp->ibp, len, wp, wlen);
307                     if (ex_ldisplay(sp, wp, wlen, 0, 0))
308                               break;
309           }
310           if (ferror(fp))
311                     msgq(sp, M_SYSERR, "filter read");
312           (void)fclose(fp);
313           return (0);
314 }
315