/*
 * Copyright (C) 2007 Nokia Corporation.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * 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., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 * Author: Adrian Hunter
 */

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

struct child_info {
	struct child_info *next;
	pid_t pid;
	int terminated;
	int killed;
	int gone;
};

struct child_info *children = 0;

void kill_children(void)
{
	struct child_info *child;

	child = children;
	while (child) {
		if (!child->gone) {
			if (!child->terminated) {
				child->terminated = 1;
				kill(child->pid, SIGTERM);
			} /*else if (!child->killed) {
				child->killed = 1;
				kill(child->pid, SIGKILL);
			}*/
		}
		child = child->next;
	}
}

void add_child(pid_t child_pid)
{
	struct child_info *child;
	size_t sz;

	sz = sizeof(struct child_info);
	child = (struct child_info *) malloc(sz);
	memset(child, 0, sz);
	child->pid = child_pid;
	child->next = children;
	children = child;
}

void mark_child_gone(pid_t child_pid)
{
	struct child_info *child;

	child = children;
	while (child) {
		if (child->pid == child_pid) {
			child->gone = 1;
			break;
		}
		child = child->next;
	}
}

int have_children(void)
{
	struct child_info *child;

	child = children;
	while (child) {
		if (!child->gone)
			return 1;
		child = child->next;
	}
	return 0;
}

int parse_command_line(char *cmdline, int *pargc, char ***pargv)
{
	char **tmp;
	char *p, *v, *q;
	size_t sz;
	int argc = 0;
	int state = 0;
	char *argv[1024];

	if (!cmdline)
		return 1;
	q = v = (char *) malloc(strlen(cmdline) + 1024);
	if (!v)
		return 1;
	p = cmdline;
	for (;;) {
		char c = *p++;
		if (!c) {
			*v++ = 0;
			break;
		}
		switch (state) {
			case 0: /* Between args */
				if (isspace(c))
					break;
				argv[argc++] = v;
				if (c == '"') {
					state = 2;
					break;
				} else if (c == '\'') {
					state = 3;
					break;
				}
				state = 1;
			case 1: /* Not quoted */
				if (c == '\\') {
					if (*p)
						*v++ = *p;
				} else if (isspace(c)) {
					*v++ = 0;
					state = 0;
				} else
					*v++ = c;
				break;
			case 2: /* Double quoted */
				if (c == '\\' && *p == '"') {
					*v++ = '"';
					++p;
				} else if (c == '"') {
					*v++ = 0;
					state = 0;
				} else
					*v++ = c;
				break;
			case 3: /* Single quoted */
				if (c == '\'') {
					*v++ = 0;
					state = 0;
				} else
					*v++ = c;
				break;
		}
	}
	argv[argc] = 0;
	sz = sizeof(char *) * (argc + 1);
	tmp = (char **) malloc(sz);
	if (!tmp) {
		free(q);
		return 1;
	}
	if (argc == 0)
		free(q);
	memcpy(tmp, argv, sz);
	*pargc = argc;
	*pargv = tmp;
	return 0;
}

void signal_handler(int signum)
{
	kill_children();
}

int result = 0;
int alarm_gone_off = 0;

void alarm_handler(int signum)
{
	if (!result)
		alarm_gone_off = 1;
	kill_children();
}

int main(int argc, char *argv[], char **env)
{
	int p;
	pid_t child_pid;
	int status;
	int duration = 0;

	p = 1;
	if (argc > 1) {
		if (strncmp(argv[p], "--help", 6) == 0 ||
				strncmp(argv[p], "-h", 2) == 0) {
			printf(	"Usage is: "
				"fstest_monitor options programs...\n"
				"    Options are:\n"
				"        -h, --help           "
				"This help message\n"
				"        -d, --duration arg   "
				"Stop after arg seconds\n"
				"\n"
				"Run programs and wait for them."
				" If duration is specified,\n"
				"kill all programs"
				" after that number of seconds have elapsed.\n"
				"Example: "
				"fstest_monitor \"/bin/ls -l\" /bin/date\n"
				);
			return 1;
		}
		if (strncmp(argv[p], "--duration", 10) == 0 ||
				strncmp(argv[p], "-d", 2) == 0) {
			char *s;
			if (p+1 < argc && !isdigit(argv[p][strlen(argv[p])-1]))
				++p;
			s = argv[p];
			while (*s && !isdigit(*s))
				++s;
			duration = atoi(s);
			++p;
		}
	}

	signal(SIGTERM, signal_handler);
	signal(SIGINT, signal_handler);
	for (; p < argc; ++p) {
		child_pid = fork();
		if (child_pid) {
			/* Parent */
			if (child_pid == (pid_t) -1) {
				kill_children();
				result = 1;
				break;
			}
			add_child(child_pid);
		} else {
			/* Child */
			int cargc;
			char **cargv;

			if (parse_command_line(argv[p], &cargc, &cargv))
				return 1;
			execve(cargv[0], cargv, env);
			return 1;
		}
	}
	if (!result && duration > 0) {
		signal(SIGALRM, alarm_handler);
		alarm(duration);
	}
	while (have_children()) {
		status = 0;
		child_pid = wait(&status);
		if (child_pid == (pid_t) -1) {
			if (errno == EINTR)
				continue;
			kill_children();
			return 1;
		}
		mark_child_gone(child_pid);
		if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
			result = 1;
			kill_children();
		}
	}

	if (alarm_gone_off)
		return 0;

	return result;
}