#include <unistd.h>
#include "libbb.h"

pid_t xvfork_and_run(void (*fn)(void*) NORETURN, void *arg) {
	pid_t pid = vfork_and_run(fn, arg);

	if (pid < 0)
		bb_perror_msg_and_die("vfork");

	return pid;
}

/* Like vfork, but calls fn(arg) in the child instead of returning.
 * This is designed to match the semantics of GNO's fork2 call.
 */
#ifndef __GNO__

pid_t vfork_and_run(void (*fn)(void*) NORETURN, void *arg) {
	pid_t pid = vfork();

	if (pid == 0) {
		fn(arg);
	}

	return pid;
}

#else

# include <signal.h>
# include <gno/kvm.h>
# include <orca.h>

/* Turn off all ORCA/C stack repair code to avoid corruption. */
# ifdef __ORCAC__
#  pragma optimize 72
# endif

# pragma databank 1
void fork_thunk(void (*fn)(void*) NORETURN, void *arg, long sigmask) {
	sigsetmask(sigmask);
	fn(arg);
}
# pragma databank 0

const char * forked_child_name = "hush (forked)";

pid_t vfork_and_run(void (*fn)(void*) NORETURN, void *arg) {
	/* GNO's fork2 call will return immediately and allow the parent and 
	 * child processes to execute concurrently using the same memory
	 * space.  To prevent them stomping on each other, we want to get
	 * behavior like a traditional vfork() implementation, where the
	 * parent blocks until the child terminates or execs.
	 *
	 * Our approach will be to have the child send SIGALRM to the parent
	 * just before it terminates or execs, and block waiting for that here.
	 * We also set an alarm in case the child doesn't signal.
	 *
	 * When we get SIGALRM, we check the process tables to make sure the
	 * child has actually finished or exec'd.  If not, we loop and try again.
	 * We can't just rely on the fact that the child signaled us, because
	 * it may still be running in libc's implementation of exec*.
	 */
	
	long oldmask;
	bool environPushed;
	sig_t prev_alarm_sig;
	pid_t pid;
	kvmt *kvm_context;
	struct pentry *proc_entry;
	bool done = 0;
	
	/* Block all signals for now */
	oldmask = sigblock(-1);
	
	/* Isolate child process's environment from parent */
	environPushed = !environPush();
	
	pid = fork2(fork_thunk, 1024, 0, forked_child_name, 
				(sizeof(fn) + sizeof(arg) + sizeof(oldmask) + 1) / 2, 
				fn, arg, oldmask);
	if (pid < 0) 
		goto ret;
	
	prev_alarm_sig = signal(SIGALRM, SIG_IGN);
	
	while (!done) {
		/* Set alarm. This is a backup in case the child dies without signaling us. */
		alarm10(1);
	
		/* Wait until we get SIGALRM */
		sigpause(~sigmask(SIGALRM));
		
		/* Check if the child is really dead or forked by inspecting
		 * the kernel's process entry for it. */
		kvm_context = kvm_open();
		if (kvm_context == NULL)
			break;
		proc_entry = kvmgetproc(kvm_context, pid);
		if (proc_entry == NULL 
			|| (proc_entry->args != NULL 
				&& strcmp(forked_child_name, proc_entry->args + 8) != 0))
			done = 1;
		kvm_close(kvm_context);
	}
	
	alarm10(0);
	sigsetmask(~sigmask(SIGALRM));
	signal(SIGALRM, prev_alarm_sig);
	
ret:
	sigsetmask(oldmask);
	if (environPushed)
		environPop();
	return pid;
}

#endif