/*-
 * Copyright (c) 1980, 1991 The Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/* 
 * The fork() here is based on the ideas used by cygwin
 * -amol
 *
 */

/*
 * _M_ALPHA changes by Mark Tucker
 */

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <fcntl.h>
#include <io.h>
#include <stdlib.h>
#include <setjmp.h>
#include <ntport.h>
#include "forkdata.h"
#include "sh.h"

#pragma intrinsic("memcpy", "memset","memcmp")
#pragma warning(push,3) // forget about W4 here

typedef unsigned long u_long;
typedef void *ptr_t;
typedef unsigned char U_char;	
typedef unsigned int U_int;
typedef unsigned short U_short;
typedef unsigned long U_long;


static void stack_probe(void *ptr) ;
/*static void heap_init(void);*/
BOOL CreateWow64Events(DWORD , HANDLE *, HANDLE *, BOOL);

//
// This is exported from the user program.
// It must return 0 for no error !!!!
extern int fork_copy_user_mem(HANDLE );

/* 
 * Apparently , visual c++ on the alpha does not place the
 * fork data contiguously. To work around that, Mark created
 * this structure (see forkdata.h)
 * -amol
 */
ForkData gForkData = {0,0,0,0,0,{0},0,0,0};


#ifdef _M_IX86

u_long _old_exr = 0; // Saved exception registration for longjmp

#endif // _M_ALPHA
/*
 * This hack is an attempt at getting to the exception registration
 * in an architecture-independent way. It's critical for longjmp in a
 * code using __try/__except blocks. Microsoft Visual C++ does a global
 * unwind during a longjmp, and that can cause havoc if the exception 
 * registration stored in longjmp is lower(address wise, indicating a jump
 * from below of the stack upward.) in the stack than the current
 * registration (returned by NtCurrentTeb).
 *
 * This works with VC++, because that's all I have. With other compilers, 
 * there might be minimal changes required, depending on where the 
 * exception registration record is stored in the longjmp structure.
 *
 * -amol 2/6/97
 */

NT_TIB * (* myNtCurrentTeb)(void);

#define GETEXCEPTIONREGIST() (((NT_TIB*)get_teb())->ExceptionList)
#define GETSTACKBASE()		 (((NT_TIB*)get_teb())->StackBase)



static NT_TIB *the_tib;

#if !defined(_M_IA64) && !defined(_M_AMD64)
void *get_teb(void) {


	if (the_tib)
		return the_tib;

#pragma warning(suppress:6309)
	myNtCurrentTeb = (void*)GetProcAddress(LoadLibrary("ntdll.dll"),
							"NtCurrentTeb");
	if (!myNtCurrentTeb)
		return NULL;
	the_tib = myNtCurrentTeb();

	if (the_tib == NULL)
		abort();
	return the_tib;
}
#else
#define get_teb NtCurrentTeb
#endif _M_IA64

void set_stackbase(void*ptr){
	GETSTACKBASE() = ptr;
}
/* 
 * This must be called by the application as the first thing it does.
 * -amol 2/6/97
 *
 * Well, maybe not the FIRST..
 * -amol 11/10/97
 */

extern BOOL bIsWow64Process;

int fork_init(void) {


	//heap_init(); Now called as the very first thing in silly_entry().

	if (__forked) {


		// stack_probe probes out a decent-sized stack for the child,
		// since initially it has a very small stack (1 page).
		//

		/* not needed since default commit is set to 0.5MB in 
		 * makefile.win32
		 *
		 * stack_probe((char *)__fork_stack_end - 64);
		 */

		//
		// Save the old Exception registration record and jump
		// off the cliff.
		//
#ifdef  _M_IX86
		_old_exr = __fork_context[6];
		__fork_context[6] =(int)GETEXCEPTIONREGIST();//tmp;
#endif  
		//
		// Whee !
		longjmp(__fork_context,1);
	}

	return 0;
}
int fork(void) {

	size_t rc;
	size_t stacksize;
	char modname[512];/*FIXBUF*/
	HANDLE  hProc,hThread, hArray[2];
	STARTUPINFO si;
	PROCESS_INFORMATION pi;
	SECURITY_ATTRIBUTES sa;
	DWORD dwCreationflags;
	unsigned int priority;
	HANDLE h64Parent,h64Child;

#ifndef _M_ALPHA
	unsigned long fork_stack_end;
#endif _M_ALPHA

	__fork_stack_begin =GETSTACKBASE();

#ifndef _M_ALPHA
	__fork_stack_end = &fork_stack_end;
#else
	__fork_stack_end = (unsigned long *)__asm("mov $sp, $0");
#endif /*_M_ALPHA*/

	h64Parent = h64Child = NULL;
	//
	// Create two inheritable events
	//
	sa.nLength = sizeof(sa);
	sa.lpSecurityDescriptor =0;
	sa.bInheritHandle = TRUE;
	if (!__hforkchild)
		__hforkchild = CreateEvent(&sa,TRUE,FALSE,NULL);
	if (!__hforkparent)
		__hforkparent = CreateEvent(&sa,TRUE,FALSE,NULL);

	rc = setjmp(__fork_context);
 #if _M_AMD64
    ((_JUMP_BUFFER *)&__fork_context)->Frame = 0; //https://stackoverflow.com/questions/26605063/an-invalid-or-unaligned-stack-was-encountered-during-an-unwind-operation
#endif
	if (rc) { // child
#ifdef  _M_IX86
		//
		// Restore old registration
		// -amol 2/2/97
		GETEXCEPTIONREGIST() = (struct _EXCEPTION_REGISTRATION_RECORD*)_old_exr;
#endif // _M_ALPHA
		SetEvent(__hforkchild);

		dprintf("Child ready to rumble\n");
		if(WaitForSingleObject(__hforkparent,FORK_TIMEOUT) != WAIT_OBJECT_0)
			ExitProcess(0xFFFF);

		CloseHandle(__hforkchild);
		CloseHandle(__hforkparent);
		__hforkchild = __hforkparent=0;

		//__asm { int 3};
		restore_fds();

		STR_environ = blk2short(environ);
		environ = short2blk(STR_environ);	/* So that we can free it */

        dprintf("returning 0\n");
		return 0;
	}
	copy_fds();
	memset(&si,0,sizeof(si));
	si.cb= sizeof(si);

	/*
	 * This f!@#!@% function returns the old value even if the std handles
	 * have been closed.
	 * Skip this step, since we know tcsh will do the right thing later.
	 * 
	 si.hStdInput= GetStdHandle(STD_INPUT_HANDLE);
	 si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
	 si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
	 */

	if (!GetModuleFileName(GetModuleHandle(NULL),modname,512) ) {
		rc = GetLastError();
		return -1;
	}
	dwCreationflags = GetPriorityClass(GetCurrentProcess());
	priority = GetThreadPriority(GetCurrentThread());
	rc = CreateProcess(NULL,
			modname,
			NULL,
			NULL,
			TRUE,
			CREATE_SUSPENDED | dwCreationflags,
			NULL,
			NULL,
			&si,
			&pi);
	if (!rc)  {
		rc = GetLastError();
		return -1;
	}

	ResetEvent(__hforkchild);
	ResetEvent(__hforkparent);

	hProc = pi.hProcess;
	hThread = pi.hThread;


	__forked=1;
	/*
	 * Usage of events in the wow64 case:
	 *
	 * h64Parent : initially non-signalled
	 * h64Child  : initially non-signalled
	 *
	 *    1. Create the events, resume the child thread.
	 *    2. Child opens h64Parent to see if it is a child process in wow64
	 *    3. Child opens and sets h64Child to tell parent it's running. (This
	 *       step is needed because we can't copy to a process created in the
	 *       suspended state on wow64.)
	 *    4. Copy gForkData and then set h64Parent. This tells the child
	 *       that the parameters in the structure are trustworthy.
	 *    5. Wait for h64Child so that we know the child has created the stack
	 *       in dynamic memory.
	 *
	 *   The rest of the fork hack should now proceed as in x86
	 *
	 */
	if (bIsWow64Process) {

		// allocate the heap for the child. this can be done even when
		// the child is suspended. 
		// avoids inexplicable allocation failures in the child.
		if (VirtualAllocEx(hProc,
					__heap_base,
					__heap_size,
					MEM_RESERVE,
					PAGE_READWRITE) == NULL) {
			dprintf("virtual allocex failed %d\n",GetLastError());
			goto error;
		}
		if (VirtualAllocEx(hProc,
					__heap_base,
					__heap_size,
					MEM_COMMIT,
					PAGE_READWRITE) == NULL) {
			dprintf("virtual allocex2 failed %d\n",GetLastError());
			goto error;
		}
        {
            char *stk,*end;
            stk = (char*)__fork_stack_begin + sizeof(char*)- (1<<20) - 65536;

            dprintf("begin is 0x%08x\n",stk);
            end = VirtualAllocEx(hProc,stk, (1<<20)+65536 , MEM_RESERVE , PAGE_READWRITE);
        }

		// Do NOT expect existing events
		if (!CreateWow64Events(pi.dwProcessId,&h64Parent,&h64Child,FALSE)) {
			goto error;
		}
		ResumeThread(hThread);

		// wait for the child to tell us it is running
		//if (WaitForSingleObject(h64Child,FORK_TIMEOUT) != WAIT_OBJECT_0) {
		//	rc = GetLastError();
		//	goto error;
		//}
		hArray[0] = h64Child;
		hArray[1] = hProc;

		if (WaitForMultipleObjects(2,hArray,FALSE,FORK_TIMEOUT) != 
				WAIT_OBJECT_0){

			rc = GetLastError();
			goto error;
		}

	}
	//
	// Copy all the shared data
	//
	if (!WriteProcessMemory(hProc,&gForkData,&gForkData,
				sizeof(ForkData),&rc)) {
		goto error;
	}
	if (rc != sizeof(ForkData)) 
		goto error;

	if (!bIsWow64Process) {
		rc = ResumeThread(hThread);
	}
	// in the wow64 case, the child will be waiting  on h64parent again.
	// set it, and then wait for h64child. This will mean the child has
	// a stack set up at the right location.
	else {
		SetEvent(h64Parent);
		hArray[0] = h64Child;
		hArray[1] = hProc;

		if (WaitForMultipleObjects(2,hArray,FALSE,FORK_TIMEOUT) != 
				WAIT_OBJECT_0){

			rc = GetLastError();
			goto error;
		}
		CloseHandle(h64Parent);
		CloseHandle(h64Child);
		h64Parent = h64Child = NULL;
	}

	//
	// Wait for the child to start and init itself.
	// The timeout is so that we don't wait too long
	//
	hArray[0] = __hforkchild;
	hArray[1] = hProc;

	if (WaitForMultipleObjects(2,hArray,FALSE,FORK_TIMEOUT) != WAIT_OBJECT_0){

		int err = GetLastError(); // For debugging purposes
		dprintf("wait failed err %d\n",err);
		goto error;
	}

	// Stop the child again and copy the stack and heap
	//
	SuspendThread(hThread);

	if (!SetThreadPriority(hThread,priority) ) {
		priority =GetLastError();
	}

	// stack
	stacksize = (char*)__fork_stack_begin - (char*)__fork_stack_end;
	if (!WriteProcessMemory(hProc,(char *)__fork_stack_end,
				(char *)__fork_stack_end,
				(u_long)stacksize,
				&rc)){
		goto error;
	}
	//
	// copy heap itself
	if (!WriteProcessMemory(hProc, (void*)__heap_base,(void*)__heap_base, 
				(DWORD)((char*)__heap_top-(char*)__heap_base),
				&rc)){
		goto error;
	}

	rc = fork_copy_user_mem(hProc);

	if(rc) {
		goto error;
	}

	// Release the child.
	SetEvent(__hforkparent);
	rc = ResumeThread(hThread);

	__forked=0;
	dprintf("forked process %d\n",pi.dwProcessId);
	start_sigchild_thread(hProc,pi.dwProcessId);
	close_copied_fds();

	CloseHandle(hThread);
	//
	// return process id to parent.
	return pi.dwProcessId;

error:
	__forked=0;
	SetEvent(__hforkparent);
	ResumeThread(hThread);
	CloseHandle(hProc);
	CloseHandle(hThread);
	if (h64Parent) {
		SetEvent(h64Parent); // don't let child block forever
		CloseHandle(h64Parent);
	}
	if (h64Child)
		CloseHandle(h64Child);
	return -1;
}
#pragma optimize("",off)
// The damn optimizer will remove the recursion, resulting in an infinite
// loop. -amol 4/17/97
void stack_probe (void *ptr) {
	char buf[1000];
	int x;

	if (&x > (int *)ptr)
		stack_probe(ptr);
	(void)buf;
}
#pragma optimize("",on)
//
// This function basically reserves some heap space.
// In the child it also commits the size committed in the parent.
void heap_init(void) {

	char * temp;
	int err;
	if (__forked) {
		temp = (char *)VirtualAlloc((void*)__heap_base,__heap_size, MEM_RESERVE,
				PAGE_READWRITE);
		if (temp != (char*)__heap_base) {
			if (!temp){
				err = GetLastError();
				if (bIsWow64Process)
					ExitProcess(0);
				abort();
			}
			else 
				__heap_base = temp;
		}
		if (!VirtualAlloc(__heap_base,(char*)__heap_top -(char*)__heap_base, 
					MEM_COMMIT,PAGE_READWRITE)){
			err = GetLastError();
			if (bIsWow64Process)
				ExitProcess(0);
			abort();
		}
		temp = (char*)__heap_base;
	}
	else {
		SYSTEM_INFO sysinfo;
		GetSystemInfo(&sysinfo);
		__heap_size = sysinfo.dwPageSize * 1024;
		__heap_base = VirtualAlloc(0 , __heap_size,MEM_RESERVE|MEM_TOP_DOWN,
				PAGE_READWRITE);

		if (__heap_base == 0) {
			abort();
		}

		__heap_top = __heap_base;
	}

}
//
// Implementation of sbrk() for the fmalloc family
//
void * sbrk(int delta) {

	void *retval;
	void *old_top=__heap_top;
	char *b = (char*)__heap_top;

	if (delta == 0)
		return  __heap_top;
	if (delta > 0) {

		retval =VirtualAlloc((void*)__heap_top,delta,MEM_COMMIT,PAGE_READWRITE);

		if (retval == 0 )
			abort();

		b += delta;
		__heap_top = (void*)b;
	}
	else {
		retval = VirtualAlloc((void*)((char*)__heap_top - delta), 
				delta,MEM_DECOMMIT, PAGE_READWRITE);

		if (retval == 0)
			abort();

		b -= delta;
		__heap_top = (void*)b;
	}

	return (void*) old_top;
}
/*
 * Semantics of CreateWow64Events
 *
 * Try to open the events even if bOpenExisting is FALSE. This will help
 * us detect name duplication.
 *
 *       1. If OpenEvent succeeds,and bOpenExisting is FALSE,  fail.
 *
 *       2. If OpenEvent failed,and bOpenExisting is TRUE fail
 *
 *       3. else create the events anew
 *
 */
#define TCSH_WOW64_PARENT_EVENT_NAME "tcsh-wow64-parent-event"
#define TCSH_WOW64_CHILD_EVENT_NAME  "tcsh-wow64-child-event"
BOOL CreateWow64Events(DWORD pid, HANDLE *hParent, HANDLE *hChild, 
		BOOL bOpenExisting) {

	SECURITY_ATTRIBUTES sa;
	char parentname[256],childname[256];

	*hParent = *hChild = NULL;

	// make darn sure they're not inherited
	sa.nLength = sizeof(sa);
	sa.lpSecurityDescriptor =0;
	sa.bInheritHandle = FALSE;
	//

#pragma warning(disable:4995)

	// This event tells the child to hold for gForkData to be copied
	wsprintfA(parentname, "Local\\%u-%s",pid, TCSH_WOW64_PARENT_EVENT_NAME);

	wsprintfA(childname, "Local\\%u-%s",pid, TCSH_WOW64_CHILD_EVENT_NAME );

#pragma warning(default:4995)

	*hParent = OpenEvent(EVENT_ALL_ACCESS,FALSE, parentname);

	if(*hParent) {
		if (bOpenExisting == FALSE) { // didn't expect to be a child process
			CloseHandle(*hParent);
			*hParent = NULL;
			return FALSE;
		}

		*hChild = OpenEvent(EVENT_ALL_ACCESS,FALSE, childname);
		if (!*hChild) {
			CloseHandle(*hParent);
			*hParent = NULL;
			return FALSE;
		}

		return TRUE;
	}
	else { //event does not exist
		if (bOpenExisting == TRUE)
			return FALSE;
	}

	*hParent = CreateEvent(&sa,FALSE,FALSE,parentname);	
	if (!*hParent)
		return FALSE;


	*hChild = CreateEvent(&sa,FALSE,FALSE,childname);	
	if (!*hChild){
		CloseHandle(*hParent);
		*hParent = NULL;
		return FALSE;
	}
	return TRUE;
}
