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