#include <sys/types.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdarg.h>
#include <sys/wait.h>
#include <sys/ptrace.h>

/*
 * This belongs to some LIBC header files for 2.6
 */
#ifndef PTRACE_SETOPTIONS

/* 0x4200-0x4300 are reserved for architecture-independent additions.  */
#define PTRACE_SETOPTIONS	0x4200
#define PTRACE_GETEVENTMSG	0x4201
#define PTRACE_GETSIGINFO	0x4202
#define PTRACE_SETSIGINFO	0x4203

/* options set using PTRACE_SETOPTIONS */
#define PTRACE_O_TRACESYSGOOD	0x00000001
#define PTRACE_O_TRACEFORK	0x00000002
#define PTRACE_O_TRACEVFORK	0x00000004
#define PTRACE_O_TRACECLONE	0x00000008
#define PTRACE_O_TRACEEXEC	0x00000010
#define PTRACE_O_TRACEVFORKDONE	0x00000020
#define PTRACE_O_TRACEEXIT	0x00000040

/* Wait extended result codes for the above trace pt_options.  */
#define PTRACE_EVENT_FORK	1
#define PTRACE_EVENT_VFORK	2
#define PTRACE_EVENT_CLONE	3
#define PTRACE_EVENT_EXEC	4
#define PTRACE_EVENT_VFORK_DONE	5
#define PTRACE_EVENT_EXIT	6
#endif /* PTRACE_OPTIONS */


static void fatal_error(char *fmt,...) __attribute__((noreturn));

static void
fatal_error(char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	va_end(ap);

	exit(1);
}

int
child(char **arg)
{

	/*
	 * will cause the program to stop before executing the first
	 * user level instruction. We can only attach (load) a context
	 * if the task is in the STOPPED state.
	 */
	ptrace(PTRACE_TRACEME, 0, NULL, NULL);

	/*
	 * execute the requested command
	 */
	execvp(arg[0], arg);

	fatal_error("cannot exec: %s\n", arg[0]);
	/* not reached */
}

int
parent(char **arg)
{
	unsigned long ptrace_flags = 0, sig;
	int event, status, ret, wait_type;
	pid_t pid, new_pid;

	ptrace_flags |= PTRACE_O_TRACEFORK;
	/*
	 * Create the child task
	 */
	pid = fork();
	switch(pid) {
		case -1:
			fatal_error("Cannot fork process\n");
		case 0:
			exit(child(arg));
	}

	/*
	 * wait for the child to exec
	 */
	waitpid(pid, &status, WUNTRACED);

	/*
	 * check if process exited early
	 */
	if (WIFEXITED(status))
		fatal_error("command %s exited too early with status %d\n", arg[0], WEXITSTATUS(status));

	ptrace_flags |= PTRACE_O_TRACEEXEC;
	ptrace_flags |= PTRACE_O_TRACEFORK;

	ret = ptrace(PTRACE_SETOPTIONS, pid, NULL, (void *)ptrace_flags);
	if (ret)
		fatal_error("ptrace setopions=%d\n", errno);

	ret = ptrace(PTRACE_CONT, pid, NULL, NULL);
	if (ret)
		fatal_error("ptrace cont=%d\n", errno);

	wait_type = WUNTRACED|WNOHANG|__WALL;

	for (;;) {
		pid = wait4(-1, &status, wait_type, NULL);
		if (pid == 0)
			continue;
		if (pid < 1)
			break;
		printf("pid=%d errno=%d exited=%d stopped=%d signaled=%d stopsig=%-2d\n",
			pid, errno, 
			WIFEXITED(status), 
			WIFSTOPPED(status), 
			WIFSIGNALED(status), 
			WSTOPSIG(status));

		if (WIFEXITED(status) || WIFSIGNALED(status)) {
			printf("EXITED [%d]\n", pid);
			continue;
		}

		sig = WSTOPSIG(status);

		if (sig == SIGTRAP) {
			sig = 0;
			event = status >> 16;
			switch(event) {
				case PTRACE_EVENT_FORK:
					ret = ptrace(PTRACE_GETEVENTMSG, pid, NULL, (void *)&new_pid);
					if (ret)
						fatal_error("ptrace getmsg=%d\n", errno);
					printf("FORK new_pid [%ld]\n", new_pid);
					ret = ptrace(PTRACE_SETOPTIONS, pid, NULL, (void *)ptrace_flags);
					if (ret)
						fatal_error("ptrace options newpid=%d\n", errno);
					break;
				default:
					printf("unexpected event %d\n", event);
			}
		} else if (sig == SIGSTOP) {
			printf("SIGSTOP from [%d]\n", pid);
			sig = 0;
		}

		ret = ptrace(PTRACE_CONT, pid, NULL, (void *)sig);
		if (ret)
			fatal_error("ptrace cont=%d\n", errno);
	}
	/*
	 * simply wait for completion
	 */
	waitpid(pid, &status, 0);

	return 0;
}

int
main(int argc, char **argv)
{
	if (argc < 2) {
		fatal_error("You must specify a command to execute\n");
	}
	return parent(argv+1);
}
