/*
 *   Copyright (c) 1999-2000 David Parsons. All rights reserved.
 *   
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *  3. My name may not be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *     
 *  THIS SOFTWARE IS PROVIDED BY DAVID PARSONS ``AS IS'' AND ANY
 *  EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DAVID
 *  PARSONS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 *  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 *  IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 *  THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * actions() do various filtering actions
 */

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>

#include "magicfilter.h"
#include "rule.h"


/*
 * spool() copies the input out to a workfile, and returns its name
 */
static int
spool(char *bfr, int len, char **name)
{
#define TEMPLATE	"/tmp/magicXXXXXX"
    static char tmpfile[sizeof(TEMPLATE) + 1];
    int fd;
    char blk[1024];
    int size;

    sprintf(tmpfile, TEMPLATE);

    if ( mktemp(tmpfile) == 0) {
	perror("spool::mktemp");
	exit(1);
    }

    fd = open(tmpfile, O_RDWR|O_EXCL|O_CREAT, 0600);

    if (fd == -1) {
	perror("spool::open");
	exit(1);
    }
    if (len > 0 && write(fd, bfr, len) != len) {
	perror("spool::write");
	unlink(tmpfile);
	exit(1);
    }
    while ((size = read(0, blk, sizeof blk)) > 0)
	if (write(fd, blk, size) != size) {
	    perror("spool::write");
	    unlink(tmpfile);
	    exit(1);
	}

    *name = tmpfile;

    lseek(fd, 0, 0);
    return fd;
} /* spool */


/*
 * execute() runs a program, replacing $FILE arguments with
 * the passed-in filename
 */
static int
execute(struct rule *r, char *file)
{
    pid_t child;
    int status;

    if ( (child = fork()) < 0) {
	perror("execute::fork");
	exit(1);
    }
    if (child == 0) {
	int x;

	for (x=0; x < r->argc; x++)
	    if (strcmp(r->argv[x], "$FILE") == 0)
		r->argv[x] = file;

	trace("execute", r);
	execvp(r->argv[0], r->argv);
	perror(r->argv[0]);
	exit(1);
    }
    waitpid(child, &status, 0);
    unlink(file);

    return (WIFEXITED(status) == 0 || WEXITSTATUS(status) != 0) ? 1 : 0;
} /* execute */


/*
 * pipe_it(): process text and refeed it to magicfilter.
 */
void
pipe_it(char *blk, int size, struct rule *r)
{
    int io[2];
    int sio[2];
    pid_t spawn;
    char bfr[1024];
    int bsize;
    int status;
    char *file = 0;
    pid_t sysproc;

    if (pipe(io) == -1) {
	perror("pipe::pipe");
	exit(1);
    }

    switch (spawn = fork()) {
    case -1:
	perror("pipe::fork");
	exit(1);

    default:
	/* parent -- run xyzzy against the output of the filter, then exit
	 */
	dup2(io[0], 0);
	close(io[1]);
	level++;
	if (r->hinted)
	    plugh(r->hinted, 0, 0);
	else
	    xyzzy();
	exit(0);	/* be paranoid */

    case 0:
	if (r->action == FFILTER) {
	    /* fpipe spools stdin to a file before processing, so it's easy
	     */
	    int fd;

	    fd = spool(blk, size, &file);
	    dup2(fd, 0);
	    dup2(io[1], 1);
	    close(io[0]);
	    exit(execute(r, file));
	}
	else if (size == 0 || (isatty(0) == 0 && lseek(0, 0, 0) != -1)) {
	    /* if we got here via hinting, or our input is a file,
	     * just rewind and fork off the filter
	     */
	    dup2(io[1], 1);
	    close(io[0]);
	    trace("rewind", r);
	    execvp(r->argv[0], r->argv);
	    perror(r->argv[0]);
	    exit(1);
	}
	else {
	    /* we're reading input from a pipe or a terminal, so we
	     * must refork again so we can shovel what we got plus
	     * what we didn't get into the filter.
	     */
	    if (pipe(sio) == -1) {
		perror("pipe::pipe");
		exit(1);
	    }
	    switch (sysproc = fork()) {
	    case -1:
		perror("pipe::fork");
		exit(1);
	    case 0:
		/* child -- execvp() the process
		 */
		close(sio[1]);
		close(io[0]);
		dup2(sio[0], 0);
		dup2(io[1], 1);
		trace("pipe", r);
		execvp(r->argv[0], r->argv);
		perror(r->argv[0]);
		exit(1);
	    default:
		/* parent -- copy blk + stdin -> stdout
		 */
		close(io[0]);
		close(io[1]);
		close(sio[0]);
		write(sio[1], blk, size);
		while ((size = read(0, bfr, sizeof bfr)) > 0)
		    write(sio[1], bfr, size);
		close(sio[1]);
		waitpid(spawn, &status, 0);
	    }
	}
    }
} /* pipe_it */


/*
 * filter_it() processes text and dumps it to stdout
 */
void
filter_it(char *blk, int size, struct rule *r)
{
    int fd;
    char *file;
    char bfr[1024];
    int io[2];
    pid_t spawn;
    int status;

    if (r->action == FPIPE) {
	/* spool and pipe
	 */
	fd = spool(bfr, size, &file);
	dup2(fd, 0);
	exit(execute(r, file));
    }
    else if (size == 0 || (isatty(0) == 0 && lseek(0, 0, 0) != -1)) {
	/* if we got here via hinting, or our input is a file,
	 * just rewind and fork off the filter
	 */
	trace("rewind", r);
	execvp(r->argv[0], r->argv);
	perror(r->argv[0]);
	exit(1);
    }
    /* fork and feed all of our input to the command
     */
    if (pipe(io) == -1) {
	perror("filter::pipe");
	exit(1);
    }

    switch (spawn = fork()) {
    case -1:
	perror("filter::spawn");
	exit(1);
    case 0:
	dup2(io[0], 0);
	close(io[1]);
	trace("pipe", r);
	execvp(r->argv[0], r->argv);
	perror(r->argv[0]);
	exit(1);
    default:
	close(io[0]);
	if ( write(io[1], blk, size) != size ) {
	    perror("filter::write");
	    exit(1);
	}
	while ((size = read(0, bfr, sizeof bfr)) > 0) {
	    write(io[1], bfr, size);
	}
	close(io[1]);
	waitpid(spawn, &status, 0);
    }
} /* filter_it */


/*
 * cat_it() copies input to output, doing various magic to it
 */
void
cat_it(char *blk, int size, struct rule *r)
{
    char bfr[1024], out[1024+1];
    int idx;
    int odx;
    int lastchar = 0;

    if (r->argc > 0)
	write(1, r->argv[0], strlen(r->argv[0]));

    trace(0, r);
    switch (r->action) {
    case TEXT:
    case POSTSCRIPT:
	odx = 0;
    shovel:
	if (size > 0)
	    lastchar = blk[size-1];
	for (idx = 0; idx < size; idx++) {
	    if (odx >= 1024) {
		write(1, out, odx);
		odx = 0;
	    }
	    if (blk[idx] == '\n')
		out[odx++] = '\r';
	    out[odx++] = blk[idx];
	}
	if ( (size = read(0, bfr, sizeof bfr)) > 0) {
	    blk = bfr;
	    goto shovel;
	}
	if (odx > 0)
	    write(1, out, odx);
	break;
    default:
	if (size > 0)
	    write(1, blk, size);
	while ((size = read(0, bfr, sizeof bfr)) > 0)
	    write(1, bfr, size);
	break;
    }
    if (r->argc > 1)
	write(1, r->argv[1], strlen(r->argv[1]));
    if (r->action == POSTSCRIPT && r->argc <= 1 && lastchar != '\004')
	write(1, "\004", 1);
} /* cat_it */
