[BACK]Return to ex_filter.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / vi / ex

File: [local] / src / usr.bin / vi / ex / ex_filter.c (download)

Revision 1.15, Mon Aug 1 18:27:35 2016 UTC (7 years, 10 months ago) by bentley
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, OPENBSD_7_2_BASE, OPENBSD_7_2, OPENBSD_7_1_BASE, OPENBSD_7_1, OPENBSD_7_0_BASE, OPENBSD_7_0, OPENBSD_6_9_BASE, OPENBSD_6_9, OPENBSD_6_8_BASE, OPENBSD_6_8, OPENBSD_6_7_BASE, OPENBSD_6_7, OPENBSD_6_6_BASE, OPENBSD_6_6, OPENBSD_6_5_BASE, OPENBSD_6_5, OPENBSD_6_4_BASE, OPENBSD_6_4, OPENBSD_6_3_BASE, OPENBSD_6_3, OPENBSD_6_2_BASE, OPENBSD_6_2, OPENBSD_6_1_BASE, OPENBSD_6_1, HEAD
Changes since 1.14: +2 -6 lines

Remove vi's "directory" option and TMPDIR support.

ok jung@

/*	$OpenBSD: ex_filter.c,v 1.15 2016/08/01 18:27:35 bentley Exp $	*/

/*-
 * Copyright (c) 1991, 1993, 1994
 *	The Regents of the University of California.  All rights reserved.
 * Copyright (c) 1991, 1993, 1994, 1995, 1996
 *	Keith Bostic.  All rights reserved.
 *
 * See the LICENSE file for redistribution information.
 */

#include "config.h"

#include <sys/stat.h>
#include <sys/types.h>
#include <sys/queue.h>

#include <bitstring.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "../common/common.h"

static int filter_ldisplay(SCR *, FILE *);

/*
 * ex_filter --
 *	Run a range of lines through a filter utility and optionally
 *	replace the original text with the stdout/stderr output of
 *	the utility.
 *
 * PUBLIC: int ex_filter(SCR *, 
 * PUBLIC:    EXCMD *, MARK *, MARK *, MARK *, char *, enum filtertype);
 */
int
ex_filter(SCR *sp, EXCMD *cmdp, MARK *fm, MARK *tm, MARK *rp, char *cmd,
    enum filtertype ftype)
{
	FILE *ifp, *ofp;
	pid_t parent_writer_pid, utility_pid;
	recno_t nread;
	int input[2], output[2], fd, rval;
	char *name, tname[] = "/tmp/vi.XXXXXXXXXX";

	rval = 0;

	/* Set return cursor position, which is never less than line 1. */
	*rp = *fm;
	if (rp->lno == 0)
		rp->lno = 1;

	/* We're going to need a shell. */
	if (opts_empty(sp, O_SHELL, 0))
		return (1);

	/*
	 * There are three different processes running through this code.
	 * They are the utility, the parent-writer and the parent-reader.
	 * The parent-writer is the process that writes from the file to
	 * the utility, the parent reader is the process that reads from
	 * the utility.
	 *
	 * Input and output are named from the utility's point of view.
	 * The utility reads from input[0] and the parent(s) write to
	 * input[1].  The parent(s) read from output[0] and the utility
	 * writes to output[1].
	 *
	 * !!!
	 * Historically, in the FILTER_READ case, the utility reads from
	 * the terminal (e.g. :r! cat works).  Otherwise open up utility
	 * input pipe.
	 */
	ofp = NULL;
	input[0] = input[1] = output[0] = output[1] = -1;

	if (ftype == FILTER_BANG) {
		fd = mkstemp(tname);
		if (fd == -1) {
			msgq(sp, M_SYSERR,
			    "Unable to create temporary file");
			if (fd != -1) {
				(void)close(fd);
				(void)unlink(tname);
			}
			goto err;
		}
		if (unlink(tname) == -1)
			msgq(sp, M_SYSERR, "unlink");
		if ((ifp = fdopen(fd, "w")) == NULL) {
			msgq(sp, M_SYSERR, "fdopen");
			(void)close(fd);
			goto err;
		}
		if ((input[0] = dup(fd)) == -1) {
			msgq(sp, M_SYSERR, "dup");
			(void)fclose(ifp);
			goto err;
		}
		/*
		 * Write the selected lines into the temporary file.
		 * This instance of ifp is closed by ex_writefp.
		 */
		if (ex_writefp(sp, "filter", ifp, fm, tm, NULL, NULL, 1))
			goto err;
		if (lseek(input[0], 0, SEEK_SET) == -1) {
			msgq(sp, M_SYSERR, "lseek");
			goto err;
		}
	} else if (ftype != FILTER_READ && pipe(input) < 0) {
		msgq(sp, M_SYSERR, "pipe");
		goto err;
	}

	/* Open up utility output pipe. */
	if (pipe(output) < 0) {
		msgq(sp, M_SYSERR, "pipe");
		goto err;
	}
	if ((ofp = fdopen(output[0], "r")) == NULL) {
		msgq(sp, M_SYSERR, "fdopen");
		goto err;
	}

	/* Fork off the utility process. */
	switch (utility_pid = vfork()) {
	case -1:			/* Error. */
		msgq(sp, M_SYSERR, "vfork");
err:		if (input[0] != -1)
			(void)close(input[0]);
		if (input[1] != -1)
			(void)close(input[1]);
		if (ofp != NULL)
			(void)fclose(ofp);
		else if (output[0] != -1)
			(void)close(output[0]);
		if (output[1] != -1)
			(void)close(output[1]);
		return (1);
	case 0:				/* Utility. */
		/*
		 * Redirect stdin from the read end of the input pipe, and
		 * redirect stdout/stderr to the write end of the output pipe.
		 *
		 * !!!
		 * Historically, ex only directed stdout into the input pipe,
		 * letting stderr come out on the terminal as usual.  Vi did
		 * not, directing both stdout and stderr into the input pipe.
		 * We match that practice in both ex and vi for consistency.
		 */
		if (input[0] != -1)
			(void)dup2(input[0], STDIN_FILENO);
		(void)dup2(output[1], STDOUT_FILENO);
		(void)dup2(output[1], STDERR_FILENO);

		/* Close the utility's file descriptors. */
		if (input[0] != -1)
			(void)close(input[0]);
		if (input[1] != -1)
			(void)close(input[1]);
		(void)close(output[0]);
		(void)close(output[1]);

		if ((name = strrchr(O_STR(sp, O_SHELL), '/')) == NULL)
			name = O_STR(sp, O_SHELL);
		else
			++name;

		execl(O_STR(sp, O_SHELL), name, "-c", cmd, (char *)NULL);
		msgq_str(sp, M_SYSERR, O_STR(sp, O_SHELL), "execl: %s");
		_exit (127);
		/* NOTREACHED */
	default:			/* Parent-reader, parent-writer. */
		/* Close the pipe ends neither parent will use. */
		if (input[0] != -1)
			(void)close(input[0]);
		(void)close(output[1]);
		break;
	}

	/*
	 * FILTER_RBANG, FILTER_READ:
	 *
	 * Reading is the simple case -- we don't need a parent writer,
	 * so the parent reads the output from the read end of the output
	 * pipe until it finishes, then waits for the child.  Ex_readfp
	 * appends to the MARK, and closes ofp.
	 *
	 * For FILTER_RBANG, there is nothing to write to the utility.
	 * Make sure it doesn't wait forever by closing its standard
	 * input.
	 *
	 * !!!
	 * Set the return cursor to the last line read in for FILTER_READ.
	 * Historically, this behaves differently from ":r file" command,
	 * which leaves the cursor at the first line read in.  Check to
	 * make sure that it's not past EOF because we were reading into an
	 * empty file.
	 */
	if (ftype == FILTER_RBANG || ftype == FILTER_READ) {
		if (ftype == FILTER_RBANG)
			(void)close(input[1]);

		if (ex_readfp(sp, "filter", ofp, fm, &nread, 1))
			rval = 1;
		sp->rptlines[L_ADDED] += nread;
		if (ftype == FILTER_READ) {
			if (fm->lno == 0)
				rp->lno = nread;
			else
				rp->lno += nread;
		}
	}

	/*
	 * FILTER_WRITE
	 *
	 * Here we need both a reader and a writer.  Temporary files are
	 * expensive and we'd like to avoid disk I/O.  Using pipes has the
	 * obvious starvation conditions.  It's done as follows:
	 *
	 *	fork
	 *	child
	 *		write lines out
	 *		exit
	 *	parent
	 *		read and display lines
	 *		wait for child
	 *
	 * We get away without locking the underlying database because we know
	 * that filter_ldisplay() does not modify it.  When the DB code has
	 * locking, we should treat vi as if it were multiple applications
	 * sharing a database, and do the required locking.  If necessary a
	 * work-around would be to do explicit locking in the line.c:db_get()
	 * code, based on the flag set here.
	 */
	if (ftype == FILTER_WRITE) {
		F_SET(sp->ep, F_MULTILOCK);
		switch (parent_writer_pid = fork()) {
		case -1:		/* Error. */
			msgq(sp, M_SYSERR, "fork");
			(void)close(input[1]);
			(void)close(output[0]);
			rval = 1;
			break;
		case 0:			/* Parent-writer. */
			/*
			 * Write the selected lines to the write end of the
			 * input pipe.  This instance of ifp is closed by
			 * ex_writefp.
			 */
			(void)close(output[0]);
			if ((ifp = fdopen(input[1], "w")) == NULL)
				_exit (1);
			_exit(ex_writefp(sp, "filter",
			    ifp, fm, tm, NULL, NULL, 1));
			/* NOTREACHED */
		default:		/* Parent-reader. */
			(void)close(input[1]);
			/*
			 * Read the output from the read end of the output
			 * pipe and display it.  Filter_ldisplay closes ofp.
			 */
			if (filter_ldisplay(sp, ofp))
				rval = 1;

			/* Wait for the parent-writer. */
			if (proc_wait(sp,
			    parent_writer_pid, "parent-writer", 0, 1))
				rval = 1;
			break;
		}
		F_CLR(sp->ep, F_MULTILOCK);
	}

	/*
	 * FILTER_BANG
	 *
	 * Here we need a temporary file because our database lacks locking.
	 *
	 * XXX
	 * Temporary files are expensive and we'd like to avoid disk I/O.
	 * When the DB code has locking, we should treat vi as if it were
	 * multiple applications sharing a database, and do the required
	 * locking.  If necessary a work-around would be to do explicit
	 * locking in the line.c:db_get() code, based on F_MULTILOCK flag set
	 * here.
	 */
	if (ftype == FILTER_BANG) {
		/*
		 * Read the output from the read end of the output
		 * pipe.  Ex_readfp appends to the MARK and closes
		 * ofp.
		 */
		if (ex_readfp(sp, "filter", ofp, tm, &nread, 1))
			rval = 1;
		sp->rptlines[L_ADDED] += nread;

		/* Delete any lines written to the utility. */
		if (rval == 0 &&
		    (cut(sp, NULL, fm, tm, CUT_LINEMODE) ||
		    del(sp, fm, tm, 1))) {
			rval = 1;
			goto uwait;
		}

		/*
		 * If the filter had no output, we may have just deleted
		 * the cursor.  Don't do any real error correction, we'll
		 * try and recover later.
		 */
		 if (rp->lno > 1 && !db_exist(sp, rp->lno))
			--rp->lno;
	}

	/*
	 * !!!
	 * Ignore errors on vi file reads, to make reads prettier.  It's
	 * completely inconsistent, and historic practice.
	 */
uwait:	return (proc_wait(sp, utility_pid, cmd,
	    ftype == FILTER_READ && F_ISSET(sp, SC_VI) ? 1 : 0, 0) || rval);
}

/*
 * filter_ldisplay --
 *	Display output from a utility.
 *
 * !!!
 * Historically, the characters were passed unmodified to the terminal.
 * We use the ex print routines to make sure they're printable.
 */
static int
filter_ldisplay(SCR *sp, FILE *fp)
{
	size_t len;

	EX_PRIVATE *exp;

	for (exp = EXP(sp); !ex_getline(sp, fp, &len) && !INTERRUPTED(sp);)
		if (ex_ldisplay(sp, exp->ibp, len, 0, 0))
			break;
	if (ferror(fp))
		msgq(sp, M_SYSERR, "filter read");
	(void)fclose(fp);
	return (0);
}