/* Copyright 2001  Mark Pulford <mark@kyne.com.au>
 * This file is subject to the terms and conditions of the GNU General Public
 * License. Read the file COPYING found in this archive for details, or
 * visit http://www.gnu.org/copyleft/gpl.html
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif

/* Split conditions */
int split_bytes = 0;
int split_lines = 0;

int padding_size = 1;

char **command_argv;
int command_count = 0;
int fatal_code = 255;

int pipe_exec(char **argv, int write);
int split_output(int *fd, char *buf, int size);
int safe_write(int *fd, const char *buf, int size);
void write_zero(int fd, int size);
void reap_child(int *fd);
char next_arg(int argc, char **argv);
void check_args(int argc, char **argv);
int size2num(int *size, const char *s);
int str2num(int *size, const char *s);
void version();
void usage();

int main(int argc, char **argv)
{
	char *buf;
	int bsize = 4096;
	int rsize;
	int fd = -1;
	int done = 0;

	check_args(argc, argv);

	buf = malloc(bsize);
	if(!buf) {
		fprintf(stderr, "xin: Out of memory\n");
		exit(-1);
	}
	signal(SIGPIPE, SIG_IGN);
	
	/* Only exec the command if there is input that needs to be handled */
	while( (rsize = read(0, buf, bsize)) ) {
		if(rsize < 0) {
			if(errno == EINTR)
				continue;
			perror("xin: read");
			exit(-1);
		}
		done = split_output(&fd, buf, rsize);
	}
	if(fd != -1) {
		if(done % padding_size)
			write_zero(fd, padding_size - done % padding_size);
		reap_child(&fd);
	}

	return 0;
}

void write_zero(int fd, int size)
{
	char buf[4096];
	int s;

	memset(buf, 0, sizeof(buf));
	while(size > 0) {
		s = sizeof(buf)<size ? sizeof(buf) : size;
		s = write(fd, buf, s);
		if(s < 0) {
			if(errno == EINTR)
				continue;
			perror("xin: write");
			exit(-1);
		}
		size -= s;
	}
}

/* Returns the bytes outputed so far in the current pipe */
int split_output(int *fd, char *buf, int size)
{
	static int bytes_done = 0;
	static int lines_done = 0;
	int bytes_left;
	int lines_left;
	int lines;
	int ss;
	int reap;

	while(size) {
		bytes_left = split_bytes - bytes_done;
		lines_left = split_lines - lines_done;

		lines = 0;
		reap = 0;
		ss = size;
	
		/* If we are splitting by bytes set the split size
		 * to the number of remaining bytes in this section */
		if(split_bytes && bytes_left <= ss) {
			ss = bytes_left;
			reap = 1;
		}

		/* If we are splitting by lines find the last newline
		 * and set the split size (ss) base on it.
		 * If no newline is found, just output the whole buffer */
		if(split_lines) {
			int last_newline = -1;
			int i;

			for(i=0; i<ss; i++) {
				if(buf[i] == '\n') {
					last_newline = i;
					if(++lines == lines_left) {
						reap = 1;
						break;
					}
				}
			}
			if(last_newline != -1)
				ss = last_newline + 1;
		}

		safe_write(fd, buf, ss);
		bytes_done += ss;
		lines_done += lines;
		buf += ss;
		size -= ss;

		if(reap) {
			if(bytes_done % padding_size)
				write_zero(*fd, padding_size - bytes_done %
						padding_size);
			reap_child(fd);
			bytes_done = 0;
			lines_done = 0;
		}
	}

	return bytes_done;
}

/* safe_write will output the entire buffer or die trying */
int safe_write(int *fd, const char *buf, int size)
{
	int i = 0;
	int ret;

	while(i < size) {
		if(-1 == *fd)
			*fd = pipe_exec(command_argv, 1);
		ret = write(*fd, buf+i, size-i);
		if(ret < 0) {
			if(EINTR == errno)
				continue;
			perror("xin: write");
			exit(-1);
		}
		i += ret;
	}

	return i;
}

/* close the child's fd & wait for it to exit */
void reap_child(int *fd)
{
	int status;
	int ret;

	if(*fd != -1) {
		if(close(*fd) < 0) {
			perror("xin: close");
			exit(-1);
		}
		*fd = -1;
	}

	ret = waitpid(-1, &status, 0);
	if(ret < 0) {
		perror("xin: waitpid");
		exit(-1);
	}

	if(!WIFEXITED(status) || WEXITSTATUS(status) == fatal_code) {
		fprintf(stderr, "xin: fatal error from child\n");
		exit(-1);
	}
	*fd = -1;
}

/* Return the fd to the child or die trying */
int pipe_exec(char **argv, int write)
{
	int fds[2];
	int pid;

	if(pipe(fds) < 0) {
		perror("xin: pipe");
		exit(-1);
	}

	write = write?1:0;	/* Ensure write is 1 or 0 */

	command_count++;

	pid = fork();
	if(pid < 0) {
		perror("xin: fork");
		exit(-1);
	}

	if(pid) {
		/* parent */
		if(close(fds[!write]) < 0) {
			perror("xin: close");
			exit(-1);
		}
		return fds[write];
	}
	
	/* child */
	close(fds[write]);

	if(dup2(fds[!write], !write) < 0) {
		perror("xin child: dup2");
		_exit(fatal_code);
	}

	execvp(command_argv[0], argv);
	perror("xin child: exec");

	_exit(fatal_code);	/* Let the parent know we cannot continue */
}

char next_arg(int argc, char **argv)
{
	char *optstr = "b:l:f:p:hVe";
#ifdef HAVE_GETOPT_H
	struct option optslong[] = {
		{"bytes", 1, NULL, 'b'},
		{"lines", 1, NULL, 'l'},
		{"fatal-code", 1, NULL, 'f'},
		{"help", 0, NULL, 'h'},
		{"version", 0, NULL, 'V'},
		{"exec", 0, NULL, 'e'},
		{"padding", 0, NULL, 'p'},
		{NULL, 0, NULL, 0}
	};

	return getopt_long(argc, argv, optstr, optslong, NULL);
#else
	return getopt(argc, argv, optstr);
#endif
}

/* Removed {"lines-bytes", 1, NULL, 'C'} option */
void check_args(int argc, char **argv)
{
	char ch;

	if(1 == argc) {
		version();
		exit(0);
	}
	
	ch = next_arg(argc, argv);
	while(ch != -1) {
		switch(ch) {
		case 'b':
			if(!size2num(&split_bytes, optarg)) {
				fprintf(stderr, "xin: bad format for -b or --bytes: %s\n", optarg);
				exit(-1);
			}
			break;
		case 'l':
			if(!size2num(&split_lines, optarg)) {
				fprintf(stderr, "xin: bad format for -l or --lines: %s\n", optarg);
				exit(-1);
			}
			break;
		case 'p':
			if(!size2num(&padding_size, optarg)) {
				fprintf(stderr, "xin: bad format for -p or --pad: %s\n", optarg);
				exit(-1);
			}
			break;
		case 'f':
			if(!str2num(&fatal_code, optarg)) {
				fprintf(stderr, "xin: bad argument for -f or --fatal-code: %s\n", optarg);
				exit(-1);
			}
			break;
		case 'h':
			usage();
			exit(0);
		case 'V':
			version();
			exit(0);
		case 'e':
			break;
		case ':': case '?':
			/* missing parameter or unknown option */
			exit(-1);
		default:
			abort();	/* BUG: Unimplented option */
		}
		if(ch == 'e')
			break;
		ch = next_arg(argc, argv);
	}

	if(argc <= optind) {
		fprintf(stderr, "xin: no command given\n");
		exit(-1);
	}

	/* Copy argv into NULL terminated command_argv */
	command_argv = malloc((argc - optind + 1) * sizeof(argv));
	if(!command_argv) {
		fprintf(stderr, "xin: Out of memeory\n");
		exit(-1);
	}
	memcpy(command_argv, &argv[optind], (argc - optind) * sizeof(argv));
	command_argv[argc - optind] = 0;
}

/* handles b, k, m suffixes
 * returns: 0	failure
 * 	    1	success
 * Note: this doesn't handle overflow */
int size2num(int *size, const char *s)
{
	char *suff;

	*size = strtol(s, &suff, 10);
	if(suff == s)
		return 0;	/* No number found */
	if(*size < 1)
		return 0;	/* Must be positive non zero */

	if(0 == *suff)
		return 1;	/* No suffix */

	if(0 != suff[1])
		return 0;	/* suffix too long */

	switch(*suff) {
	case 'b': case 'B':
		*size *= 512;
		return 1;
	case 'k': case 'K':
		*size *= 1024;
		return 1;
	case 'm': case 'M':
		*size *= 1048576;
		return 1;
	}

	return 0;	/* Bad modifier */
}

int str2num(int *size, const char *s)
{
	char *end;
	
	*size = strtol(s, &end, 10);
	if(*s != 0 && *end == 0)
		return 1;
	else
		return 0;
}

void usage()
{
	fprintf(stderr, "Usage: xin [OPTION...] -e COMMAND [ARGS...]\n");
	fprintf(stderr, "  -b  --bytes=SIZE       Split after SIZE bytes\n");
	fprintf(stderr, "  -l  --lines=LINES      Split after LINES lines\n");
	fprintf(stderr, "  -p  --padding=SIZE     Zero extend each section to a multiple of SIZE bytes\n");
	fprintf(stderr, "  -f  --fatal-code=CODE  Fatal error CODE returned by child COMMAND\n");
	fprintf(stderr, "  -e  --exec             Remaining arguments belong to COMMAND\n");
	fprintf(stderr, "  -V  --version          Display version\n");
	fprintf(stderr, "  -h  --help             Display help\n");
}

void version()
{
	fprintf(stderr, "xin v%s\n", VERSION);
}


syntax highlighted by Code2HTML, v. 0.9.1