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(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(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 } 318