/*
 * @(#)Process.cpp
 *
 * This file is part of webCDwriter - Network CD Writing.
 *
 * Copyright (C) 1999-2004 Jörg P. M. Haeger
 *
 * webCDwriter is free software. See CDWserver.cpp for details.
 *
 * 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) {
	init(fileName);
}

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

	if (protoStream != NULL && protoStream != stdout)
		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 new Exception("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::addArg(String &str) {
	try {
		addArg("%s", str.getBytes());
	}
	catch (Exception *e) {
		String::free(&str);
		throw e;
	}
	String::free(&str);
}

void Process::addArgs(const char *args) {
	char str[strlen(args) + 1];
	for (int i = 0; ; i++, args++) {
		str[i] = *args;
		if (str[i] == 0 || str[i] == ' ' || str[i] == '\t') {
			str[i] = 0;
			if (i > 0)
				addArg(str);
			i = -1;
		}
		if (*args == 0)
			break;
	}
}

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::getOutputStream() {
	return outPipe[STDOUT_FILENO];
}

int Process::getResult() {
	log.debug("Process", "getResult() %d %d", 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;
}

void Process::init(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;
}

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(int timeout) {
	const char *lineStr = inStream->next(timeout);
	if (lineStr != NULL)
		if (protoStream != NULL)
			if (protoStream != stdout) {
				fprintf(protoStream, "%s\n", lineStr);
				fflush(protoStream);
			}
			else
				log.put(5, S.e + "S"
					+ (pid < 10000? "0": "")
					+ (pid < 1000? "0": "")
					+ (pid < 100? "0": "")
					+ (pid < 10? "0": "")
					+ pid + " " + lineStr);
	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::sendTerminateSignal() {
	if (pid > 0 && result < 0) {
		log.put(5, S.e + "kill " + pid);
		int res = kill(pid, SIGTERM);
		log.put(5, S.e + "kill " + pid + " = " + res);
		if (res != 0) {
			log.put(5, S.e + "kill " + errno + " " + strerror(errno));
			String name = "/tmp/CDWcancel-";
			name = name + pid;
			FILE *out = fopen(name.getBytes(), "w");
			if (out != NULL)
				fclose(out);
		}
	}
}

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

void Process::setProtoFile(const char *fileName) {
	if (fileName != NULL && config.getLogLevel() > 9) {
		makeDirs(fileName);
		protoStream = fopen(fileName, "w");
		if (protoStream == NULL)
			protoStream = stdout;
	}
	else
		protoStream = stdout;
}

void Process::show() {
	log.put(4, S.e + "Process: \"" + args[0] + "\"");
	for (int i = 1; i < argsNum; i++)
		log.put(4, S.e + "      " + (i < 10? " ": "") + 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 new Exception("outPipe failed");
	if (pipe(inPipe) != 0)
		throw new Exception("inPipe failed");
	if (splitOut)
		if (pipe(errPipe) != 0)
			throw new Exception("errPipe failed");

	bool debugProcessStart
		= config.getDebug().indexOf("Process::start") >= 0;

	char logDir[1024];
	strncpy(logDir, config.getLogDir(), sizeof logDir - 1);
	logDir[sizeof logDir - 1] = 0;

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

	if (pid == (pid_t)0) {
		// We are the child side.
		FILE *logFile = NULL;
		if (debugProcessStart) {
			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", 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 new Exception("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);
}
