#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <dirent.h>
#include <sched.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <limits.h>
#include <mntent.h>
#include <sys/epoll.h>
#include <sys/signalfd.h>
#include <inttypes.h>
#include <ctype.h>

#include "../config.h"
#include "hyper.h"
#include "net.h"
#include "util.h"
#include "exec.h"
#include "event.h"
#include "parse.h"
#include "container.h"

struct hyper_pod global_pod = {
	.exec_head	=	LIST_HEAD_INIT(global_pod.exec_head),
};
struct hyper_exec *global_exec;

#define MAXEVENTS	10

struct hyper_ctl ctl;

static int hyper_handle_exit(struct hyper_pod *pod);

static int hyper_set_win_size(char *json, int length)
{
	struct hyper_win_size ws = {
		.tty = NULL,
	};
	struct winsize size;
	struct hyper_exec *exec;
	char *name, path[128];
	int fd, ret;

	fprintf(stdout, "call hyper_win_size, json %s, len %d\n", json, length);
	if (hyper_parse_winsize(&ws, json, length) < 0) {
		fprintf(stderr, "set term size failed\n");
		return -1;
	}

	name = ws.tty;
	if (!ws.tty) {
		exec = hyper_find_exec_by_seq(&global_pod, ws.seq);
		if (exec == NULL) {
			fprintf(stdout, "can not find exec whose seq is %" PRIu64"\n", ws.seq);
			return 0;
		}

		fprintf(stdout, "find exec %s, pts %s, pid is %d, seq is %" PRIu64"\n",
			exec->id ? exec->id : "pod", exec->pty, exec->pid, ws.seq);
		name = exec->pty;
	} else {
		if (sprintf(path, "/dev/%s", ws.tty) < 0) {
			fprintf(stderr, "get tty device failed\n");
			return -1;
		}
		name = path;
	}

	fprintf(stdout, "try to open %s\n", name);
	ret = hyper_open_serial_dev(name);
	if (ret < 0) {
		fprintf(stderr, "cannot open %s to set term size\n", name);
		goto out;
	}

	size.ws_row = ws.row;
	size.ws_col = ws.column;
	fd = ret;

	ret = ioctl(fd, TIOCSWINSZ, &size);
	if (ret < 0)
		fprintf(stderr, "cannot ioctl to set %s term size\n", name);

	close(fd);
out:
	free(ws.tty);
	return ret;
}

static void hyper_kill_process(int pid)
{
	char path[64];
	char *line = NULL, *ignore = "SigIgn:";
	size_t len = 0;
	ssize_t read;
	FILE *file;
	char *sub;

	sprintf(path, "/proc/%u/status", pid);

	fprintf(stdout, "fopen %s\n", path);
	file = fopen(path, "r");
	if (file == NULL) {
		perror("can not open process proc status file");
		return;
	}

	while ((read = getline(&line, &len, file)) != -1) {
		long mask;

		if (strstr(line, ignore) == NULL)
			continue;

		sub = line + strlen(ignore);
		fprintf(stdout, "find sigign %s", sub);

		mask = atol(sub);
		fprintf(stdout, "mask is %ld\n", mask);

		if ((mask >> (SIGTERM - 1)) & 0x1) {
			fprintf(stdout, "signal term is ignored, kill it\n");
			kill(pid, SIGKILL);
		}

		break;
	}

	fclose(file);
	free(line);
}

static void hyper_term_all(struct hyper_pod *pod)
{
	int npids = 0;
	int index = 0;
	int pid;
	DIR *dp;
	struct dirent *de;
	pid_t *pids = NULL;

	dp = opendir("/proc");
	if (dp == NULL)
		return;

	while ((de = readdir(dp)) && de != NULL) {
		if (!isdigit(de->d_name[0]))
			continue;
		pid = atoi(de->d_name);
		if (pid == 1)
			continue;
		if (index <= npids) {
			pids = realloc(pids, npids + 16384);
			if (pids == NULL)
				return;
			npids += 16384;
		}

		pids[index++] = pid;
	}

	fprintf(stdout, "Sending SIGTERM\n");

	for (--index; index >= 0; --index) {
		fprintf(stdout, "kill process %d\n", pids[index]);
		kill(pids[index], SIGTERM);
	}

	free(pids);
	closedir(dp);

	for (index = 0; index < pod->c_num; index++) {
		hyper_kill_process(pod->c[index].exec.pid);
	}
}

static int pod_ctl_pipe_handle(struct hyper_event *de, uint32_t len)
{
	struct hyper_buf *buf = &de->rbuf;

	fprintf(stdout, "%s\n", __func__);

	if (hyper_get_be32(buf->data) == STOPPOD) {
		struct hyper_pod *pod = de->ptr;

		fprintf(stdout, "pod init get type STOPPOD, exit\n");
		hyper_term_all(pod);
		hyper_reset_event(&pod->ctl);
		hyper_unmount_all();
		_exit(0);
	}

	return 0;
}

static int hyper_handle_exit(struct hyper_pod *pod)
{
	int pid, status;
	/* pid + exit code */
	uint8_t data[5];

	while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
		data[4] = 0;

		if (WIFEXITED(status)) {
			data[4] = WEXITSTATUS(status);
			fprintf(stdout, "pid %d exit normally, status %" PRIu8 "\n",
				pid, data[4]);

		} else if (WIFSIGNALED(status)) {
			fprintf(stdout, "pid %d exit by signal, status %d\n",
				pid, WTERMSIG(status));
		}

		if (hyper_send_exec_eof(ctl.tty.fd, pod, pid, data[4]) < 0)
			fprintf(stderr, "signal_loop send eof failed\n");
	}

	return 0;
}

static int hyper_signal_loop(struct hyper_event *de)
{
	int size;
	struct signalfd_siginfo sinfo;
	struct hyper_pod *pod = de->ptr;

	fprintf(stdout, "%s write to ctl tty fd\n", __func__);

	while (1) {
		size = read(de->fd, &sinfo, sizeof(struct signalfd_siginfo));
		if (size <= 0) {
			if (errno == EINTR)
				continue;
			if (errno == EAGAIN)
				break;

			perror("fail to read signal fd");
			return -1;
		} else if (size != sizeof(struct signalfd_siginfo)) {
			perror("read signalfd siginfo failed");
			return -1;
		}

		if (sinfo.ssi_signo != SIGCHLD) {
			fprintf(stderr, "why give me signal %d?\n", sinfo.ssi_signo);
			return 0;
		}

		hyper_handle_exit(pod);
	}

	return 0;
}

static struct hyper_event_ops pod_ctl_pipe_ops = {
	.read		= hyper_event_read,
	.handle		= pod_ctl_pipe_handle,
	.hup		= hyper_event_hup,
	.rbuf_size	= 8,
	.len_offset	= 4,
};

static int pod_init_loop(struct hyper_pod *pod)
{
	int i, n;
	struct epoll_event *events;

	pod->efd = epoll_create1(EPOLL_CLOEXEC);
	if (pod->efd < 0) {
		perror("epoll_create failed");
		return -1;
	}

	fprintf(stdout, "hyper_init_event pod ctl pipe event %p, ops %p, fd %d\n",
		&pod->ctl, &pod_ctl_pipe_ops, pod->ctl.fd);
	if (hyper_init_event(&pod->ctl, &pod_ctl_pipe_ops, pod) < 0 ||
	    hyper_add_event(pod->efd, &pod->ctl, EPOLLIN) < 0) {
		fprintf(stderr, "hyper add pod ctl pipe event failed\n");
		return -1;
	}

	events = calloc(MAXEVENTS, sizeof(*events));

	while (1) {
		n = epoll_wait(pod->efd, events, MAXEVENTS, -1);
		fprintf(stdout, "%s epoll_wait %d\n", __func__, n);

		if (n < 0) {
			if (errno == EINTR)
				continue;
			perror("pod wait event failed");
			return -1;
		}

		for (i = 0; i < n; i++) {
			if (hyper_handle_event(pod->efd, &events[i]) < 0)
				return -1;
		}
	}

	close(pod->efd);
	return 0;
}

struct hyper_pod_arg {
	struct hyper_pod	*pod;
	int		ctl_pipe[2];
};

static int hyper_pod_init(void *data)
{
	struct hyper_pod_arg *arg = data;
	struct hyper_pod *pod = arg->pod;

	close(arg->ctl_pipe[0]);
	close(ctl.sig.fd);
	close(ctl.efd);
	close(ctl.chan.fd);
	close(ctl.tty.fd);

	pod->ctl.fd = arg->ctl_pipe[1];
	if (hyper_setfd_cloexec(pod->ctl.fd) < 0) {
		perror("set pod init ctl pipe fd FD_CLOEXEC failed");
		goto fail;
	}

	/* mount new proc directory */
	if (umount("/proc") < 0) {
		perror("umount proc filesystem failed\n");
		goto fail;
	}

	if (mount("proc", "/proc", "proc", 0, NULL) < 0) {
		perror("mount proc filesystem failed\n");
		goto fail;
	}

	if (sethostname(pod->hostname, strlen(pod->hostname)) < 0) {
		perror("set host name failed");
		goto fail;
	}

	fprintf(stdout, "pod ctl_pipe %d\n", arg->ctl_pipe[1]);
	if (hyper_send_type(arg->ctl_pipe[1], READY) < 0) {
		fprintf(stderr, "container init send ready message failed\n");
		goto fail;
	}

	pod_init_loop(pod);
	fprintf(stdout, "pod init exit\n");
out:
	_exit(-1);

fail:
	hyper_send_type(arg->ctl_pipe[1], ERROR);
	goto out;
}

static int hyper_ctl_pipe_handle(struct hyper_event *de, uint32_t len)
{
	struct hyper_buf *buf = &de->rbuf;
	uint32_t type;

	/* container exec finish message */
	fprintf(stdout, "%s\n", __func__);

	type = hyper_get_be32(buf->data);

	switch (type) {
	case ACK:
		/* ACK only being sent on stoppod, in this case, ctl pipe
		 * fd is block, and ACK is the last message, exit loop. */
		fprintf(stdout, "hyper_ctl_pipe_loop get ack\n");
		return 1;
	default:
		fprintf(stdout, "get unknown type %" PRIu32"\n", type);
		break;
	}

	return 0;
}

static int hyper_do_start_containers(void *data)
{
	int i, pidns, ipcns, utsns, ret;
	struct hyper_container *c;
	struct hyper_pod_arg *arg;
	struct hyper_pod *pod;
	char path[64];

	arg = data;
	pod = arg->pod;

	ret = pidns = ipcns = utsns = -1;

	sprintf(path, "/proc/%d/ns/pid", pod->init_pid);
	pidns = open(path, O_RDONLY| O_CLOEXEC);
	if (pidns < 0) {
		perror("fail to open pidns of pod init");
		goto out;
	}

	/* enter pidns of pod init, so the children of this process will run in
	 * pidns of pod init, see man 2 setns */
	if (setns(pidns, CLONE_NEWPID) < 0) {
		perror("enter pidns of pod init failed");
		goto out;
	}

	sprintf(path, "/proc/%d/ns/uts", pod->init_pid);
	utsns = open(path, O_RDONLY| O_CLOEXEC);
	if (utsns < 0) {
		perror("fail to open utsns of pod init");
		goto out;
	}

	sprintf(path, "/proc/%d/ns/ipc", pod->init_pid);
	ipcns = open(path, O_RDONLY| O_CLOEXEC);
	if (ipcns < 0) {
		perror("fail to open ipcns of pod init");
		goto out;
	}

	for (i = 0; i < pod->c_num; i++) {
		c = &pod->c[i];
		list_add_tail(&c->exec.list, &pod->exec_head);
		hyper_start_container(c, utsns, ipcns);
	}

	if (hyper_send_type(arg->ctl_pipe[1], READY) < 0) {
		fprintf(stderr, "container init send ready message failed\n");
		goto out;
	}

	ret = 0;
out:
	close(pidns);
	close(utsns);
	close(ipcns);
	close(arg->ctl_pipe[0]);
	close(arg->ctl_pipe[1]);

	_exit(ret);
}

int hyper_start_containers(struct hyper_pod *pod)
{
	int stacksize = getpagesize() * 4;
	void *stack = malloc(stacksize);
	struct hyper_pod_arg arg = {
		.pod		= pod,
		.ctl_pipe	= {-1, -1},
	};
	int ret = -1, pid;
	uint32_t type;

	if (stack == NULL) {
		perror("fail to allocate stack for container init");
		goto out;
	}

	if (hyper_socketpair(PF_UNIX, SOCK_STREAM, 0, arg.ctl_pipe) < 0) {
		perror("create pipe between hyper init and pod init failed");
		goto out;
	}

	pid = clone(hyper_do_start_containers, stack + stacksize, CLONE_VM| CLONE_FILES, &arg);
	free(stack);
	if (pid < 0) {
		perror("enter container pid ns failed");
		goto out;
	}

	/* Wait for container start */
	if (hyper_get_type_block(arg.ctl_pipe[0], &type) < 0) {
		perror("get enter_container_pidns ready message failed");
		goto out;
	}

	if (type != READY) {
		fprintf(stderr, "get incorrect enter_container_pidns message type %d, expect READY\n",
			type);
		goto out;
	}

	ret = 0;
out:
	close(arg.ctl_pipe[0]);
	close(arg.ctl_pipe[1]);
	return ret;
}

static struct hyper_event_ops hyper_ctl_pipe_ops = {
	.read		= hyper_event_read,
	.handle		= hyper_ctl_pipe_handle,
	.hup		= hyper_event_hup,
	.rbuf_size	= 256,
	.len_offset	= 4,
};

static int hyper_setup_container(struct hyper_pod *pod)
{
	int stacksize = getpagesize() * 4;
	int flags = CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC |
		    CLONE_NEWUTS | CLONE_VM | SIGCHLD;

	struct hyper_pod_arg arg = {
		.pod		= NULL,
		.ctl_pipe	= {-1, -1},
	};

	uint32_t type;
	void *stack;

	if (hyper_socketpair(PF_UNIX, SOCK_STREAM, 0, arg.ctl_pipe) < 0) {
		perror("create pipe between hyper init and pod init failed");
		return -1;
	}

	stack = malloc(stacksize);
	if (stack == NULL) {
		perror("fail to allocate stack for container init");
		return -1;
	}

	arg.pod = pod;

	pod->init_pid = clone(hyper_pod_init, stack + stacksize, flags, &arg);
	free(stack);
	if (pod->init_pid < 0) {
		perror("create container init process failed");
		return -1;
	}
	fprintf(stdout, "pod init pid %d\n", pod->init_pid);

	close(arg.ctl_pipe[1]);
	ctl.ctl.fd = arg.ctl_pipe[0];

	/* Wait for container start */
	if (hyper_get_type_block(ctl.ctl.fd, &type) < 0) {
		perror("get container init ready message failed");
		return -1;
	}

	if (type != READY) {
		fprintf(stderr, "get incorrect message type %d, expect READY\n", type);
		return -1;
	}

	if (hyper_start_containers(pod) < 0) {
		fprintf(stderr, "start containers failed\n");
		return -1;
	}

	if (hyper_setfd_cloexec(ctl.ctl.fd) < 0) {
		perror("set ctl pipe fd FD_CLOEXEC failed");
		return -1;
	}

	fprintf(stdout, "hyper_init_event hyper ctl pipe fd %d\n", ctl.ctl.fd);
	if (hyper_init_event(&ctl.ctl, &hyper_ctl_pipe_ops, pod) < 0 ||
	    hyper_add_event(ctl.efd, &ctl.ctl, EPOLLIN) < 0) {
		return -1;
	}

	return 0;
}

#ifdef WITH_VBOX

#define MAX_HOST_NAME  256
#define MAX_NLS_NAME    32

#define VBSF_MOUNT_SIGNATURE_BYTE_0 '\377'
#define VBSF_MOUNT_SIGNATURE_BYTE_1 '\376'
#define VBSF_MOUNT_SIGNATURE_BYTE_2 '\375'

struct vbsf_mount_info_new
{
	char nullchar;			/* name cannot be '\0' -- we use this field
					 to distinguish between the old structure
					 and the new structure */
	char signature[3];		/* signature */
	int  length;			/* length of the whole structure */
	char name[MAX_HOST_NAME];	/* share name */
	char nls_name[MAX_NLS_NAME];	/* name of an I/O charset */
	int  uid;			/* user ID for all entries, default 0=root */
	int  gid;			/* group ID for all entries, default 0=root */
	int  ttl;			/* time to live */
	int  dmode;			/* mode for directories if != 0xffffffff */
	int  fmode;			/* mode for regular files if != 0xffffffff */
	int  dmask;			/* umask applied to directories */
	int  fmask;			/* umask applied to regular files */
};

static int hyper_setup_shared(struct hyper_pod *pod)
{
	struct vbsf_mount_info_new mntinf;

	if (pod->tag == NULL) {
		fprintf(stdout, "no shared directroy\n");
		return 0;
	}

	if (hyper_mkdir("/tmp/hyper/shared") < 0) {
		perror("fail to create /tmp/hyper/shared");
		return -1;
	}

	bzero(&mntinf, sizeof(mntinf));
	mntinf.nullchar = '\0';
	mntinf.signature[0]	= VBSF_MOUNT_SIGNATURE_BYTE_0;
	mntinf.signature[1]	= VBSF_MOUNT_SIGNATURE_BYTE_1;
	mntinf.signature[2]	= VBSF_MOUNT_SIGNATURE_BYTE_2;
	mntinf.length		= sizeof(mntinf);
	mntinf.dmode		= ~0U;
	mntinf.fmode		= ~0U;
	strcpy(mntinf.name, pod->tag);

	if (mount(NULL, "/tmp/hyper/shared", "vboxsf",
		  MS_NODEV, &mntinf) < 0) {
		perror("fail to mount shared dir");
		return -1;
	}

	return 0;
}
#else
static int hyper_setup_shared(struct hyper_pod *pod)
{
	if (pod->tag == NULL) {
		fprintf(stdout, "no shared directroy\n");
		return 0;
	}

	if (hyper_mkdir("/tmp/hyper/shared") < 0) {
		perror("fail to create /tmp/hyper/shared");
		return -1;
	}

	if (mount(pod->tag, "/tmp/hyper/shared", "9p",
		  MS_MGC_VAL| MS_NODEV, "trans=virtio,cache=loose") < 0) {

		perror("fail to mount shared dir");
		return -1;
	}

	return 0;
}
#endif

static int hyper_setup_pod(struct hyper_pod *pod)
{
	/* create tmp proc directroy */
	if (hyper_mkdir("/tmp/hyper/proc") < 0) {
		perror("create tmp proc failed");
		return -1;
	}

	if (hyper_setup_network(pod) < 0) {
		fprintf(stderr, "setup network failed\n");
		return -1;
	}

	if (hyper_setup_dns(pod) < 0) {
		fprintf(stderr, "setup network failed\n");
		return -1;
	}

	if (hyper_setup_shared(pod) < 0) {
		fprintf(stderr, "setup shared directory failed\n");
		return -1;
	}

	if (hyper_setup_container(pod) < 0) {
		fprintf(stderr, "start container failed\n");
		return -1;
	}

	return 0;
}

static void hyper_print_uptime(void)
{
	char buf[128];
	int fd = open("/proc/uptime", O_RDONLY);

	if (fd < 0)
		return;
	memset(buf, 0, sizeof(buf));
	if (read(fd, buf, sizeof(buf)))
		fprintf(stdout, "uptime %s\n", buf);

	close(fd);
}

static int hyper_start_pod(char *json, int length)
{
	struct hyper_pod *pod = &global_pod;

	fprintf(stdout, "call hyper_start_pod, json %s, len %d\n", json, length);

	if (pod->init_pid)
		fprintf(stdout, "pod init_pid exist %d\n", pod->init_pid);

	if (hyper_parse_pod(pod, json, length) < 0) {
		fprintf(stderr, "parse pod json failed\n");
		return -1;
	}

	if (hyper_setup_pod(pod) < 0) {
		hyper_shutdown(pod);
		return -1;
	}

	return 0;
}

static int hyper_cmd_write_file(char *json, int length)
{
	struct hyper_writter writter;
	struct hyper_container *c;
	struct hyper_pod *pod = &global_pod;
	int pipe[2] = {-1, -1};
	int pid, mntns = -1, fd;
	char path[512];
	int len = 0, size;

	fprintf(stdout, "%s\n", __func__);
	memset(&writter, 0, sizeof(writter));

	if (hyper_parse_write_file(&writter, json, length) < 0) {
		goto out;
	}

	c = hyper_find_container(pod, writter.id);
	if (c == NULL) {
		fprintf(stderr, "can not find container whose id is %s\n", writter.id);
		goto out;
	}

	if (hyper_socketpair(PF_UNIX, SOCK_STREAM, 0, pipe) < 0) {
		perror("create writter pipe failed");
		goto out;
	}

	sprintf(path, "/proc/%d/ns/mnt", c->exec.pid);
	mntns = open(path, O_RDONLY);
	if (mntns < 0) {
		perror("fail to open mnt ns");
		goto out;
	}

	pid = fork();
	if (pid < 0) {
		perror("fail to fork writter process");
		goto out;
	} else if (pid > 0) {
		uint32_t type;

		if (hyper_get_type_block(pipe[0], &type) < 0 || type != READY) {
			fprintf(stderr, "get incorrect message type %d, expect READY\n", type);
			goto out;
		}

		goto out;
	}

	if (setns(mntns, CLONE_NEWNS) < 0) {
		perror("fail to enter container ns");
		_exit(-1);
	}

	sprintf(path, "/tmp/hyper/%s/root/%s/", c->id, c->rootfs);
	fprintf(stdout, "write file %s, data len %d\n", writter.file, writter.len);

	/* TODO: wait for container finishing setup root */
	if (chroot(path) < 0) {
		perror("chroot for exec command failed");
		_exit(-1);
	}

	fd = open(writter.file, O_CREAT| O_WRONLY, 0644);
	if (fd < 0) {
		perror("fail to open target file");
		_exit(-1);
	}

	while(len < writter.len) {
		size = write(fd, writter.data + len, writter.len - len);

		if (size < 0) {
			if (errno == EINTR)
				continue;

			perror("fail to write data to file");
			_exit(-1);
		}

		len += size;
	}

	hyper_send_type(pipe[1], READY);
	_exit(0);
out:
	close(pipe[0]);
	close(pipe[1]);
	close(mntns);
	free(writter.id);
	free(writter.file);
	free(writter.data);

	return 0;
}

struct hyper_file_arg {
	int		mntns;
	int		pipe[2];
	char		*file;
	char		root[128];
	uint32_t	*datalen;
	uint8_t		**data;
};

static int hyper_do_cmd_read_file(void *data)
{
	struct stat st;
	int len = 0, size, fd;
	struct hyper_file_arg *arg = data;

	if (setns(arg->mntns, CLONE_NEWNS) < 0) {
		perror("fail to enter container ns");
		goto err;
	}

	fprintf(stdout, "read file %s\n", arg->file);

	/* TODO: wait for container finishing setup root */
	if (chroot(arg->root) < 0) {
		perror("chroot for exec command failed");
		goto err;
	}

	if (stat(arg->file, &st) < 0) {
		perror("fail to state file");
		goto err;
	}

	*arg->datalen = st.st_size;
	*arg->data = malloc(*arg->datalen);
	if (*arg->data == NULL) {
		fprintf(stderr, "allocate memory for reading file failed\n");
		goto err;
	}

	fd = open(arg->file, O_RDONLY);
	if (fd < 0) {
		perror("fail to open target file");
		goto err;
	}

	fprintf(stdout, "file length %d\n", *arg->datalen);
	while(len < *arg->datalen) {
		size = read(fd, *arg->data + len, *arg->datalen - len);

		if (size < 0) {
			if (errno == EINTR)
				continue;

			perror("fail to read data from file");
			goto err;
		}

		len += size;
	}

	fprintf(stdout, "read data %s\n", *arg->data);
	hyper_send_type(arg->pipe[1], READY);
	return 0;
err:
	hyper_send_type(arg->pipe[1], ERROR);
	return -1;
}

static int hyper_cmd_read_file(char *json, int length, uint32_t *datalen, uint8_t **data)
{
	struct hyper_reader reader;
	struct hyper_container *c;
	struct hyper_pod *pod = &global_pod;
	struct hyper_file_arg arg = {
		.pipe = {-1, -1},
	};
	int stacksize = getpagesize() * 4;
	void *stack = malloc(stacksize);
	int pid, ret = -1;
	char path[128];
	uint32_t type;

	if (stack == NULL) {
		perror("fail to allocate stack for container init");
		goto out;
	}

	fprintf(stdout, "%s\n", __func__);
	memset(&reader, 0, sizeof(reader));

	if (hyper_parse_read_file(&reader, json, length) < 0) {
		goto out;
	}

	c = hyper_find_container(pod, reader.id);
	if (c == NULL) {
		fprintf(stderr, "can not find container whose id is %s\n", reader.id);
		goto out;
	}

	if (hyper_socketpair(PF_UNIX, SOCK_STREAM, 0, arg.pipe) < 0) {
		perror("create reader pipe failed");
		goto out;
	}

	sprintf(path, "/proc/%d/ns/mnt", c->exec.pid);
	arg.mntns = open(path, O_RDONLY);
	if (arg.mntns < 0) {
		perror("fail to open mnt ns");
		goto out;
	}

	arg.file = reader.file;
	arg.datalen = datalen;
	arg.data = data;
	sprintf(arg.root, "/tmp/hyper/%s/root/%s/", c->id, c->rootfs);

	pid = clone(hyper_do_cmd_read_file, stack + stacksize, CLONE_VM, &arg);
	free(stack);
	if (pid < 0) {
		perror("fail to fork writter process");
		goto out;
	}

	if (hyper_get_type_block(arg.pipe[0], &type) < 0 || type != READY) {
		fprintf(stderr, "%s to incorrect type %" PRIu32 "\n", __func__, type);
		goto out;
	}

	ret = 0;
out:
	close(arg.pipe[0]);
	close(arg.pipe[1]);
	close(arg.mntns);
	free(reader.id);
	free(reader.file);

	return ret;
}

static void hyper_cleanup_shared(struct hyper_pod *pod)
{
	if (pod->tag == NULL) {
		fprintf(stdout, "no shared directroy\n");
		return;
	}

	free(pod->tag);
	if (umount("/tmp/hyper/shared") < 0 &&
	    umount2("/tmp/hyper/shared", MNT_DETACH)) {
		perror("fail to umount 9p dir");
		return;
	}

	if (rmdir("/tmp/hyper/shared") < 0)
		perror("fail to delete /tmp/hyper/shared");
}

static int hyper_send_stoppod(int fd)
{
	if (hyper_setfd_block(fd) < 0) {
		perror("set fd BLOCK failed");
		return -1;
	}

	if (hyper_send_type(fd, STOPPOD) < 0) {
		fprintf(stderr, "send STOPPOD message failed\n");
		return -1;
	}

	hyper_event_read(&ctl.ctl);

	return 0;
}

static int hyper_stop_pod(struct hyper_pod *pod)
{
	fprintf(stdout, "hyper_stop_pod init_pid %d\n", pod->init_pid);
	if (pod->init_pid == 0) {
		fprintf(stdout, "container init pid is already exit\n");
		return 0;
	}

	pod->init_pid = 0;
	/* Make hyper ctl_pipe blocked */
	hyper_send_stoppod(ctl.ctl.fd);

	hyper_cleanup_exec(pod);
	hyper_cleanup_container(pod);
	hyper_cleanup_network(pod);
	hyper_cleanup_dns(pod);
	hyper_cleanup_shared(pod);

	free(pod->hostname);

	sync();
	/* Wait for pod init ack */
	close(ctl.ctl.fd);
	hyper_reset_event(&ctl.ctl);
	return 0;
}

static int hyper_setup_ctl_channel(char *name)
{
	int ret = hyper_open_channel(name, 0);

	if (ret < 0)
		return ret;

	fprintf(stdout, "send ready message\n");
	if (hyper_send_type(ret, READY) < 0) {
		perror("send READY MESSAGE failed\n");
		goto out;
	}

	return ret;
out:
	close(ret);
	return -1;
}

static int hyper_setup_tty_channel(char *name)
{
	int ret = hyper_open_channel(name, O_NONBLOCK);
	if (ret < 0)
		return -1;

	return ret;
}

static int hyper_ttyfd_handle(struct hyper_event *de, uint32_t len)
{
	struct hyper_buf *rbuf = &de->rbuf;
	struct hyper_pod *pod = de->ptr;
	struct hyper_exec *exec;
	struct hyper_buf *wbuf;
	uint64_t seq = 0;
	int size;

	seq = hyper_get_be64(rbuf->data);

	dprintf(stdout, "\n%s seq %" PRIu64", len %" PRIu32"\n", __func__, seq, len - 12);

	exec = hyper_find_exec_by_seq(pod, seq);
	if (exec == NULL) {
		wbuf = &de->wbuf;
		fprintf(stderr, "can't find exec whose seq is %" PRIu64 "\n", seq);

		/* goodbye */
		if (wbuf->get + 12 > wbuf->size)
			return 0;

		hyper_set_be64(wbuf->data + wbuf->get, seq);
		hyper_set_be32(wbuf->data + wbuf->get + 8, 12);
		wbuf->get += 12;

		if (hyper_modify_event(ctl.efd, de, EPOLLIN | EPOLLOUT) < 0) {
			fprintf(stderr, "modify ctl tty event to in & out failed\n");
			return -1;
		}

		return 0;
	}

	dprintf(stdout, "find exec %s pid %d, seq is %" PRIu64 "\n",
		exec->id ? exec->id : "pod", exec->pid, exec->seq);
	wbuf = &exec->e.wbuf;

	size = wbuf->size - wbuf->get;
	if (size == 0)
		return 0;

	/* Data may lost since pts buffer is full. do not allow one exec pts occupy all
	 * of the tty buff. */
	if (size > (len - 12))
		size = (len - 12);

	if (size > 0) {
		memcpy(wbuf->data + wbuf->get, rbuf->data + 12, size);
		wbuf->get += size;
		if (hyper_modify_event(ctl.efd, &exec->e, EPOLLIN | EPOLLOUT) < 0) {
			fprintf(stderr, "modify exec pts event to in & out failed\n");
			return -1;
		}
	}

	return 0;
}

static int hyper_channel_handle(struct hyper_event *de, uint32_t len)
{
	struct hyper_buf *buf = &de->rbuf;
	struct hyper_pod *pod = de->ptr;
	uint32_t type = 0, datalen = 0;
	uint8_t *data = NULL;
	int i, ret = 0;

	for (i = 0; i < buf->get; i++)
		fprintf(stdout, "%0x ", buf->data[i]);

	type = hyper_get_be32(buf->data);

	fprintf(stdout, "\n %s, type %" PRIu32 ", len %" PRIu32 "\n",
		__func__, type, len);

	pod->type = type;
	switch (type) {
	case STARTPOD:
		ret = hyper_start_pod((char *)buf->data + 8, len - 8);
		hyper_print_uptime();
		break;
	case STOPPOD:
		ret = hyper_stop_pod(pod);
		break;
	case DESTROYPOD:
		fprintf(stdout, "get DESTROYPOD message\n");
		hyper_shutdown(pod);
		return 0;
	case EXECCMD:
		ret = hyper_exec_cmd((char *)buf->data + 8, len - 8);
		break;
	case WRITEFILE:
		ret = hyper_cmd_write_file((char *)buf->data + 8, len - 8);
		break;
	case READFILE:
		ret = hyper_cmd_read_file((char *)buf->data + 8, len - 8, &datalen, &data);
		break;
	case PING:
	case GETPOD:
		break;
	case READY:
		ret = hyper_rescan();
		break;
	case WINSIZE:
		ret = hyper_set_win_size((char *)buf->data + 8, len - 8);
		break;
	default:
		ret = -1;
		break;
	}

	if (ret < 0)
		hyper_send_type(de->fd, ERROR);
	else
		hyper_send_msg(de->fd, ACK, datalen, data);

	free(data);
	return 0;
}

static struct hyper_event_ops hyper_channel_ops = {
	.read		= hyper_event_read,
	.handle		= hyper_channel_handle,
	.rbuf_size	= 10240,
	.len_offset	= 4,
	/* TODO: vbox hyper should support channel ack */
	.ack		= 1,
};

static struct hyper_event_ops hyper_ttyfd_ops = {
	.read		= hyper_event_read,
	.write		= hyper_event_write,
	.handle		= hyper_ttyfd_handle,
	.rbuf_size	= 4096,
	.wbuf_size	= 10240,
	.len_offset	= 8,
};

static struct hyper_event_ops hyper_signal_ops = {
	.read		= hyper_signal_loop,
	.hup		= hyper_event_hup,
};

static int hyper_loop(void)
{
	int i, n;
	struct epoll_event *events;
	struct hyper_pod *pod = &global_pod;

	ctl.efd = epoll_create1(EPOLL_CLOEXEC);
	if (ctl.efd < 0) {
		perror("epoll_create failed");
		return -1;
	}

	fprintf(stdout, "hyper_init_event hyper channel event %p, ops %p, fd %d\n",
		&ctl.chan, &hyper_channel_ops, ctl.chan.fd);
	if (hyper_init_event(&ctl.chan, &hyper_channel_ops, pod) < 0 ||
	    hyper_add_event(ctl.efd, &ctl.chan, EPOLLIN) < 0) {
		return -1;
	}

	fprintf(stdout, "hyper_init_event hyper ttyfd event %p, ops %p, fd %d\n",
		&ctl.tty, &hyper_ttyfd_ops, ctl.tty.fd);
	if (hyper_init_event(&ctl.tty, &hyper_ttyfd_ops, pod) < 0 ||
	    hyper_add_event(ctl.efd, &ctl.tty, EPOLLIN) < 0) {
		return -1;
	}

	fprintf(stdout, "hyper_init_event hyper signal event %p, ops %p, fd %d\n",
		&ctl.sig, &hyper_signal_ops, ctl.sig.fd);
	if (hyper_init_event(&ctl.sig, &hyper_signal_ops, pod) < 0 ||
	    hyper_add_event(ctl.efd, &ctl.sig, EPOLLIN) < 0) {
		return -1;
	}

	events = calloc(MAXEVENTS, sizeof(*events));

	while (1) {
		n = epoll_wait(ctl.efd, events, MAXEVENTS, -1);
		fprintf(stdout, "%s epoll_wait %d\n", __func__, n);

		if (n < 0) {
			if (errno == EINTR)
				continue;
			perror("hyper wait event failed");
			return -1;
		}
		for (i = 0; i < n; i++) {
			if (hyper_handle_event(ctl.efd, &events[i]) < 0)
				return -1;
		}
	}

	free(events);
	close(ctl.efd);
	return 0;
}

int main(int argc, char *argv[])
{
	char *cmdline, *ctl_serial, *tty_serial;
	sigset_t mask;

	if (hyper_mkdir("/dev") < 0 ||
	    hyper_mkdir("/sys") < 0 ||
	    hyper_mkdir("/proc") < 0) {
		perror("create basic directroy failed");
		return -1;
	}

	if (mount("proc", "/proc", "proc", 0, NULL) == -1) {
		perror("mount proc failed");
		return -1;
	}

	hyper_print_uptime();

	if (mount("sysfs", "/sys", "sysfs", 0, NULL) == -1) {
		perror("mount sysfs failed");
		return -1;
	}

	if (mount("dev", "/dev", "devtmpfs", 0, NULL) == -1) {
		perror("mount sysfs failed");
		return -1;
	}

	if (hyper_mkdir("/dev/pts") < 0) {
		perror("create basic directroy failed");
		return -1;
	}

	if (mount("devpts", "/dev/pts", "devpts", 0, NULL) == -1) {
		perror("mount devpts failed");
		return -1;
	}

	cmdline = read_cmdline();

	setsid();

	ioctl(STDIN_FILENO, TIOCSCTTY, 1);

#ifdef WITH_VBOX
	ctl_serial = "/dev/ttyS0";
	tty_serial = "/dev/ttyS1";

	if (hyper_insmod("/vboxguest.ko") < 0 ||
	    hyper_insmod("/vboxsf.ko") < 0) {
		fprintf(stderr, "fail to load modules\n");
		return -1;
	}
#else
	ctl_serial = "sh.hyper.channel.0";
	tty_serial = "sh.hyper.channel.1";
#endif

	setenv("PATH", "/bin:/sbin/:/usr/bin/:/usr/sbin/", 1);

	sigemptyset(&mask);
	sigaddset(&mask, SIGCHLD);

	if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) {
		perror("sigprocmask SIGCHLD failed");
		return -1;
	}

	ctl.sig.fd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);
	if (ctl.sig.fd < 0) {
		perror("create signalfd failed");
		return -1;
	}

	ctl.chan.fd = hyper_setup_ctl_channel(ctl_serial);
	if (ctl.chan.fd < 0) {
		fprintf(stderr, "fail to setup hyper control serial port\n");
		goto out1;
	}

	ctl.tty.fd = hyper_setup_tty_channel(tty_serial);
	if (ctl.tty.fd < 0) {
		fprintf(stderr, "fail to setup hyper tty serial port\n");
		goto out2;
	}

	hyper_loop();

	close(ctl.tty.fd);
out2:
	close(ctl.chan.fd);
out1:
	close(ctl.sig.fd);

	free(cmdline);

	return 0;
}
