/*
 * Run scripts in parallel
 *
 * Copyright (C) 2003--2007  Enrico Zini <enrico@debian.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
 */

#include "processrunner.h"
#include "starter.h"
#include "util/output.h"

#include <wibble/sys/exec.h>
#include <wibble/sys/childprocess.h>

#include <sys/types.h>	// pid_t
#include <sys/wait.h>	// wait

#include <map>
#include <algorithm>
#include <sstream>

using namespace std;
using namespace wibble::sys;
using namespace util;

static ProcessRunner* instance = 0;

ProcessRunner& ProcessRunner::get()
{
	if (!instance)
	{
		instance = new ProcessRunner;
		Starter::get().add(instance);
	}
	return *instance;
}

void ProcessRunner::reset()
{
	if (instance)
	{
		delete instance;
		instance = 0;
	}
}

namespace processrunner {

/**
 * ChildProcess used to run a script in the background
 */
class Script : public ChildProcess
{
protected:
	string tag;
	vector<string> env;
	string cmdline;

public:
	Script(const std::string& tag, const std::vector<std::string>& env, const std::string& cmdline) throw ()
		: tag(tag), env(env), cmdline(cmdline) {}
	virtual ~Script() throw () {}

	virtual int main();
};

int Script::main()
{
	try {
		ShellCommand cmd(cmdline);
		cmd.envFromParent = false;
		std::copy(env.begin(), env.end(), back_inserter(cmd.env));
		#if 0
		cmd.env.push_back("NAME=" + tag);
		cmd.env.push_back("IFACE=" + iface);
		cmd.env.push_back("GUESSNET=true");
		cmd.env.push_back(string("PATH=") + SCRIPTDIR + ":/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin");
		#endif

		//debug("SCRIPT MAIN Running %s\n", cmdline.c_str());

		cmd.exec();
	} catch (std::exception& e) {
		error("%s\n", e.what());
	}
	return 1;
}

ProcData::ProcData(
	const string& tag,
	const string& cmdline,
	const std::vector<std::string>& env,
	ProcessListener* listener)
	: tag(tag), script(0), listener(listener)
{
	script = new Script(tag, env, cmdline);
}

ProcData::ProcData(const ProcData& d)
    : tag(d.tag), script(d.script), listener(d.listener)
{
}

ProcData::~ProcData()
{
}

void ProcData::run()
{
	script->fork();
}

}


ProcessRunner::ProcessRunner()
	: requested_shutdown(false), running(false)
{
}

ProcessRunner::~ProcessRunner()
{
	if (running)
		shutdown();
}

void ProcessRunner::addProcess(
	const std::string& tag,
	const std::string& cmdline,
	const std::vector<std::string>& env,
	ProcessListener* pl)
{
	{
		MutexLock lock(listenersMutex);
		queuedForRunning.push(processrunner::ProcData(tag, cmdline, env, pl));
		listenersCond.broadcast();
		debug("PRI added process %s: %s\n", tag.c_str(), cmdline.c_str());
	}
}

void ProcessRunner::shutdown()
{
	if (running)
	{
		{
			MutexLock lock(listenersMutex);
			requested_shutdown = true;
			debug("PRI asked for shutdown\n");
		}
		// FIXME: if someone has a cleaner ideas...
		//kill(SIGCHLD);
		/*
		   cancel();
		   */
		join();
		running = false;
	}
}

void* ProcessRunner::main()
{
	using namespace processrunner;

	running = true;

	// FIXME: Whatever the man pages tell about wait, it seems that  all
	// process handling needs to be done in the same thread.  With the bad
	// consequence that we can't use wait to sleep, since we have to keep track
	// of other things happening >:-(((
	// The wait(2) manpage states that wait should also wait for the children
	// of other threads with 2.4 kernels, but I have 2.4.20 and it didn't seem
	// to work (or I had other problems and made a wrong observation).


	// Let the signals be caught by some other process
	sigset_t sigs, oldsigs;
	sigfillset(&sigs);
	sigdelset(&sigs, SIGFPE);
	sigdelset(&sigs, SIGILL);
	sigdelset(&sigs, SIGSEGV);
	sigdelset(&sigs, SIGBUS);
	sigdelset(&sigs, SIGABRT);
	sigdelset(&sigs, SIGIOT);
	sigdelset(&sigs, SIGTRAP);
	sigdelset(&sigs, SIGSYS);
	// Don't block sigchld: we need it
	//sigdelset(&sigs, SIGCHLD);
	pthread_sigmask(SIG_SETMASK, &sigs, &oldsigs);

	map<pid_t, ProcData> proclist;

	while (true)
	{
		debug("PRI Main Loop\n");
		bool want_shutdown = false;
		try {
			{
				// Wait for some child process to be run
				MutexLock lock(listenersMutex);

				debug("PRI Check Requested Shutdown\n");

				if (requested_shutdown)
				{
					debug("PRI Requested Shutdown\n");
					want_shutdown = true;
					requested_shutdown = false;
				} else {
					debug("PRI Run queued processes\n");
					while (!queuedForRunning.empty())
					{
						debug("PRI Run a queued process\n");
						ProcData d = queuedForRunning.front();
						queuedForRunning.pop();
						d.run();
						proclist.insert(make_pair(d.script->pid(), d));
						//debug("run process %s pid: %d\n", d.tag.c_str(), d.script->pid());
					}
				}
			}

			if (want_shutdown)
			{
				debug("PRI Perform shutdown\n");
				//debug("Requested shutdown\n");
				for (map<pid_t, ProcData>::iterator i = proclist.begin();
						i != proclist.end(); i++)
				{
					debug("PRI Perform shutdown of %s\n", i->second.tag.c_str());
					//debug("still running: %s (%d)\n", i->second.tag.c_str(), i->second.script->pid());
					i->second.script->kill(9);
					i->second.script->wait();
					delete i->second.script;
					debug("PRI Performed shutdown of %s\n", i->second.tag.c_str());
				}
				proclist.clear();
				debug("PRI Performed shutdown\n");
				return 0;
			}

			//fprintf(stderr, "There are %d processes\n", proclist.size());
			
			debug("PRI Checking Children\n");

			pid_t pid = 0;
			int status;

			if (!proclist.empty())
				pid = waitpid(-1, &status, WNOHANG);

			//debug("Wait gave %d\n", pid);

			if (pid == -1)
				throw wibble::exception::System("Waiting for a child to terminate");
			if (pid == 0)
			{
				/*
				sigset_t wsigs;
				sigemptyset(&wsigs);
				sigaddset(&wsigs, SIGCHLD);
				int sig;
				debug("sigwaiting\n");
				sigwait(&wsigs, &sig);
				debug("sigwaited\n");
				*/
				
				struct timespec sleep_time = { 0, 100000000 };
				nanosleep(&sleep_time, 0);
				
			}
			else
			{
				//debug("Process %d has terminated\n", pid);

				map<pid_t, ProcData>::iterator i = proclist.find(pid);
				if (i == proclist.end())
				{
					stringstream str;
					str << "Child pid " << pid << " exited, but has not been found in the child pid list";
					throw wibble::exception::Consistency(str.str());
				}

				//debug("Notifying termination of %s\n", i->second.tag.c_str());

				string tag = i->second.tag;
				
				// Notify the listener
				i->second.listener->handleTermination(tag, status);

				// Remove the process from the proclist
				delete i->second.script;
				proclist.erase(i);
			}
		} catch (std::exception& e) {
			error("%s\n", e.what());
		}
	}
}

// vim:set ts=4 sw=4:
