/*****************************************************************************
 *                                                                           *
 * DH_PROC.C                                                                 *
 *                                                                           *
 * Freely redistributable and modifiable.  Use at your own risk.             *
 *                                                                           *
 * Copyright 1994 The Downhill Project                                       *
 *                                                                           *
 *****************************************************************************/


/* Include stuff *************************************************************/
#include "INCLUDE/DH.H"
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <io.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/param.h>


/* Define stuff **************************************************************/
#define DOWNHILL_PROCESS_MAX 1024


/* Struct stuff **************************************************************/
struct downhill_Process_Struct
{
	pid_t pid[DOWNHILL_PROCESS_MAX];
	int   sigged[DOWNHILL_PROCESS_MAX];
};


/* Global stuff **************************************************************/
static struct downhill_Process_Struct* downhill_Process_Info = NULL;


/* Function stuff ************************************************************/

/* Init the process table ================================================== */
static int downhill_Process_Init(void)
{
	int process_Index;

	/* Skip this if it's already been done */
	if (downhill_Process_Info == NULL)
	{
		/* Get the memory for the process table */
		downhill_Process_Info =
		 calloc(sizeof(struct downhill_Process_Struct),1);
		if (downhill_Process_Info == NULL)
		{
			errno = ENOMEM;
			return FALSE;
		}

		/* And set it up */
		for (process_Index = 0;process_Index < DOWNHILL_PROCESS_MAX;
		 process_Index++)
		{
			downhill_Process_Info->pid[process_Index] = -1;
		}
	}

	/* Return that we're ready */
	return TRUE;
}

/* Find the index of a pid in the table ==================================== */
static int downhill_Process_GetIndex(pid_t process_Id)
{
	int process_Index;

	for (process_Index = 0;process_Index < DOWNHILL_PROCESS_MAX;
	 process_Index++)
	{
		if (process_Id == downhill_Process_Info->pid[process_Index])
		{
			return process_Index;
		}
	}

	return -1;
}

/* Clear a pid entry from the table ======================================== */
static void downhill_Process_ClearPid(pid_t process_Id)
{
	int process_Index = downhill_Process_GetIndex(process_Id);

	if (process_Index != -1)
	{
		downhill_Process_Info->pid[process_Index] = -1;
		CloseHandle((HANDLE)process_Id);
	}
}

/* Check if a process is signaled ========================================== */
int downhill_Process_IsSignaled(pid_t process_Id)
{
	int process_Index = downhill_Process_GetIndex(process_Id);

	if (process_Index != -1)
	{
		return downhill_Process_Info->sigged[process_Index];
	}

	return FALSE;
}

/* Signal a pid entry in the table ========================================= */
void downhill_Process_Signal(pid_t process_Id)
{
	int process_Index = downhill_Process_GetIndex(process_Id);

	if (process_Index != -1)
	{
		downhill_Process_Info->sigged[process_Index] = TRUE;
	}
}

/* Get the status of a process ============================================= */
static pid_t downhill_Process_GetStatus(pid_t process_Id,int* process_Status)
{
	DWORD process_StatusTemp;

	/* Get the status of the process */
	if (GetExitCodeProcess((HANDLE)process_Id,&process_StatusTemp) ==
	 FALSE)
	{
		errno = ECHILD;
		return -1;
	}

	/* Still living? */
	if (process_StatusTemp == STILL_ACTIVE)
	{
		return 0;
	}

	/* Return OK */
	if (process_Status != NULL)
	{
		*process_Status = (int)(((unsigned char)
		 process_StatusTemp)<<8);
	}
	return process_Id;
}

/* Get the status of any process =========================================== */
pid_t downhill_Process_GetStatusAny(int* process_Status)
{
	int          loop_Index;
	static int   process_Index = 0;
	pid_t        process_Id;

	/* Make sure the info exists */
	if (downhill_Process_Init() == FALSE)
	{
		return -1;
	}

	/* Check any processes */
	for (loop_Index = 0;loop_Index < DOWNHILL_PROCESS_MAX;loop_Index++)
	{
		/* Check this process */
		if (downhill_Process_Info->pid[process_Index] != -1)
		{
			process_Id = downhill_Process_GetStatus(
			 downhill_Process_Info->pid[process_Index],
			 process_Status);

			/* Handle how things came out */
			if (process_Id == -1)
			{
				downhill_Process_Info->pid[process_Index] = -1;
			}
			else if (process_Id > 0)
			{
				return process_Id;
			}
		}

		process_Index++;
		if (process_Index == DOWNHILL_PROCESS_MAX)
		{
			process_Index = 0;
		}
	}

	return 0;
}

/* Count the number of active children ===================================== */
static int downhill_Process_CountChildren(void)
{
	int process_Index;
	int child_Count = 0;

	/* Make sure we're inited */
	if (downhill_Process_Init() == FALSE)
	{
		return 0;
	}

	/* Count the number of active children */
	for (process_Index = 0;process_Index < DOWNHILL_PROCESS_MAX;
	 process_Index++)
	{
		if (downhill_Process_Info->pid[process_Index] != -1)
		{
			child_Count++;
		}
	}

	/* And return it */
	return child_Count;
}

/* Wait for any process to terminate ======================================= */
pid_t wait(int* process_Status)
{
	pid_t process_Id;

	/* Run through all processes */
	for (;;)
	{
		/* Check for interrupts */
		if (downhill_Signal_IsInterrupted())
		{
			errno = EINTR;
			return -1;
		}

		/* Make sure there are children */
		if (downhill_Process_CountChildren() == 0)
		{
			return -1;
		}

		/* Get any dead processes */
		process_Id = downhill_Process_GetStatusAny(process_Status);
		if (process_Id == -1)
		{
			return -1;
		}
		else if (process_Id > 0)
		{
			downhill_Process_ClearPid(process_Id);
			return process_Id;
		}

		/* Don't busy-loop */
		Sleep(DOWNHILL_SLEEP_TIME);
	}
}

/* Wait for a process to terminate ========================================= */
pid_t waitpid(pid_t process_Id,int* process_Status,int wait_Flags)
{
	pid_t wait_Pid;

	/* Waiting on more than one? */
	if (process_Id < 1)
	{
		/* Non-blocking */
		if (wait_Flags&WNOHANG)
		{
			if (downhill_Signal_IsInterrupted() > 0)
			{
				errno = EINTR;
				return -1;
			}
			wait_Pid = downhill_Process_GetStatusAny(
			 process_Status);
			if (wait_Pid > 0)
			{
				downhill_Process_ClearPid(wait_Pid);
			}
			return wait_Pid;
		}

		/* Blocking */
		return wait(process_Status);
	}
	else
	{
		/* Non-blocking */
		if (wait_Flags&WNOHANG)
		{
			if (downhill_Signal_IsInterrupted() > 0)
			{
				errno = EINTR;
				return -1;
			}
			wait_Pid = downhill_Process_GetStatus(process_Id,
			 process_Status);
			if (wait_Pid > 0)
			{
				downhill_Process_ClearPid(wait_Pid);
			}
			return wait_Pid;
		}

		/* Blocking */
		for (;;)
		{
			/* Check for interrupts */
			if (downhill_Signal_IsInterrupted() > 0)
			{
				errno = EINTR;
				return -1;
			}

			/* Should we return? */
			wait_Pid = downhill_Process_GetStatus(process_Id,
			 process_Status);
			if (wait_Pid != 0)
			{
				if (wait_Pid > 0)
				{
					downhill_Process_ClearPid(wait_Pid);
				}
				return wait_Pid;
			}

			/* Don't busy-loop */
			Sleep(DOWNHILL_SLEEP_TIME);
		}
	}
}

/* Wait for a process to terminate ========================================= */
#if (_DOWNHILL_POSIX_SOURCE == 0)
pid_t wait4(pid_t process_Id, int* process_Status,int wait_Flags,
       struct rusage* process_Usage)
{
	/* Just call waitpid() */
	process_Id = waitpid(process_Id,process_Status,wait_Flags);

	/* Dummy up the rusage entry */
	if (process_Usage != NULL)
	{
		memset(process_Usage,0,sizeof(struct rusage));
	}

	return process_Id;
}
#endif

/* Wait for any process to terminate ======================================= */
#if (_DOWNHILL_POSIX_SOURCE == 0)
pid_t wait3(int* process_Status,int wait_Flags,struct rusage* process_Usage)
{
	/* Just call wait4() */
	return wait4(0,process_Status,wait_Flags,process_Usage);
}
#endif

/* Add a process to the list =============================================== */
pid_t Downhill_Process_Add(HANDLE process_Handle)
{
	int process_Index;

	/* Make sure the table is there */
	if (downhill_Process_Init() == FALSE)
	{
		return -1;
	}

	/* Run through each pid */
	for (process_Index = 0;process_Index < DOWNHILL_PROCESS_MAX;
	 process_Index++)
	{
		/* If we find an empty slot (or they're re-adding the pid),
		   add the info */
		if ((downhill_Process_Info->pid[process_Index] == -1)
		 || (downhill_Process_Info->pid[process_Index] ==
		 (pid_t)process_Handle))
		{
			downhill_Process_Info->pid[process_Index] =
			 (pid_t)process_Handle;
			downhill_Process_Info->sigged[process_Index] = FALSE;

			return (pid_t)process_Handle;
		}
	}

	return 0;
}

/* Run a program in the background ========================================= */
static HANDLE downhill_Process_Forkexec(char* exec_Name,char* exec_Argv[],
               HANDLE file_Handle[],DWORD exec_Flags)
{
	char                exec_Command[MAXPATHLEN+1];
	STARTUPINFO         exec_Startup;
	PROCESS_INFORMATION exec_Info;
	int                 exec_Index;
	int                 exec_Inherit = FALSE;

	/* Make sure the table is there */
	if (downhill_Process_Init() == FALSE)
	{
		return INVALID_HANDLE_VALUE;
	}

	/* Build the command */
	strcpy(exec_Command,exec_Name);
	if (exec_Argv != NULL)
	{
		for (exec_Index = 1;exec_Argv[exec_Index] != NULL;exec_Index++)
		{
			strcat(exec_Command," ");
			strcat(exec_Command,exec_Argv[exec_Index]);
		}
	}

	/* Run the command */
	memset(&exec_Startup,0,sizeof(STARTUPINFO));
	exec_Startup.cb = sizeof(STARTUPINFO);
	if (file_Handle != NULL)
	{
		if (file_Handle[0] != INVALID_HANDLE_VALUE)
		{
			exec_Startup.hStdInput = file_Handle[0];
			exec_Inherit = TRUE;
			exec_Startup.dwFlags = STARTF_USESTDHANDLES;
		}
		if (file_Handle[1] != INVALID_HANDLE_VALUE)
		{
			exec_Startup.hStdOutput = file_Handle[1];
			exec_Inherit = TRUE;
			exec_Startup.dwFlags = STARTF_USESTDHANDLES;
		}
		if (file_Handle[2] != INVALID_HANDLE_VALUE)
		{
			exec_Startup.hStdError = file_Handle[2];
			exec_Inherit = TRUE;
			exec_Startup.dwFlags = STARTF_USESTDHANDLES;
		}
	}
	if (!CreateProcess(NULL,exec_Command,NULL,NULL,exec_Inherit,exec_Flags,
	 NULL,NULL,&exec_Startup,&exec_Info))
	{
		return INVALID_HANDLE_VALUE;
	}

	/* Clean up the thread */
	CloseHandle(exec_Info.hThread);

	/* Return the result */
	return exec_Info.hProcess;
}

/* Run a file in the background ============================================ */
pid_t Downhill_Process_Forkexec(char* exec_Name,char* exec_Argv[],
       HANDLE file_Handle[],DWORD exec_Flags)
{
	HANDLE exec_Handle = downhill_Process_Forkexec(exec_Name,exec_Argv,
                file_Handle,exec_Flags);

	if (exec_Handle == INVALID_HANDLE_VALUE)
	{
		return -1;
	}
	return Downhill_Process_Add(exec_Handle);
}

/* Run a file and return the results ======================================= */
char* Downhill_Process_System(char* exec_Name,char* exec_Argv[],
       int* exec_Return,int file_Return)
{
	HANDLE              exec_Handle;
	HANDLE              dump_Handle[3];
	static char         dump_File[MAXPATHLEN+1];
	char*               dump_Dir;
	SECURITY_ATTRIBUTES dump_Security;
	int                 result_Status;
	struct stat         result_Stat;
	char*               result_Mem;
	int                 result_Fd;

	/* Create the dump file */
	dump_Dir = getenv("TEMP");
	if (dump_Dir == NULL)
	{
		dump_Dir = getenv("TMP");
		if (dump_Dir == NULL)
		{
			dump_Dir = ".";
		}
	}
	strcpy(dump_File,dump_Dir);
	if ((dump_File[strlen(dump_File)-1] != '/') && (dump_File[strlen(
	 dump_File)-1] != '\\'))
	{
		strcat(dump_File,"/");
	}
	strcat(dump_File,"DHXXXXXX");
	mktemp(dump_File);
	strcat(dump_File,".TMP");
	dump_Security.nLength = sizeof(SECURITY_ATTRIBUTES);
	dump_Security.lpSecurityDescriptor = NULL;
	dump_Security.bInheritHandle = TRUE;
	dump_Handle[0] = INVALID_HANDLE_VALUE;
	dump_Handle[1] = dump_Handle[2] = CreateFile(dump_File,
	 GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,
	 &dump_Security,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,
	 INVALID_HANDLE_VALUE);
	if (dump_Handle[1] == INVALID_HANDLE_VALUE)
	{
		return NULL;
	}

	/* Run the process */
	exec_Handle = downhill_Process_Forkexec(exec_Name,exec_Argv,
	 dump_Handle,0);
	CloseHandle(dump_Handle[1]);
	if (exec_Handle == INVALID_HANDLE_VALUE)
	{
		errno = EINVAL;
		unlink(dump_File);
		return NULL;
	}
	for (;;)
	{
		Downhill_Signal_Check();
		if (GetExitCodeProcess(exec_Handle,&result_Status) == FALSE)
		{
			CloseHandle(exec_Handle);
			errno = EINVAL;
			unlink(dump_File);
			return NULL;
		}
		if (result_Status != STILL_ACTIVE)
		{
			CloseHandle(exec_Handle);
			break;
		}
		Sleep(DOWNHILL_SLEEP_TIME);
	}

	/* Return the name if that's what they want */
	if (file_Return != 0)
	{
		return dump_File;
	}

	/* Read the file */
	if (stat(dump_File,&result_Stat) != 0)
	{
		errno = EINVAL;
		unlink(dump_File);
		return NULL;
	}
	result_Mem = calloc(result_Stat.st_size+1,1);
	if (result_Mem == NULL)
	{
		errno = ENOMEM;
		unlink(dump_File);
		return NULL;
	}
	result_Fd = open(dump_File,O_RDONLY|O_BINARY);
	if (read(result_Fd,result_Mem,result_Stat.st_size) < 0)
	{
		close(result_Fd);
		unlink(dump_File);
		return NULL;
	}
	close(result_Fd);
	unlink(dump_File);

	/* And return */
	return result_Mem;
}
