// ddiff/dpatch - manipulate differences between debian packages.
// Copyright 2000 Tom Rothamel <tom-ddiff@onegeek.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  

#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <malloc.h>
#include "genfile.h"

#define debug(x, y...) fprintf(stderr, "gfpipe: " x "\n" , ## y) /**/
/* #define debug(x, y...) /**/

void (*on_gfpipe)() = NULL;

// This is an implementation of a GFILE that takes a gfile and
// pipes it through a command, capturing the output and making it
// into a GFILE.
struct PipeGF : public GFILE {
	int feed_pid;
	int cmd_pid;
	FILE *f;
	char *cmd;
	GFILE *parent;
	int valid;
	long offset;

	
	void start();
	void stop();
	
	char *gets(char *, int);
	size_t read(void *, size_t, size_t);
	void seek(long);
	void skip(long);
	long tell();
	GFILE *dup();
	
	PipeGF(char *, GFILE *);
	~PipeGF();
};

char *PipeGF::gets(char *s, int l) {
	char *rv;

	if (!valid) start();
	if (!valid) return NULL;
	
	rv = fgets(s, l, f);
	offset += strlen(rv);
	return rv;
}

size_t PipeGF::read(void *b, size_t size, size_t nmemb) {
	int rv;

	if (!valid) start();
	if (!valid) return 0;

	rv = fread(b, size, nmemb, f);
	offset += rv * size;

	return rv;
}

void PipeGF::seek(long to) {
	if (valid) {		
		if (to >= offset) {
			skip(to - offset);
			return;
		}
		stop();
	}

//	debug("Arrgh... Backstepping hurts.");		
	
	start();
	skip(to);
}

void PipeGF::skip(long left) {
	char buf[1024];
	int l;

	if (!valid) {
		start();
		if (!valid) return;
	}
	
	while (l = fread(buf, 1, (left > 1024) ? 1024 : left, f)) {
		left -= l;
		offset += l;
	}
}

long PipeGF::tell() {
	return offset;
}

void PipeGF::stop() {
	if (!valid) return;

	valid = 0;

	if (f) {
		fclose(f);
		f = NULL;
	}

	if (feed_pid > 0) {
		kill(feed_pid, SIGKILL);
		waitpid(feed_pid, NULL, 0);
	}

	if (cmd_pid > 0) {
		kill(cmd_pid, SIGKILL);
		waitpid(cmd_pid, NULL, 0);
	}	
}

// Return a fd open to /dev/null.
int devnull() {
	static int fd = 0;

	if (fd) return fd;	
	fd = open("/dev/null", O_RDWR);
	return fd;
}
	
// This forks off two sub-processes. The first, cmd_pid, is the filter
// process who's output we care about. The second one, feed_pid, is
// responsible for reading input from our parent GFILE and sending it
// into the mouth of the filter process.
void PipeGF::start() {
	int inp[2];
	int outp[2];
	char buf[1024];
	int l;


	// Fix problems with buffers being emptied multiple times.	
	fflush(NULL); 
	
	if (pipe(inp) == -1) return;
	if (pipe(outp) == -1) {
		close(inp[0]);
		close(inp[1]);
		return;
	}
	

	cmd_pid = fork();
	if (cmd_pid == -1) {
		close(inp[0]);
		close(inp[1]);
		close(outp[0]);
		close(outp[1]);
		return;
	}

	if (!cmd_pid) {
		if (on_gfpipe) on_gfpipe();

		close(inp[1]);
		close(outp[0]);

		dup2(inp[0], 0);
		dup2(outp[1], 1);
		dup2(devnull(), 2);
		
		execlp("/bin/sh", "/bin/sh", "-c", cmd, NULL);
		exit(-1);
	}

	close(inp[0]);
	close(outp[1]);

	feed_pid = fork();

	if (feed_pid == -1) {
		close(inp[1]);
		close(outp[0]);
		stop(); /* Kills off cmd_pid. */
		return;
	}
	
	if (!feed_pid) {
		GFILE *fparent;

		if(on_gfpipe) on_gfpipe();
		
		close(outp[0]);
		dup2(inp[1], 1);
		/* Leave stderr and stdin alone. */

		fparent = parent->dup();
		fparent->seek(0);

		while(l = fparent->read(buf, 1, 1024)) {
			write(1, buf, l);
		}
		
		exit(0);
	}

	close(inp[1]);

	f = fdopen(outp[0], "r");
	valid = 1;
	offset = 0;

	sleep(0);
	       
}

GFILE *PipeGF::dup() {
	PipeGF *pgf;

	pgf = new PipeGF(cmd, parent->dup());
	pgf->seek(offset);

	return pgf;
}

PipeGF::PipeGF(char *icmd, GFILE *gf) {
	valid = 0;
	cmd = strdup(icmd);
	parent = gf;
	f = NULL;
	feed_pid = 0;
	cmd_pid = 0;
	offset = 0;
}

PipeGF::~PipeGF() {
	stop();
	free(cmd);
}

GFILE *open_gfpipe(char *cmd, GFILE *parent) {
	if (!parent) return NULL;

	return new PipeGF(cmd, parent);
}
