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