#include "../uwsgi.h"


extern struct uwsgi_server uwsgi;

#ifndef CLONE_NEWUTS
#define CLONE_NEWUTS 0x04000000
#endif

#ifndef CLONE_NEWPID
#define CLONE_NEWPID 0x20000000
#endif

#ifndef CLONE_NEWIPC
#define CLONE_NEWIPC 0x08000000
#endif

#ifndef CLONE_NEWNET
#define CLONE_NEWNET 0x40000000
#endif

int uwsgi_is_a_keep_mount(char *mp) {
	struct uwsgi_string_list *usl = uwsgi.ns_keep_mount;
	while(usl) {
		char *colon = strchr(usl->value, ':');
		if (colon) {
			if (!strcmp(colon+1, mp)) {
				return 1;
			}
		}
		else {
			// slip first part
			if (!uwsgi_startswith(usl->value, uwsgi.ns, strlen(uwsgi.ns))) {
				char *skipped = usl->value + strlen(uwsgi.ns);
				if (uwsgi.ns[strlen(uwsgi.ns)-1] == '/') {
					skipped--;
				}	
				if (!strcmp(skipped, mp)) {
					return 1;
				}
			}
			else {
				if (!strcmp(usl->value, mp)) {
					return 1;
				}
			}
		}
		usl = usl->next;
	}

	return 0;
	
}

static int uwsgi_ns_start(void *v_argv) {
	uwsgi_start(v_argv);
	return uwsgi_run();
}

void linux_namespace_start(void *argv) {
	for (;;) {
		char stack[PTHREAD_STACK_MIN];
		int waitpid_status;
		char *pid_str = NULL;
		uwsgi_log("*** jailing uWSGI in %s ***\n", uwsgi.ns);
		int clone_flags = SIGCHLD | CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWIPC | CLONE_NEWNS;
		if (uwsgi.ns_net) {
			clone_flags |= CLONE_NEWNET;
		}
		pid_t pid = clone(uwsgi_ns_start, stack + PTHREAD_STACK_MIN, clone_flags, (void *) argv);
		if (pid == -1) {
			uwsgi_error("clone()");
			exit(1);
		}
#if defined(MS_REC) && defined(MS_PRIVATE)
		if (mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL)) {
			uwsgi_error("mount()");
			exit(1);
		}
#endif
		pid_str = uwsgi_num2str((int) pid);
		// run the post-jail scripts
		if (setenv("UWSGI_JAIL_PID", pid_str, 1)) {
			uwsgi_error("setenv()");
		}
		free(pid_str);
		uwsgi_hooks_run(uwsgi.hook_post_jail, "post-jail", 1);
        	struct uwsgi_string_list *usl = uwsgi.exec_post_jail;
        	while(usl) {
                	uwsgi_log("running \"%s\" (post-jail)...\n", usl->value);
                	int ret = uwsgi_run_command_and_wait(NULL, usl->value);
                	if (ret != 0) {
                        	uwsgi_log("command \"%s\" exited with non-zero code: %d\n", usl->value, ret);
                        	exit(1);
                	}
                	usl = usl->next;
        	}

		uwsgi_foreach(usl, uwsgi.call_post_jail) {
                        if (uwsgi_call_symbol(usl->value)) {
                                uwsgi_log("unable to call function \"%s\"\n", usl->value);
				exit(1);
                        }
                }

		uwsgi_log("waiting for jailed master (pid: %d) death...\n", (int) pid);
		pid = waitpid(pid, &waitpid_status, 0);
		if (pid < 0) {
			uwsgi_error("waitpid()");
			exit(1);
		}

		// in Linux this is reliable
		if (WIFEXITED(waitpid_status) && WEXITSTATUS(waitpid_status) == 1) {
			exit(1);
		}
		else if (uwsgi.exit_on_reload && WIFEXITED(waitpid_status) && WEXITSTATUS(waitpid_status) == 0) {
			uwsgi_log("jailed master process exited and exit-on-reload is enabled, shutting down\n");
			exit(0);
		}


		uwsgi_log("pid %d ended. Respawning...\n", (int) pid);
	}

	// never here
}



void linux_namespace_jail() {

	char *ns_tmp_mountpoint = NULL, *ns_tmp_mountpoint2 = NULL;

	if (getpid() != 1) {
		uwsgi_log("your kernel does not support linux pid namespace\n");
		exit(1);
	}

	char *ns_hostname = strchr(uwsgi.ns, ':');
	if (ns_hostname) {
		ns_hostname[0] = 0;
		ns_hostname++;
		if (sethostname(ns_hostname, strlen(ns_hostname))) {
			uwsgi_error("sethostname()");
		}
	}

	FILE *procmounts;
	char line[1024];
	int unmounted = 1;
	char *delim0, *delim1;

	if (chdir(uwsgi.ns)) {
		uwsgi_error("chdir()");
		exit(1);
	}

	if (strcmp(uwsgi.ns, "/")) {
		ns_tmp_mountpoint = uwsgi_concat2(uwsgi.ns, "/.uwsgi_ns_tmp_mountpoint");
		if (mkdir(ns_tmp_mountpoint, S_IRWXU) < 0) {
			uwsgi_error("mkdir() ns_tmp_mountpoint");
			exit(1);
		}

		ns_tmp_mountpoint2 = uwsgi_concat2(ns_tmp_mountpoint, "/.uwsgi_ns_tmp_mountpoint");
		if (mkdir(ns_tmp_mountpoint2, S_IRWXU) < 0) {
			uwsgi_error("mkdir() ns_tmp_mountpoint2");
			exit(1);
                }

		if (mount(uwsgi.ns, ns_tmp_mountpoint, "none", MS_BIND, NULL)) {
			uwsgi_error("mount()");
		}
		if (chdir(ns_tmp_mountpoint)) {
			uwsgi_error("chdir()");
		}

		if (pivot_root(".", ns_tmp_mountpoint2)) {
			uwsgi_error("pivot_root()");
			exit(1);
		}



		if (chdir("/")) {
			uwsgi_error("chdir()");
			exit(1);
		}

	}

	uwsgi_log("remounting /proc\n");
	if (mount("proc", "/proc", "proc", 0, NULL)) {
		uwsgi_error("mount()");
		exit(1);
	}

	struct uwsgi_string_list *usl = uwsgi.ns_keep_mount;
	while(usl) {
		// bind mounting keep-mount items
		char *keep_mountpoint = usl->value;
		char *destination = strchr(usl->value, ':');
		if (destination) {
			keep_mountpoint = uwsgi_concat2n(usl->value, destination - usl->value, "", 0);
		}
		char *ks = uwsgi_concat2("/.uwsgi_ns_tmp_mountpoint", keep_mountpoint);
		if (!destination) {
			destination = usl->value;
			// skip first part of the name if under the jail
			if (!uwsgi_startswith(destination, uwsgi.ns, strlen(uwsgi.ns))) {
				if (uwsgi.ns[strlen(uwsgi.ns)-1] == '/') {
					destination += strlen(uwsgi.ns)-1;
				}
				else {
					destination += strlen(uwsgi.ns);
				}
			}
		}
		else {
			free(keep_mountpoint);
			destination++;
		}

		uwsgi_log("remounting %s to %s\n", ks+25, destination);
		if (mount(ks, destination, "none", MS_BIND, NULL)) {
			uwsgi_error("mount()");
		}
		free(ks);
		usl = usl->next;
	}

	while (unmounted) {

		unmounted = 0;
		procmounts = fopen("/proc/self/mounts", "r");
		if (!procmounts)
			break;
		while (fgets(line, 1024, procmounts) != NULL) {
			delim0 = strchr(line, ' ');
			if (!delim0)
				continue;
			delim0++;
			delim1 = strchr(delim0, ' ');
			if (!delim1)
				continue;
			*delim1 = 0;
			// and now check for keep-mounts
			if (uwsgi_is_a_keep_mount(delim0)) continue;
			if (!strcmp(delim0, "/") || !strcmp(delim0, "/proc"))
				continue;
			if (!umount(delim0)) {
				unmounted++;
			}
		}
		fclose(procmounts);
	}

	if (rmdir("/.uwsgi_ns_tmp_mountpoint/.uwsgi_ns_tmp_mountpoint")) {
		uwsgi_error("rmdir()");
	}
	if (rmdir("/.uwsgi_ns_tmp_mountpoint")) {
		uwsgi_error("rmdir()");
	}

	if (strcmp(uwsgi.ns, "/")) {
		free(ns_tmp_mountpoint2);
		free(ns_tmp_mountpoint);
	}



}
