
// Project: webCDwriter - Network CD Writing
// File: Process.cpp
// Author: Jörg Haeger, 02.11.1999

#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdarg.h>
#include <string.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>
#include "Config.h"
#include "Support.h"
#include "LineReader.h"
#include "Log.h"
#include "Process.h"

Process::Process(const char *fileName)
{
	argsNum = envsNum = 0;
	args[argsNum++] = strDup(fileName);
	args[argsNum] = NULL;
	envs[envsNum] = NULL;
	pid = -1;
	result = -1;
	outPipe[0] = outPipe[1] = -1;
	inPipe[0] = inPipe[1] = -1;
	errPipe[0] = errPipe[1] = -1;
	inStream = NULL;
	protoStream = NULL;
}

Process::~Process()
{
	if (pid > 0 && result < 0)
	{
		kill(pid, SIGKILL);
		getResult();
	}
	log.debug("Process", "~Process() %s %d result = %d\n",
		args[0], pid, result);

	if (protoStream != NULL)
		fclose(protoStream);
	if (inStream != NULL)
		delete inStream;
	for (int i = 0; i < envsNum; i++)
		delete envs[i];
	for (int i = 0; i < argsNum; i++)
		delete args[i];
	closePipes();
}

void Process::addArg(const char *format, ...)
{
	if (argsNum == 256 - 1)
		throw "Process::addArg: to many args";

	char str[1024];
	va_list ap;
	va_start(ap, format);
	vsnprintf(str, sizeof str, format, ap);
	va_end(ap);

	args[argsNum++] = strDup(str);
	args[argsNum] = NULL;
}

void Process::closePipes()
{
	for (int i = 0; i < 2; i++)
	if (outPipe[i] >= 0)
	{
		close(outPipe[i]);
		outPipe[i] = -1;
	}

	for (int i = 0; i < 2; i++)
	if (inPipe[i] >= 0)
	{
		close(inPipe[i]);
		inPipe[i] = -1;
	}

	for (int i = 0; i < 2; i++)
	if (errPipe[i] >= 0)
	{
		close(errPipe[i]);
		errPipe[i] = -1;
	}
}

void Process::closePipeToChild()
{
	close(outPipe[STDOUT_FILENO]);
	outPipe[STDOUT_FILENO] = -1;
}

int Process::getErrorStream()
{
	return errPipe[STDIN_FILENO];
}

int Process::getInputStream()
{
	return inPipe[STDIN_FILENO];
}

int Process::getResult()
{
	log.debug("Process", "getResult() %d %d\n", pid, result);
	if (pid <= 0 || result >= 0)
		return result;

	if (inStream != NULL)
		delete inStream;
	inStream = NULL;

	int res, status;
//	TEMP_FAILURE_RETRY(waitpid(pid, &status, 0));
	while ((res = waitpid(pid, &status, 0)) == -1 && errno == EINTR);

	if (res == -1)
		result = 256;
	else
	if (WIFEXITED(status))
		result = WEXITSTATUS(status);
	else
	if (WIFSIGNALED(status))
		result = 1000 + WTERMSIG(status);
	else
		result = 257;
	return result;
}

int Process::isLineAvailable()
{
	return inStream != NULL && inStream->available();
}

int Process::read(char *buf, int count)
{
	fcntl(inPipe[STDIN_FILENO], F_SETFL, O_NONBLOCK);
	return ::read(inPipe[STDIN_FILENO], buf, count);
}

const char *Process::readLine()
{
	const char *lineStr = inStream->next();
	if (protoStream != NULL && lineStr != NULL)
	{
		fprintf(protoStream, "%s\n", lineStr);
		fflush(protoStream);
	}
	return lineStr;
}

int Process::run()
{
	start();
	while (1)
	{
		const char *lineStr = readLine();
		if (lineStr == NULL)
			break;
	}
	return getResult();
}

void Process::select()
{
	fd_set fdSet;
	FD_ZERO(&fdSet);
	if (inPipe[STDIN_FILENO] >= 0)
		FD_SET(inPipe[STDIN_FILENO], &fdSet);

	fd_set fdWriteSet;
	FD_ZERO(&fdWriteSet);
	if (outPipe[STDOUT_FILENO] >= 0)
		FD_SET(outPipe[STDOUT_FILENO], &fdWriteSet);

	::select(FD_SETSIZE, &fdSet, &fdWriteSet, NULL, NULL);
}

void Process::setInputStream(int fd)
{
	outPipe[STDIN_FILENO] = fd;
}

void Process::setProtoFile(const char *fileName)
{
	if (config.getLogSubProcess())
	{
		makeDirs(fileName);
		protoStream = fopen(fileName, "w");
	}
}

void Process::show()
{
	log.putv("Process: <%s>\n", args[0]);
	for (int i = 1; i < argsNum; i++)
		log.putv("%8d: <%s>\n", i, args[i]);
}

/**
	@see glibc-2.1.1/sysdeps/posix/pipestream.c
*/
void Process::start(const char *workDirName, int splitOut)
{
	if (outPipe[STDIN_FILENO] < 0)
		if (pipe(outPipe) != 0)
			throw "outPipe failed";
	if (pipe(inPipe) != 0)
		throw "inPipe failed";
	if (splitOut)
		if (pipe(errPipe) != 0)
			throw "errPipe failed";

	char debug[1024], logDir[1024];
	strcpy(debug, config.getDebug());
	strcpy(logDir, config.getLogDir());

	pid = fork();
	if (pid == (pid_t)-1)
		throw "fork failed";

	if (pid == (pid_t)0)
	{
		// We are the child side.
		FILE *logFile = NULL;
		if (strstr(debug, "Process") != NULL)
		{
			char logFileName[1024];
			sprintf(logFileName, "%s/childs/%d",
				logDir, getpid());
			makeDirs(logFileName);
			logFile = fopen(logFileName, "w");
			if (logFile != NULL)
			{
				fprintf(logFile, "1\n");
				fflush(logFile);
			}
		}

		dup2(outPipe[STDIN_FILENO], STDIN_FILENO);
		dup2(inPipe[STDOUT_FILENO], STDOUT_FILENO);
		if (splitOut)
			dup2(errPipe[STDOUT_FILENO], STDERR_FILENO);
		else
			dup2(inPipe[STDOUT_FILENO], STDERR_FILENO);

		if (logFile != NULL)
		{
			fprintf(logFile, "2\n");
			fflush(logFile);
		}

		closePipes();

		if (logFile != NULL)
		{
			fprintf(logFile, "3\n");
			fflush(logFile);
		}

		if (workDirName != NULL)
			if (chdir(workDirName) != 0)
				exit(129);

		if (logFile != NULL)
		{
			fprintf(logFile, "4\n");
			fflush(logFile);
		}

		execvp(args[0], args);

		if (logFile != NULL)
		{
			fprintf(logFile, "5\n");
			fclose(logFile);
		}

		exit(128);
	}

	log.debug("Process", "start() %s %d\n", args[0], pid);
	close(outPipe[STDIN_FILENO]);
	outPipe[STDIN_FILENO] = -1;
	close(inPipe[STDOUT_FILENO]);
	inPipe[STDOUT_FILENO] = -1;
	if (errPipe[STDOUT_FILENO] >= 0)
	{
		close(errPipe[STDOUT_FILENO]);
		errPipe[STDOUT_FILENO] = -1;
	}

	if (splitOut)
	{
		inStream = new LineReader(errPipe[STDIN_FILENO]);
//		errPipe[STDIN_FILENO] = -1;
	}
	else
	{
		inStream = new LineReader(inPipe[STDIN_FILENO]);
//		inPipe[STDIN_FILENO] = -1;
	}
	if (inStream == NULL)
		throw "open inStream failed";
}

void Process::terminate()
{
	if (pid > 0 && result < 0)
	{
		kill(pid, SIGTERM);
		getResult();
	}
}

const char *Process::toString(char *str)
{
	sprintf(str, "Process: <%s", args[0]);
	for (int i = 1; i < argsNum; i++)
	{
		strcat(str, " ");
		strcat(str, args[i]);
	}
	strcat(str, ">");
	return str;
}

int Process::write(const char *buf, int count)
{
	fcntl(outPipe[STDOUT_FILENO], F_SETFL, O_NONBLOCK);
	return ::write(outPipe[STDOUT_FILENO], buf, count);
}
