#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

char * this_cmd = NULL;

void parse_args(int argc, char * argv[]);
int run();
void printcmd();

int main(int argc, char * argv[]) {
	parse_args(argc, argv);

	int retcode = run();
	if( !WIFEXITED(retcode) ) {
		printf("Child Error : %d ", retcode); printcmd();
		return retcode;
	}

	printf("Child exited normally "); printcmd();
	return 0;
}

void usage( FILE * out, int code ) {
	fprintf(out, "%s [OPTION] [--] N CMD\n", this_cmd);
	fprintf(out, "Run command, killing it if it doesn't print for more than 5 continuous seconds\n\n");
	fprintf(out, "\t-h,--help\tprint this usage message\n");
	exit(code);
}

char ** cmd_to_run = NULL;
pid_t child_pid = 0;
int pipe_fds[2];

void arg_error(void) {
	fprintf(stderr,"\n");
	usage(stderr, 1);
}

void parse_args(int argc, char * argv[]) {
	this_cmd = argv[0];

	enum { Help, };
	static struct option long_opts[] = {
		{ "help", no_argument, 0, Help },
		{ 0, 0, 0, 0 }
	}; // long_opts
	int long_index;

	int c;
	while ( (c = getopt_long( argc, argv, "h", long_opts, &long_index)) != -1 ) {
		switch ( c ) {
			case Help:
			case 'h':
				usage(stdout, 0);
				break;
			default:
				arg_error();
				break;
		} // switch
	} // while

	if( argc < optind + 1 ) {
		fprintf(stderr, "Too few arguments\n");
		arg_error();
	}

	cmd_to_run = argv + optind;
}

static void exit_handler (__attribute__((unused)) int a, __attribute__((unused)) void * b) {
	close(pipe_fds[0]);
	if(child_pid != 0) kill(child_pid, SIGKILL);
}

#define checked(call, ...) ({int ret = call(__VA_ARGS__); if (ret == -1) { perror(#call); exit(1); } ret;})

void run_child();
void make_noblock(int fd);
void sink(int fd);
int waitfd(int fd);

int run() {
	on_exit(exit_handler, NULL);
	checked(pipe, pipe_fds);

	printf("Watching command: "); printcmd();

	child_pid = checked(fork);
	if (child_pid == 0) { run_child(); }

	close(pipe_fds[1]);
	make_noblock(pipe_fds[0]);

	int status;
	while(waitpid(child_pid, &status, WNOHANG) == 0) {
		if(waitfd(pipe_fds[0]) == 0) {
			printf("Child Deadlocked "); printcmd();
			exit(127);
		}
		sink(pipe_fds[0]);
	}

	child_pid = 0;
	return status;

	return 0;
}

void make_noblock(int fd) {
	int flags = fcntl(fd, F_GETFL);
	flags |= O_NONBLOCK;
	fcntl(fd, F_SETFL, flags );
}

void sink(int fd) {
	char buff[100];
	int len = 100;
	int rv;
	do {
		rv = read( fd, buff, len );
	}
	while(rv > 0);
}

int waitfd(int fd) {
	fd_set set;
	FD_ZERO(&set);
	FD_SET(fd, &set);

	struct timeval timeout;
	timeout.tv_sec = 5;
	timeout.tv_usec = 0;

	int rv = select(fd + 1, &set, NULL, NULL, &timeout);
	if(rv == -1) {
		perror("select\n");
		exit(1);
	}
	return rv;
}

void run_child() {
	/* This is the child process. */
	while ((dup2(pipe_fds[1], STDOUT_FILENO) == -1) && (errno == EINTR));

	close(pipe_fds[1]);
	close(pipe_fds[0]);

	execvp ( *cmd_to_run, cmd_to_run);

	/* The execvp  function returns only if an error occurs.  */
	fprintf(stderr, "an error occurred in execvp\n");
	abort ();
}

void printcmd() {
	if(!cmd_to_run) return;
	char ** cmd = cmd_to_run;
	while (*cmd) {
		printf("%s ", *cmd);
		cmd++;
	}
	printf("\n");
}