Home | History | Annotate | Line # | Download | only in ex
      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