119304Speter/*- 219304Speter * Copyright (c) 1991, 1993, 1994 319304Speter * The Regents of the University of California. All rights reserved. 419304Speter * Copyright (c) 1991, 1993, 1994, 1995, 1996 519304Speter * Keith Bostic. All rights reserved. 619304Speter * 719304Speter * See the LICENSE file for redistribution information. 819304Speter */ 919304Speter 1019304Speter#include "config.h" 1119304Speter 1219304Speter#ifndef lint 13254225Speterstatic const char sccsid[] = "$Id: ex_filter.c,v 10.44 2003/11/05 17:11:54 skimo Exp $"; 1419304Speter#endif /* not lint */ 1519304Speter 1619304Speter#include <sys/types.h> 1719304Speter#include <sys/queue.h> 1819304Speter 1919304Speter#include <bitstring.h> 2019304Speter#include <errno.h> 2119304Speter#include <fcntl.h> 2219304Speter#include <limits.h> 2319304Speter#include <stdio.h> 2419304Speter#include <stdlib.h> 2519304Speter#include <string.h> 2619304Speter#include <unistd.h> 2719304Speter 2819304Speter#include "../common/common.h" 2919304Speter 3019304Speterstatic int filter_ldisplay __P((SCR *, FILE *)); 3119304Speter 3219304Speter/* 3319304Speter * ex_filter -- 3419304Speter * Run a range of lines through a filter utility and optionally 3519304Speter * replace the original text with the stdout/stderr output of 3619304Speter * the utility. 3719304Speter * 3819304Speter * PUBLIC: int ex_filter __P((SCR *, 39254225Speter * PUBLIC: EXCMD *, MARK *, MARK *, MARK *, CHAR_T *, enum filtertype)); 4019304Speter */ 4119304Speterint 42254225Speterex_filter(SCR *sp, EXCMD *cmdp, MARK *fm, MARK *tm, MARK *rp, CHAR_T *cmd, enum filtertype ftype) 4319304Speter{ 4419304Speter FILE *ifp, *ofp; 4519304Speter pid_t parent_writer_pid, utility_pid; 4619304Speter recno_t nread; 4719304Speter int input[2], output[2], rval; 4819304Speter char *name; 49254225Speter char *np; 50254225Speter size_t nlen; 5119304Speter 5219304Speter rval = 0; 5319304Speter 5419304Speter /* Set return cursor position, which is never less than line 1. */ 5519304Speter *rp = *fm; 5619304Speter if (rp->lno == 0) 5719304Speter rp->lno = 1; 5819304Speter 5919304Speter /* We're going to need a shell. */ 6019304Speter if (opts_empty(sp, O_SHELL, 0)) 6119304Speter return (1); 6219304Speter 6319304Speter /* 6419304Speter * There are three different processes running through this code. 6519304Speter * They are the utility, the parent-writer and the parent-reader. 6619304Speter * The parent-writer is the process that writes from the file to 6719304Speter * the utility, the parent reader is the process that reads from 6819304Speter * the utility. 6919304Speter * 7019304Speter * Input and output are named from the utility's point of view. 7119304Speter * The utility reads from input[0] and the parent(s) write to 7219304Speter * input[1]. The parent(s) read from output[0] and the utility 7319304Speter * writes to output[1]. 7419304Speter * 7519304Speter * !!! 7619304Speter * Historically, in the FILTER_READ case, the utility reads from 7719304Speter * the terminal (e.g. :r! cat works). Otherwise open up utility 7819304Speter * input pipe. 7919304Speter */ 8019304Speter ofp = NULL; 8119304Speter input[0] = input[1] = output[0] = output[1] = -1; 8219304Speter if (ftype != FILTER_READ && pipe(input) < 0) { 8319304Speter msgq(sp, M_SYSERR, "pipe"); 8419304Speter goto err; 8519304Speter } 8619304Speter 8719304Speter /* Open up utility output pipe. */ 8819304Speter if (pipe(output) < 0) { 8919304Speter msgq(sp, M_SYSERR, "pipe"); 9019304Speter goto err; 9119304Speter } 9219304Speter if ((ofp = fdopen(output[0], "r")) == NULL) { 9319304Speter msgq(sp, M_SYSERR, "fdopen"); 9419304Speter goto err; 9519304Speter } 9619304Speter 9719304Speter /* Fork off the utility process. */ 9819304Speter switch (utility_pid = vfork()) { 9919304Speter case -1: /* Error. */ 10019304Speter msgq(sp, M_SYSERR, "vfork"); 10119304Spetererr: if (input[0] != -1) 10219304Speter (void)close(input[0]); 10319304Speter if (input[1] != -1) 10419304Speter (void)close(input[1]); 10519304Speter if (ofp != NULL) 10619304Speter (void)fclose(ofp); 10719304Speter else if (output[0] != -1) 10819304Speter (void)close(output[0]); 10919304Speter if (output[1] != -1) 11019304Speter (void)close(output[1]); 11119304Speter return (1); 11219304Speter case 0: /* Utility. */ 11319304Speter /* 11419304Speter * Redirect stdin from the read end of the input pipe, and 11519304Speter * redirect stdout/stderr to the write end of the output pipe. 11619304Speter * 11719304Speter * !!! 11819304Speter * Historically, ex only directed stdout into the input pipe, 11919304Speter * letting stderr come out on the terminal as usual. Vi did 12019304Speter * not, directing both stdout and stderr into the input pipe. 12119304Speter * We match that practice in both ex and vi for consistency. 12219304Speter */ 12319304Speter if (input[0] != -1) 12419304Speter (void)dup2(input[0], STDIN_FILENO); 12519304Speter (void)dup2(output[1], STDOUT_FILENO); 12619304Speter (void)dup2(output[1], STDERR_FILENO); 12719304Speter 12819304Speter /* Close the utility's file descriptors. */ 12919304Speter if (input[0] != -1) 13019304Speter (void)close(input[0]); 13119304Speter if (input[1] != -1) 13219304Speter (void)close(input[1]); 13319304Speter (void)close(output[0]); 13419304Speter (void)close(output[1]); 13519304Speter 13619304Speter if ((name = strrchr(O_STR(sp, O_SHELL), '/')) == NULL) 13719304Speter name = O_STR(sp, O_SHELL); 13819304Speter else 13919304Speter ++name; 14019304Speter 141254225Speter INT2CHAR(sp, cmd, STRLEN(cmd)+1, np, nlen); 142254225Speter execl(O_STR(sp, O_SHELL), name, "-c", np, (char *)NULL); 14319304Speter msgq_str(sp, M_SYSERR, O_STR(sp, O_SHELL), "execl: %s"); 14419304Speter _exit (127); 14519304Speter /* NOTREACHED */ 14619304Speter default: /* Parent-reader, parent-writer. */ 14719304Speter /* Close the pipe ends neither parent will use. */ 14819304Speter if (input[0] != -1) 14919304Speter (void)close(input[0]); 15019304Speter (void)close(output[1]); 15119304Speter break; 15219304Speter } 15319304Speter 15419304Speter /* 15519304Speter * FILTER_RBANG, FILTER_READ: 15619304Speter * 15719304Speter * Reading is the simple case -- we don't need a parent writer, 15819304Speter * so the parent reads the output from the read end of the output 15919304Speter * pipe until it finishes, then waits for the child. Ex_readfp 16019304Speter * appends to the MARK, and closes ofp. 16119304Speter * 16219304Speter * For FILTER_RBANG, there is nothing to write to the utility. 16319304Speter * Make sure it doesn't wait forever by closing its standard 16419304Speter * input. 16519304Speter * 16619304Speter * !!! 16719304Speter * Set the return cursor to the last line read in for FILTER_READ. 16819304Speter * Historically, this behaves differently from ":r file" command, 16919304Speter * which leaves the cursor at the first line read in. Check to 17019304Speter * make sure that it's not past EOF because we were reading into an 17119304Speter * empty file. 17219304Speter */ 17319304Speter if (ftype == FILTER_RBANG || ftype == FILTER_READ) { 17419304Speter if (ftype == FILTER_RBANG) 17519304Speter (void)close(input[1]); 17619304Speter 17719304Speter if (ex_readfp(sp, "filter", ofp, fm, &nread, 1)) 17819304Speter rval = 1; 17919304Speter sp->rptlines[L_ADDED] += nread; 18019304Speter if (ftype == FILTER_READ) 18119304Speter if (fm->lno == 0) 18219304Speter rp->lno = nread; 18319304Speter else 18419304Speter rp->lno += nread; 18519304Speter goto uwait; 18619304Speter } 18719304Speter 18819304Speter /* 18919304Speter * FILTER_BANG, FILTER_WRITE 19019304Speter * 19119304Speter * Here we need both a reader and a writer. Temporary files are 19219304Speter * expensive and we'd like to avoid disk I/O. Using pipes has the 19319304Speter * obvious starvation conditions. It's done as follows: 19419304Speter * 19519304Speter * fork 19619304Speter * child 19719304Speter * write lines out 19819304Speter * exit 19919304Speter * parent 20019304Speter * FILTER_BANG: 20119304Speter * read lines into the file 20219304Speter * delete old lines 20319304Speter * FILTER_WRITE 20419304Speter * read and display lines 20519304Speter * wait for child 20619304Speter * 20719304Speter * XXX 20819304Speter * We get away without locking the underlying database because we know 20919304Speter * that none of the records that we're reading will be modified until 21019304Speter * after we've read them. This depends on the fact that the current 21119304Speter * B+tree implementation doesn't balance pages or similar things when 21219304Speter * it inserts new records. When the DB code has locking, we should 21319304Speter * treat vi as if it were multiple applications sharing a database, and 21419304Speter * do the required locking. If necessary a work-around would be to do 21519304Speter * explicit locking in the line.c:db_get() code, based on the flag set 21619304Speter * here. 21719304Speter */ 21819304Speter F_SET(sp->ep, F_MULTILOCK); 21919304Speter switch (parent_writer_pid = fork()) { 22019304Speter case -1: /* Error. */ 22119304Speter msgq(sp, M_SYSERR, "fork"); 22219304Speter (void)close(input[1]); 22319304Speter (void)close(output[0]); 22419304Speter rval = 1; 22519304Speter break; 22619304Speter case 0: /* Parent-writer. */ 22719304Speter /* 22819304Speter * Write the selected lines to the write end of the input 22919304Speter * pipe. This instance of ifp is closed by ex_writefp. 23019304Speter */ 23119304Speter (void)close(output[0]); 23219304Speter if ((ifp = fdopen(input[1], "w")) == NULL) 23319304Speter _exit (1); 23419304Speter _exit(ex_writefp(sp, "filter", ifp, fm, tm, NULL, NULL, 1)); 23519304Speter 23619304Speter /* NOTREACHED */ 23719304Speter default: /* Parent-reader. */ 23819304Speter (void)close(input[1]); 23919304Speter if (ftype == FILTER_WRITE) { 24019304Speter /* 24119304Speter * Read the output from the read end of the output 24219304Speter * pipe and display it. Filter_ldisplay closes ofp. 24319304Speter */ 24419304Speter if (filter_ldisplay(sp, ofp)) 24519304Speter rval = 1; 24619304Speter } else { 24719304Speter /* 24819304Speter * Read the output from the read end of the output 24919304Speter * pipe. Ex_readfp appends to the MARK and closes 25019304Speter * ofp. 25119304Speter */ 25219304Speter if (ex_readfp(sp, "filter", ofp, tm, &nread, 1)) 25319304Speter rval = 1; 25419304Speter sp->rptlines[L_ADDED] += nread; 25519304Speter } 25619304Speter 25719304Speter /* Wait for the parent-writer. */ 25819304Speter if (proc_wait(sp, 25919304Speter (long)parent_writer_pid, "parent-writer", 0, 1)) 26019304Speter rval = 1; 26119304Speter 26219304Speter /* Delete any lines written to the utility. */ 26319304Speter if (rval == 0 && ftype == FILTER_BANG && 26419304Speter (cut(sp, NULL, fm, tm, CUT_LINEMODE) || 26519304Speter del(sp, fm, tm, 1))) { 26619304Speter rval = 1; 26719304Speter break; 26819304Speter } 26919304Speter 27019304Speter /* 27119304Speter * If the filter had no output, we may have just deleted 27219304Speter * the cursor. Don't do any real error correction, we'll 27319304Speter * try and recover later. 27419304Speter */ 27519304Speter if (rp->lno > 1 && !db_exist(sp, rp->lno)) 27619304Speter --rp->lno; 27719304Speter break; 27819304Speter } 27919304Speter F_CLR(sp->ep, F_MULTILOCK); 28019304Speter 28119304Speter /* 28219304Speter * !!! 28319304Speter * Ignore errors on vi file reads, to make reads prettier. It's 28419304Speter * completely inconsistent, and historic practice. 28519304Speter */ 286254225Speteruwait: INT2CHAR(sp, cmd, STRLEN(cmd) + 1, np, nlen); 287254225Speter return (proc_wait(sp, (long)utility_pid, np, 28819304Speter ftype == FILTER_READ && F_ISSET(sp, SC_VI) ? 1 : 0, 0) || rval); 28919304Speter} 29019304Speter 29119304Speter/* 29219304Speter * filter_ldisplay -- 29319304Speter * Display output from a utility. 29419304Speter * 29519304Speter * !!! 29619304Speter * Historically, the characters were passed unmodified to the terminal. 29719304Speter * We use the ex print routines to make sure they're printable. 29819304Speter */ 29919304Speterstatic int 300254225Speterfilter_ldisplay(SCR *sp, FILE *fp) 30119304Speter{ 30219304Speter size_t len; 303254225Speter size_t wlen; 304254225Speter CHAR_T *wp; 30519304Speter 30619304Speter EX_PRIVATE *exp; 30719304Speter 308254225Speter for (exp = EXP(sp); !ex_getline(sp, fp, &len) && !INTERRUPTED(sp);) { 309254225Speter FILE2INT5(sp, exp->ibcw, exp->ibp, len, wp, wlen); 310254225Speter if (ex_ldisplay(sp, wp, wlen, 0, 0)) 31119304Speter break; 312254225Speter } 31319304Speter if (ferror(fp)) 31419304Speter msgq(sp, M_SYSERR, "filter read"); 31519304Speter (void)fclose(fp); 31619304Speter return (0); 31719304Speter} 318