ex_filter.c revision 19305
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 13static 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 30static 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 */ 41int 42ex_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"); 104err: 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 */ 288uwait: 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 */ 300static int 301filter_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} 317