/*
** SellaNMS
** $Id: module.cxx,v 2.86 2005/05/21 19:23:26 bill Exp $
** Copyright (c) 2001-2005 Digital Genesis Software, LLC. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
*/

#ifdef HAVE_CONFIG_H
	#include "config.h"
#endif

#include "module.h"
#include "debug.h"
#include "template_func.h"
#include "storage/storage.h"
#include <time.h>
#include <dlfcn.h>
#include <errno.h>
#include <values.h>
#include <sys/file.h>
#include <sys/resource.h>
#include <stl/queue>
#include <stl/vector>

Module::Module():config(NULL), interrupt(false), scheduler_running(true), threadCount(0), uid(0), gid(0) {
	name = "module";
	stat_file = "";

	has_execute = false;
	has_scheduler = true;

	pthread_mutex_init(&modules_mutex, NULL);
	pthread_mutex_init(&threads_mutex, NULL);

	trace.config = trace.sql = trace.policy_sql = trace.policy_in = trace.policy_out = trace.thread = trace.timer = trace.internal = trace.debug = false;
}

Module::~Module() {
	/* Check if any libraries were not fully unloaded. This should no longer occur. */
	pthread_mutex_lock(&modules_mutex);
	if (modules.size() > 0) {
		LMSG("DEBUG: Module::~Module() modules.size(): %lu", modules.size());
		for (hash_map<string, module_t>::iterator it = modules.begin(); it != modules.end(); ++it) {
			LMSG("DEBUG: dlclose not called on '%s' (instance == %08x, handle == %08x) forcing it closed.", it->first.c_str(), it->second.instance, it->second.handle);
			if (it->second.handle) {
				dlclose(it->second.handle);
			}
		}
	}
	pthread_mutex_unlock(&modules_mutex);

	while (EBUSY == pthread_mutex_destroy(&threads_mutex)) {
		LMSG("Module mutex is locked. Waiting for unlock.");
		usleep(USLEEP_NORMLOOP_TIME);
	}

	while (EBUSY == pthread_mutex_destroy(&modules_mutex)) {
		LMSG("Module mutex is locked. Waiting for unlock.");
		usleep(USLEEP_NORMLOOP_TIME);
	}

	SAFE_DELETE(config);
	assert(threadCount == 0);
}

RESULT Module::readConfig() {

	return SUCCESS;
}

RESULT Module::readTraceOptions() {
	if (config->isNode("trace-options all")) { /* Everything except for 'internal'. */
		trace.config = trace.sql = trace.policy_sql = trace.policy_in = trace.policy_out = trace.thread = trace.timer = trace.debug = true;
	} else if (config->isNode("trace-options developer")) { /* Same as 'all', plus 'internal'. Very chatty. */
		trace.config = trace.sql = trace.policy_sql = trace.policy_in = trace.policy_out = trace.thread = trace.timer = trace.internal = trace.debug = true;
	} else {
		trace.config = config->isNode("trace-options config");
		trace.sql = config->isNode("trace-options sql");
		trace.policy_sql = config->isNode("trace-options policy-sql");
		if (!(trace.policy_in = trace.policy_out = config->isNode("trace-options policy"))) {
			trace.policy_in = config->isNode("trace-options policy-in");
			trace.policy_out = config->isNode("trace-options policy-out");
		}
		trace.thread = config->isNode("trace-options thread");
		trace.internal = config->isNode("trace-options internal");
		trace.timer = config->isNode("trace-options timer");
		trace.debug = config->isNode("trace-options debug");
	}

	/* Handle exceptions. */
	if (config->isNode("trace-options except")) {
		if (config->isNode("trace-options except config")) trace.config = false;
		if (config->isNode("trace-options except sql")) trace.sql = false;
		if (config->isNode("trace-options except policy-sql")) trace.policy_sql = false;
		if (config->isNode("trace-options except policy")) {
			trace.policy_in = trace.policy_out = false;
		} else {
			if (config->isNode("trace-options except policy-in")) trace.policy_in = false;
			if (config->isNode("trace-options except policy-out")) trace.policy_out = false;
		}
		if (config->isNode("trace-options except thread")) trace.thread = false;
		if (config->isNode("trace-options except internal")) trace.internal = false;
		if (config->isNode("trace-options except timer")) trace.timer = false;
		if (config->isNode("trace-options except debug")) trace.debug = false;
	}

	return SUCCESS;
}

RESULT Module::handleTraceOptions() {
	if (trace.config) {
		printConfig();
	}

	return SUCCESS;
}

RESULT Module::printConfig() {
	string str;
	vector<string> v;
	vector<string>::iterator v_it;

	if (config->assignValues("policy input", v)) {
		str.erase();
		for (v_it = v.begin(); v_it != v.end(); ++v_it) {
			str.append(*v_it + " ");
		}
		TRACE(TRACE_CONFIG, "policy input: %s", str.c_str());
	} else {
		TRACE(TRACE_CONFIG, "policy input: none");
	}

	if (config->assignValues("policy output", v)) {
		str.erase();
		for (v_it = v.begin(); v_it != v.end(); ++v_it) {
			str.append(*v_it + " ");
		}
		TRACE(TRACE_CONFIG, "policy output: %s", str.c_str());
	} else {
		TRACE(TRACE_CONFIG, "policy output: none");
	}

	return SUCCESS;
}

bool Module::schedulerRunning() {
	return (has_scheduler && scheduler_running);
}

RESULT Module::getModuleInstance(Module *&instance, string &module) {
	RESULT result = SUCCESS;
	hash_map<string, module_t>::iterator it;

	pthread_mutex_lock(&modules_mutex);
	it = modules.find(module);
	if (it == modules.end()) {
		LMSG("NOTICE: Request for instance of unknown module %s.", module.c_str());
		
		result = FAILURE;
	} else {
		instance = it->second.instance;
	}
	pthread_mutex_unlock(&modules_mutex);

	return result;
}

RESULT Module::loadModule(string &module, string &dso, bool resident) {
	char* error = NULL;
	module_t mod;
	hash_map<string, module_t>::iterator it;
	Module* (*get_instance_function)(void) = NULL;

	if (!resident) {
		mod.handle = dlopen(dso.c_str(), RTLD_NOW);
		if (mod.handle == NULL) {
			if ((error = dlerror()) != NULL) {
				LMSG("WARNING: dlopen() returned error: %s", error);
			}

			return FAILURE;
		}
	} else {
		/* Fetch the existing handle. */
		pthread_mutex_lock(&modules_mutex);
		it = modules.find(module);
		pthread_mutex_unlock(&modules_mutex);
		if (it == modules.end()) {
			LMSG("NOTICE: Request for resident module %s failed.", module.c_str());

			return FAILURE;
		} else {
			/* Handle should already be set. */
			assert(it->second.handle != NULL);
			/* Instance should have been set to NULL last time. */
			assert(it->second.instance == NULL);
			/* Assign previous module info to current. */
			mod = it->second;
		}
	}

	get_instance_function = (Module* (*)(void))dlsym(mod.handle, "getInstance");
	if ((error = dlerror()) != NULL) {
		LMSG("WARNING: dlsym returned error: %s", error);

		return FAILURE;
	}

	mod.instance = (*get_instance_function)();
	if (mod.instance == NULL) {
		LMSG("WARNING: Failed to get instance of module %s (DSO: %s).", module.c_str(), dso.c_str());

		return FAILURE;
	}

	pthread_mutex_lock(&modules_mutex);
	modules[module] = mod;
	pthread_mutex_unlock(&modules_mutex);

	return SUCCESS;
}

RESULT Module::unloadModule(string &module, bool resident) {
	RESULT result = SUCCESS;
	hash_map<string, module_t>::iterator it;

	pthread_mutex_lock(&modules_mutex);
	it = modules.find(module);
	if (it == modules.end()) {
		LMSG("NOTICE: Request for unload of unknown module %s.", module.c_str());

		result = FAILURE;
	} else {
		/* Stop using memory. */
		SAFE_DELETE(it->second.instance);

		if (!resident) {
			if (trace.internal) {
				LMSG("Module::unloadModule calling dlclose() on '%s'", module.c_str());
			}

			/* Close the library. */
			dlclose(it->second.handle);
			it->second.handle = NULL;

			/* Remove module from modules. */
			modules.erase(it);
		}
	}
	pthread_mutex_unlock(&modules_mutex);

	return result;
}

RESULT Module::createThread(pthread_t& thread_id, void* (*thread_func)(void*), void* args) {
	int err;

	if ((err = pthread_create(&thread_id, NULL, (void* (*)(void*))*thread_func, args)) == 0) {
		setThreadStatus(thread_id, THREAD_STARTING);
		threadCount++;

		/* Allow child time to startup. */
#ifdef _POSIX_PRIORITY_SCHEDULING
		sched_yield();
#else
		usleep(0);
#endif
	} else {
		thread_id = 0;
		switch (err) {
			case EAGAIN:
				LMSG("WARNING: Not enough system resources to create a process for the new thread. (Module: %s)", this->name.c_str());
				break;

			default:
				LMSG("WARNING: Unable to create a process for new thread. Error (%d) returned. (Module: %s)", err, this->name.c_str());
		}
		return FAILURE;
	}

	return SUCCESS;
}

RESULT Module::killThreadAll() {
	/* Interrupt all loops. */
	this->interrupt = true;

	/* Kill child module threads. */
	for (hash_map<string, module_t>::iterator it = modules.begin(); it != modules.end(); ++it) {
		/* Kill our child module's threads, if there are any. */
		if (it->second.instance != NULL) {
			it->second.instance->killThreadAll();
		}
	}

	/* Kill local threads. */
	pthread_mutex_lock(&threads_mutex);
	for (hash_map<pthread_t, RESULT>::iterator it = threads.begin(); it != threads.end(); ++it) {
		/* Don't overwrite THREAD_COMPLETED. */
		if (THREAD_KILLED > it->second) {
			it->second = THREAD_KILLED;
		}
	}
	pthread_mutex_unlock(&threads_mutex);

	return SUCCESS;
}

RESULT Module::killThread(pthread_t thread_id) {
	if (thread_id == 0) {
		LMSG("WARNING: Request to kill thread 0 in module '%s'.", this->name.c_str());
	} else {
		setThreadStatus(thread_id, THREAD_KILLED);
	}

	return SUCCESS;
}

RESULT Module::setThreadStatus(pthread_t thread_id, STATUS status) {
	RESULT result = SUCCESS;
	hash_map<pthread_t, RESULT>::iterator it;

	if (thread_id == 0) {
		LMSG("WARNING: Attempt to set status on thread 0 in module '%s'.", this->name.c_str());

		result = FAILURE;
	} else {
		pthread_mutex_lock(&threads_mutex);
		it = threads.find(thread_id);
		if (it == threads.end()) { /* No status, just set it. */
			threads[thread_id] = status;
		} else if (status > it->second) { /* Only allow status escalation. (ie: UNKNOWN->STARTING->RUNNING->KILLED->COMPLETED) */
			threads[thread_id] = status;
		}
		pthread_mutex_unlock(&threads_mutex);
	}

	return result;
}

RESULT Module::getThreadResult(pthread_t thread_id, RESULT &thread_result) {
	RESULT result = FAILURE;
	RESULT *t_result;

	int pthread_result;
	hash_map<pthread_t, RESULT>::iterator it;
	
	if (thread_id == 0) {
		LMSG("WARNING: Request for result from thread 0 in module '%s'.", this->name.c_str());

		result = FAILURE;
		thread_result = FAILURE;
	} else {
		pthread_result = pthread_join(thread_id, (void**) &t_result);

		/* SANITY CHECK: t_result was probably passed by value, rather than reference or a pointer. Fix the caller. */
		assert((int) t_result != 1);

		/* Save memory: Remove thread from hash. */
		pthread_mutex_lock(&threads_mutex);
		it = threads.find(thread_id);
		if (it == threads.end()) {
			LMSG("NOITCE: Didn't locate thread %d within hash. Did you pass a bad thread_id to getThreadResult()?", thread_id);
		} else {
			threads.erase(it);
		}
		pthread_mutex_unlock(&threads_mutex);

		/* Copy return value from thread, and delete memory allocated by thread. */
		if (t_result != NULL) {
			/* Sanity check. If the return value isn't SUCCESS or FAILURE, its damaged and probably isn't allocated. */
			if (*t_result == SUCCESS || *t_result == FAILURE) {
				thread_result = *t_result;
				delete t_result; /* Allocated as part of THREAD_RESULT(x), within defs.h. */
			} else {
				LMSG("DEBUG: *t_result is %d within module '%s'. It should be 0 or 1.", *t_result, this->name.c_str());
				thread_result = FAILURE;
			}
		} else {
			thread_result = FAILURE;
		}

		switch (pthread_result) {
			case 0:
				result = SUCCESS;
				threadCount--;
				break;
			case ESRCH:
				LMSG("WARNING: [ESRCH] Unknown thread %d", (long) thread_id);
				break;
			case EINVAL:
				LMSG("WARNING: [EINVAL] Already Deteched or is already waiting for %d", (long) thread_id);
				break;
			case EDEADLK:
				LMSG("WARNING: [EDEADLK] Calling thread %d matches thread to join %d", pthread_self(), thread_id);
				break;
			default:
				LMSG("WARNING: Unknown thread error for %d", thread_id);
		}
	}

	return result;
}

RESULT Module::getThreadStatus(pthread_t thread_id, STATUS &status) {
	RESULT result = SUCCESS;

	hash_map<pthread_t, RESULT>::iterator it;

	if (thread_id == 0) {
		LMSG("WARNING: Request for status on thread 0 in module '%s'.", this->name.c_str());

		status = THREAD_UNKNOWN;
		result = FAILURE;
	} else {
		pthread_mutex_lock(&threads_mutex);
		it = threads.find(thread_id);
		if (it == threads.end()) {
			/* Thread can be unknown because it hasn't registered itself as RUNNING yet. */
			status = THREAD_UNKNOWN;
		} else {
			status = it->second;
		}
		pthread_mutex_unlock(&threads_mutex);
	}

	return result;
}

RESULT Module::setConfig(ConfigNode *node) {
	/* FIXME: Convert config into a non-pointer. */
	this->config = node;

	return SUCCESS;
}

RESULT Module::setUid(uid_t uid) {
	this->uid = uid;

	return SUCCESS;
}

RESULT Module::setGid(gid_t gid) {
	this->gid = gid;

	return SUCCESS;
}

RESULT Module::setStatFile(string stat_file) {
	this->stat_file = stat_file;

	return SUCCESS;
}

RESULT Module::grantPriv() {
	/* If uid and gid are not root (0) */
	if (getuid() != 0 && (this->uid == 0 || this->gid == 0)) {
		LMSG("WARNING: Non-root user attempting to set UID or GID to 0 (root).");
		return FAILURE;
	}

	if (trace.internal) {
		LMSG("%s setting owner to uid 0 gid 0", this->name.c_str());
	}

	if (setegid(0)) {
		LMSG("WARNING: Failed to set GID to '%d' in '%s'.", this->gid, this->name.c_str());
		return FAILURE;
	} else if (seteuid(0)) {
		LMSG("WARNING: Failed to set UID to '%d' in %s'.", this->uid, this->name.c_str());
		return FAILURE;
	}

	return SUCCESS;
}

RESULT Module::dropPriv() {
	/* If uid and gid are not root (0) */
	if (getuid() != 0 && (this->uid == 0 || this->gid == 0)) {
		LMSG("WARNING: Non-root user attempting to set UID %d and GID %d.", this->uid, this->gid);
		return FAILURE;
	}

	if (trace.internal) {
		LMSG("%s setting owner to uid %d gid %d", this->name.c_str(), this->uid, this->gid);
	}

	if (setegid(this->gid)) {
		LMSG("WARNING: Failed to set GID to '%d' in '%s'.", this->gid, this->name.c_str());
		return FAILURE;
	} else if (seteuid(this->uid)) {
		LMSG("WARNING: Failed to set UID to '%d' in %s'.", this->uid, this->name.c_str());
		return FAILURE;
	}

	return SUCCESS;
}

RESULT Module::childStart(Module *child_module) {
	return SUCCESS;
}

RESULT Module::childStop(Module *child_module) {
	return SUCCESS;
}

RESULT Module::start(pthread_t &scheduler_id, pthread_t &execute_id) {
	RESULT result = SUCCESS;

	/* Handles scheduling sub modules. */
	if (has_scheduler) {
		if (createThread(scheduler_id, (void* (*)(void*))Module::scheduler_thread, (void*) this) != SUCCESS) {
			scheduler_running = false;
			result = FAILURE;
		}
	} else {
		scheduler_id = 0;
	}

	/* Runs modules code. Also calls readConfig() for module. */
	if (has_execute) {
		if (createThread(execute_id, (void* (*)(void*))Module::execute_thread, (void*) this) != SUCCESS) {
			result = FAILURE;
		}
	} else {
		execute_id = 0;
	}

	return result;
}

RESULT Module::startModule(mod_attr_t &mod_attr) {
	RESULT result = SUCCESS;
	ConfigNode *mod_config = NULL, *sub_node = NULL, *modules_node = NULL, *global_node = NULL;
	string str;

	if (mod_attr.next_start > 0 && time(NULL) > mod_attr.next_start + MODULE_SCHEDULER_SLIP_THRESHOLD) {
		LMSG("NOTICE: Scheduler slip of %d sec (%s %s).", time(NULL) - mod_attr.next_start, this->name.c_str(), mod_attr.name.c_str());
	}

	/* Set the last start time. Used to track run time. */
	mod_attr.last_start = time(NULL);

	/* Load the module. */
	if (mod_attr.resident && mod_attr.order < 0) { /* Resident at once iteration is complete. */
		if (this->loadModule(mod_attr.name, mod_attr.dso, true) != SUCCESS) {
			LMSG("WARNING: Failed to load '%s'.", mod_attr.dso.c_str());

			return FAILURE;
		}
	} else {
		if (this->loadModule(mod_attr.name, mod_attr.dso, false) != SUCCESS) {
			LMSG("WARNING: Failed to load '%s'.", mod_attr.dso.c_str());

			return FAILURE;
		}
	}

	/* Grab an instance of the module. */
	if (this->getModuleInstance(mod_attr.ptr, mod_attr.name) != SUCCESS) {
		LMSG("WARNING: Failed to getInstance() of module '%s'.", mod_attr.dso.c_str());

		return FAILURE;
	}

	/* Build the local configuration for this module. */
	mod_config = NULL;
	if (!this->config->assignNode(mod_attr.name, mod_config)) {
		mod_config = new ConfigNode();
		LMSG("NOTICE: Failed to assign config for module '%s %s'. Check for missing configuration.", this->name.c_str(), mod_attr.name.c_str());
	}

	if (modules_node != NULL) {
		LMSG("modules_node wasn't null");
		abort();
	}

	this->config->assignNode("system global", global_node);
	this->config->assignNode("system modules " + mod_attr.name, modules_node);
	if (global_node || modules_node) {
		sub_node = new ConfigNode();
		if (global_node) {
			sub_node->addNode("global", global_node);
			SAFE_DELETE(global_node);
		}
		if (modules_node) {
			sub_node->addNode("modules", modules_node);
			SAFE_DELETE(modules_node);
		}
		mod_config->addNode("system", sub_node);
		SAFE_DELETE(sub_node);
	}

	if (this->config->assignNode("policy-options", sub_node)) {
		mod_config->addNode("policy-options", sub_node);
		SAFE_DELETE(sub_node);
	}

	/* Set required module variables for Config, Storage Modules, Stat File, UID/GID. */
	if (mod_attr.ptr->setConfig(mod_config) != SUCCESS) {
		LMSG("ERROR: Failed to set config for module '%s %s'", this->name.c_str(), mod_attr.name.c_str());

		return FAILURE;
	}

	if (mod_attr.ptr->setStatFile(stat_file) != SUCCESS) {
		LMSG("WARNING: Failed to set stat file for module.");
	}

	/* Set UID/GID */
	if (mod_attr.ptr->setUid(this->uid) != SUCCESS) {
		LMSG("WARNING: Failed to set UID (%d) for module '%s %s'.", this->uid, this->name.c_str(), mod_attr.name.c_str());
	}
	if (mod_attr.ptr->setGid(this->gid) != SUCCESS) {
		LMSG("WARNING: Failed to set GID (%d) for module '%s %s'.", this->gid, this->name.c_str(), mod_attr.name.c_str());
	}

	/* Trigger childStart Hook. */
	if (childStart(mod_attr.ptr) != SUCCESS) {
		LMSG("WARNING: Hook childStart() returned in FAILURE.");

		return FAILURE;
	}

	/* Startup the thread/process */
	if (trace.thread)
		LMSG("EXEC: [%s] %s (Order %d) ", this->name.c_str(), mod_attr.name.c_str(), mod_attr.order);

	if (mod_attr.thread) {
		if (mod_attr.ptr->start(mod_attr.scheduler_id, mod_attr.execute_id) != SUCCESS) {
			LMSG("ERROR: Failed to execute module thread '%s %s' (Order %d).", this->name.c_str(), mod_attr.name.c_str(), mod_attr.order);

			return FAILURE;
		}
	} else {
		LMSG("NOTICE: The \"process\" option has not yet been implemented. Running as a thread instead.");
		if (mod_attr.ptr->start(mod_attr.scheduler_id, mod_attr.execute_id) != SUCCESS) {
			LMSG("ERROR: Failed to execute module process '%s %s' (Order %d).", this->name.c_str(), mod_attr.name.c_str(), mod_attr.order);

			return FAILURE;
		}
	}

	return SUCCESS;
}

RESULT Module::stopModule(mod_attr_t &mod_attr) {
	RESULT t_result;

	/* Shutdown the thread/process */
	if (trace.thread)
		LMSG("FINI: [%s] %s (Order %d) [%d sec]", this->name.c_str(), mod_attr.name.c_str(), mod_attr.order, time(NULL) - mod_attr.last_start);

	if (mod_attr.ptr != NULL) {
		/* Trigger childStop Hook. */
		if (childStop(mod_attr.ptr) != SUCCESS) {
			LMSG("WARNING: Hook childStop() returned in FAILURE.");
		}

		/* Get scheduler thread result */
		if (mod_attr.scheduler_id != 0) {
			mod_attr.ptr->getThreadResult(mod_attr.scheduler_id, t_result);
			if (t_result != SUCCESS) {
				LMSG("NOTICE: Module '%s %s' scheduler thread returned in error (%d).", this->name.c_str(), mod_attr.name.c_str(), t_result); 
				/* Adjust the next start, to keep the module from constantly trying to restart. */
				mod_attr.next_start = (mod_attr.next_start > 0) ? mod_attr.next_start + mod_attr.timeout : time(NULL) + mod_attr.timeout;
			}
		}

		/* Get execute thread result */
		if (mod_attr.execute_id != 0) {
			mod_attr.ptr->getThreadResult(mod_attr.execute_id, t_result);
			if (t_result != SUCCESS) {
				LMSG("NOTICE: Module '%s %s' execute thread returned in error (%d).", this->name.c_str(), mod_attr.name.c_str(), t_result); 
				/* Adjust the next start, to keep the module from constantly trying to restart. */
				mod_attr.next_start = (mod_attr.next_start > 0) ? mod_attr.next_start + mod_attr.timeout : time(NULL) + mod_attr.timeout;
			}
		}
	}

	if (this->unloadModule(mod_attr.name, mod_attr.resident) != SUCCESS) {
		LMSG("WARNING: Failed to unload module '%s %s'", this->name.c_str(), mod_attr.name.c_str());

		return FAILURE;
	}

	/* Clear ptr to instance of module. Its invalid now. */
	mod_attr.ptr = NULL;

	return SUCCESS;
}

RESULT Module::execute() {
	/* NOTE: This is actually never called. */
	return SUCCESS;
}

RESULT Module::scheduler() {
	RESULT result = SUCCESS;
	STATUS scheduler_status, execute_status;

	long current_order, lowest_order;
	list<mod_attr_t> wait_list;
	list<mod_attr_t> active_block_list;
	list<mod_attr_t> active_nonblock_list;
	list<mod_attr_t>::iterator it;

	if ((result = buildModuleList(wait_list)) == FAILURE) {
		return result;
	}

	current_order = 0;
	while ((!interrupt && !wait_list.empty()) || !active_block_list.empty() || !active_nonblock_list.empty()) {
		/* Scan wait_list for modules that should be started. */
		lowest_order = MAXLONG;
		it = wait_list.begin();
		while (!interrupt && it != wait_list.end()) {
			/* Grab the lowest blocking order still in the list. */
			if (it->order > 0 && it->order < lowest_order) {
				lowest_order = it->order;
			}

			/* Is this module less than current order? */
			if (it->order > current_order) {
				++it;
				continue;
			}

			/* Should this module be started yet? */
			if (it->next_start <= time(NULL)) {
				if (startModule(*it) != SUCCESS) {
					LMSG("NOTICE: Removing '%s' from this iteration.", it->name.c_str());
				} else {
					/* Move to blocking or nonblocking active list. */
					if (it->block) {
						active_block_list.push_front(*it);
					} else {
						/* Set next mod_attr->next_start based on the cron/delay settings. */
						setNextStart(*it);
						active_nonblock_list.push_front(*it);
					}
				}

				/* Remove from wait_list. */
				it = wait_list.erase(it);
			} else {
				++it;
			}
		}
		/* Move to next order if there are none left at the current. */
		if (active_block_list.empty() && lowest_order > current_order) {
			current_order = lowest_order;
		}

		/* Scan active_block_list for any that are completed. */
		it = active_block_list.begin();
		while (it != active_block_list.end()) {
			assert(it->ptr != NULL);
			/* Stop and remove completed module. */
			if (it->scheduler_id != 0) {
				it->ptr->getThreadStatus(it->scheduler_id, scheduler_status);
			}
			if (it->execute_id != 0) {
				it->ptr->getThreadStatus(it->execute_id, execute_status);
			}
			if ((it->execute_id == 0 || execute_status == THREAD_COMPLETED) && (it->scheduler_id == 0 || scheduler_status == THREAD_COMPLETED)) {
				stopModule(*it);

				/* Remove from active_block_list. */
				it = active_block_list.erase(it);
			} else {
				++it;
			}
		}
		
		/* Scan active_nonblock_list for any that are completed. */
		it = active_nonblock_list.begin();
		while (it != active_nonblock_list.end()) {
			assert(it->ptr != NULL);
			/* Stop and move completed modules to wait_list. */
			if (it->scheduler_id != 0) {
				it->ptr->getThreadStatus(it->scheduler_id, scheduler_status);
			}
			if (it->execute_id != 0) {
				it->ptr->getThreadStatus(it->execute_id, execute_status);
			}
			if ((it->execute_id == 0 || execute_status == THREAD_COMPLETED) && (it->scheduler_id == 0 || scheduler_status == THREAD_COMPLETED)) {
				stopModule(*it);

				/* Set order negative. Order means nothing, once a non-blocking module is started. */
				it->order = -1;

				/* Insert module in wait_list. */
				wait_list.push_front(*it);

				/* Remove from active_nonblock_list. */
				it = active_nonblock_list.erase(it);
			} else {
				++it;
			}
		}

		/* Wait for a bit... just a guess right now. */
		usleep(USLEEP_NORMLOOP_TIME);
	}

	/* Cleanup */
	
	/* SANITY CHECK: Main scheduler loop guarentees all active_*_list modules are unloaded or moved to wait_list at this point. */
	assert(active_block_list.empty());
	assert(active_nonblock_list.empty());

	/* Scan active_nonblock_list for any that are completed. */
	it = wait_list.begin();
	while (it != wait_list.end()) {
		if (it->ptr == NULL && it->resident) { /* Unload resident deallocated modules. */
			if (unloadModule(it->name, false) != SUCCESS) {
				LMSG("Failed to manually unload %s", it->name.c_str());
			}
		} else if (it->ptr) { /* Loaded module. */
			it->resident = false;
			if (stopModule(*it) != SUCCESS) {
				LMSG("Failed to manually stop %s", it->name.c_str());
			}
		} else { /* Unloaded non-resident module. */
			//LMSG("Module '%s' was waiting for restart. Already unloaded and deallocated.", it->name.c_str());
		}

		/* Remove from wait_list. */
		it = wait_list.erase(it);
	}

	return result;
}

RESULT Module::scheduler_thread(void *args) {
	RESULT result;

	pthread_t thread_id = pthread_self();
	Module *instance = (Module*) args;

	/* Set module running status. */
	instance->setThreadStatus(thread_id, THREAD_RUNNING);

	/* Set our traceoptions. */
	//instance->readTraceOptions();

	/* This contains the meat. */
	result = instance->scheduler();

	/* Set module completed status. */
	instance->setThreadStatus(thread_id, THREAD_COMPLETED);
	
	/* Mark scheduler as completed. */
	instance->scheduler_running = false;

	THREAD_RETURN(result);
}

RESULT Module::execute_thread(void *args) {
	RESULT result;

	pthread_t thread_id = pthread_self();
	Module *instance = (Module*) args;

	/* Set module status. */
	instance->setThreadStatus(thread_id, THREAD_RUNNING);

	/* This contains the meat. */
	if ((result = instance->readConfig()) == SUCCESS) {
		instance->readTraceOptions();
		instance->handleTraceOptions();

		result = instance->execute();
	}

	instance->setThreadStatus(thread_id, THREAD_COMPLETED);

	THREAD_RETURN(result);
}

RESULT Module::setNextStart(mod_attr_t& mod_attr) {
	RESULT result = SUCCESS;
	struct tm go_tm;
	char buf[132], *ptr, *end;

	if (mod_attr.startup) {
		mod_attr.next_start = time(NULL) + (time_t) mod_attr.delay;

		/* Toggle startup off, so we don't use it again. */
		mod_attr.startup = false;
		
		/* Zero delay if there is a cron available. */
		if (mod_attr.cron.empty()) {
			mod_attr.delay = 0;
		}
	} else if (mod_attr.delay > 0 && mod_attr.cron.empty()) {
		mod_attr.next_start = time(NULL) + (time_t) mod_attr.delay;

	} else if (!mod_attr.cron.empty()) {
		mod_attr.next_start = time(NULL) + 1;
		localtime_r(&(mod_attr.next_start), &go_tm);

		bzero(buf, 132);
		strncpy(buf, mod_attr.cron.c_str(), 131);
		ptr = end = buf;
		for (int i = 0; i < 6 && end != NULL; i++) {
			if (end = strchr(ptr, ' ')) {
				*end = '\0';
			}

			switch (i) {
				case 0: /* second */
					go_tm.tm_sec = cronAdj(go_tm.tm_sec, 60, ptr);
					break;
				case 1: /* minute */
					go_tm.tm_min = cronAdj(go_tm.tm_min, 60, ptr);
					break;
				case 2: /* hour */
					go_tm.tm_hour = cronAdj(go_tm.tm_hour, 24, ptr);
					break;
				case 3: /* dayofmonth */
					go_tm.tm_mday = cronAdj(go_tm.tm_mday + 1, 31, ptr) - 1;
					break;
				case 4: /* month */
					go_tm.tm_mon = cronAdj(go_tm.tm_mon + 1, 12, ptr) - 1;
					break;
				case 5: /* dayofweek - Special since mktime ignores this one. */
					go_tm.tm_mday += cronAdj(go_tm.tm_wday, 7, ptr) - go_tm.tm_wday;
					break;
			}

			/* Normalize the structure, so things don't get whacked. */
			mktime(&go_tm);

			/* Increment to next token. */
			ptr = end + 1;
		}

		/* Set next start based on cron + requested delay. */
		mod_attr.next_start = mktime(&go_tm) + (time_t) mod_attr.delay;

		/* Zero out delay so we don't use it again. */
		mod_attr.delay = 0;
	} else {
		mod_attr.next_start = 0;
	}

	return result;
}

long Module::cronAdj(long val, long val_max, char *str) {
	char *ptr;
	long ret_val, tmp, tmp2;

	assert(str != NULL);

	if (!strcmp(str, "*")) { /* All values. */
		ret_val = val;
	} else if (!strncmp(str, "*/", 2)) { /* Every X interval. */
		tmp = atol(str + 2);
		ret_val = (tmp > 0 && val % tmp > 0) ? (val + tmp) - (val % tmp) : val;
	} else if ((ptr = index(str, '-')) != NULL && str[0] != '-') { /* Ranged */
		ptr++;
		tmp = atol(str);
		tmp2 = atol(ptr);

		if (val >= tmp && val <= tmp2) {
			ret_val = val;
		} else if (val < tmp) {
			ret_val = tmp;
		} else {
			ret_val = val_max + tmp;
		}
	} else if ((ptr = index(str, ',')) != NULL && str[0] != ',') { /* Comma seperated. */
		*ptr = '\0';
		tmp = atol(str);
		tmp2 = val_max;
		do {
			/* Grab the first thats greater than val. */
			if (tmp >= val) {
				ret_val = tmp;
				break;
			}

			/* Track smallest value. */
			if (tmp < tmp2)
				tmp2 = tmp;

			/* Get next available time. */
			if ((ptr = index(ptr + 1, ',')) != NULL) {
				*ptr = '\0';
				tmp = atol(ptr + 1);
			} else {
				ret_val = val_max + tmp2;
			}
		} while (ptr);
	} else { /* Raw number: Set occurance of passed time. */
		tmp = atol(str);
		ret_val = (val > tmp) ? val_max + tmp : tmp;
	}

	assert(ret_val >= 0);

	return ret_val;
}


RESULT Module::buildModuleList(list<mod_attr_t>& mod_list) {
	string base, req, str;
	vector<string> v;
	mod_attr_t mod_attr;

	/* Get all available modules to load. */
	if (!this->config->assignValues("system modules", v)) {
		LMSG("NOTICE: No modules to load for '%s'.", this->name.c_str());
		return FAILURE;
	}

	/* Convert vector of modules into a list of mod_attr_t */
	for (vector<string>::iterator v_it = v.begin(); v_it != v.end(); ++v_it) {
		base = "system modules ";
		base.append(*v_it);

		/* Skip if disabled. */
		req = base + " disable";
		if (this->config->isNode(req)) {
			continue;
		}

		/* Skip if there is no load statement. */
		req = base + " load";
		if (!this->config->assignValue(req, mod_attr.dso)) {
			continue;
		}

		req = base + " order";
		this->config->assignValue(req, mod_attr.order, DEFAULT_MODULE_ORDER);

		/* Configure generic settings. */
		mod_attr.name = *v_it;
		mod_attr.ptr = NULL;

		/* Configure scheduling settings. */
		req = base + " schedule cron";
		if (!this->config->assignValue(req, mod_attr.cron)) {
			/* Contruct a cron line, if needed. */
			req = base + " schedule second";
			if (this->config->assignValue(req, mod_attr.cron)) {
				req = base + " schedule minute";
				this->config->assignValue(req, str, "*");
				mod_attr.cron.append(" ");
				mod_attr.cron.append(str + " ");

				req = base + " schedule hour";
				this->config->assignValue(req, str, "*");
				mod_attr.cron.append(str + " ");

				req = base + " schedule dayofmonth";
				this->config->assignValue(req, str, "*");
				mod_attr.cron.append(str + " ");

				req = base + " schedule month";
				this->config->assignValue(req, str, "*");
				mod_attr.cron.append(str + " ");

				req = base + " schedule dayofweek";
				this->config->assignValue(req, str, "*");
				mod_attr.cron.append(str);
			}
		}

		req = base + " schedule delay";
		this->config->assignValue(req, mod_attr.delay, DEFAULT_MODULE_DELAY);

		req = base + " schedule timeout";
		this->config->assignValue(req, mod_attr.timeout, DEFAULT_MODULE_TIMEOUT);

		req = base + " schedule startup";
		mod_attr.startup = this->config->isNode(req);

		/* Configure module options. */
		if (mod_attr.cron.size() > 0) { /* Scheduler, then default to non-blocking. */
			req = base + " options blocking";
			mod_attr.block = this->config->isNode(req);
		} else { /* No scheduler, then default to blocking. */
			req = base + " options non-blocking";
			mod_attr.block = !this->config->isNode(req);
		}

		req = base + " options thread";
		mod_attr.thread = this->config->isNode(req);

		req = base + " options process";
		mod_attr.thread = !this->config->isNode(req);

		/* Resident is only allowed when non-blocking. */
		if (mod_attr.block) {
			mod_attr.resident = false;
		} else {
			req = base + " options non-resident";
			mod_attr.resident = !this->config->isNode(req);

			req = base + " options resident";
			mod_attr.resident = this->config->isNode(req);
		}

		/* Set mod_attr->next_start based on the cron/delay settings. */
		setNextStart(mod_attr);

		/* Insert mod_attr into mod_list. */
		mod_list.push_back(mod_attr);
	}

	/* Release our vector. */
	v.clear();

	return SUCCESS;
}

/*
** Local Variables:
** c-basic-offset: 3
** tab-width: 3
** End:
** vim600: fdm=marker
** vim: noet ts=3 sw=3
*/
