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