/*
** SellaNMS
** $Id: watchdog.cxx,v 1.31 2005/12/01 03:28:59 sella Exp $
** Copyright (c) 2001-2005 Digital Genesis Software, LLC. All Rights Reserved.
** Released under the GPL Version 2 License.
** http://www.digitalgenesis.com
*/

static const char rcsid[] = "$Id: watchdog.cxx,v 1.31 2005/12/01 03:28:59 sella Exp $";

#include "daemon.h"
#include "watchdog.h"
#include "common/ip_util.h"
#include "storage/storage.h"
#include <fcntl.h>
#include <netdb.h>
#include <ctype.h>
#include <signal.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/poll.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <linux/if.h>
#include <vector>
#include <errno.h>

#define BUF_SIZE 2024

extern ConfigNode* config;
extern bool interrupt;

Watchdog::Watchdog() {
}

Watchdog::~Watchdog() {
	/* Free the daemons. */
	for (slist<struct daemon_t*>::iterator it = daemons.begin(); it != daemons.end(); ++it) {
		delete *it;
	}
	daemons.clear();
}

RESULT Watchdog::readConfig() {
	RESULT result = SUCCESS;
	char buf[512];
	string str;
	vector<string> v, dv;
	vector<string>::iterator v_it, dv_it;
	ConfigNode node, subnode;
	daemon_t *d;
	struct stat stat_buf;

	config->assignNode("system default", node);

	/* Read default daemon options. */
	memset(&daemonDefault, 0, sizeof(daemon_t));
	node.assignValue("nice", daemonDefault.nice, WATCHDOG_NICE_DEF);
	node.assignValue("order", daemonDefault.order, 0);
	node.assignValue("restart-limit", daemonDefault.restart_limit, WATCHDOG_RESTARTLIMIT_DEF, WATCHDOG_RESTARTLIMIT_MIN, WATCHDOG_RESTARTLIMIT_MAX);
	daemonDefault.disable = node.isNode("disable");
	node.assignValue("launch-delay", daemonDefault.launch_delay, WATCHDOG_LAUNCHDELAY_DEF, WATCHDOG_LAUNCHDELAY_MIN, WATCHDOG_LAUNCHDELAY_MAX, WATCHDOG_LAUNCHDELAY_MLT);
	daemonDefault.startup = node.isNode("schedule startup");
	node.assignValue("schedule delay", daemonDefault.delay, WATCHDOG_DELAY_DEF, WATCHDOG_DELAY_MIN, WATCHDOG_DELAY_MAX);
	readCron(node, daemonDefault.cron, sizeof(daemonDefault.cron));

	if (node.isNode("no-restart")) {
		daemonDefault.restart = false;
	} else {
		daemonDefault.restart = true;
	}

	if (node.assignValue("user", str)) {
		strncpy(daemonDefault.user, str.c_str(), sizeof(daemonDefault.user));
	}

	if (node.assignValue("group", str)) {
		strncpy(daemonDefault.group, str.c_str(), sizeof(daemonDefault.group));
	}

	if (node.assignValue("directory", str)) {
		strncpy(daemonDefault.dir, str.c_str(), sizeof(daemonDefault.dir));
	} else {
		strncpy(daemonDefault.dir, ".", sizeof(daemonDefault.dir));
	}

	if (node.assignValue("environment", str)) {
		strncpy(daemonDefault.env, str.c_str(), sizeof(daemonDefault.env));
	}

	if (subnode.assignValue("config", str)) {
		strncpy(daemonDefault.config_file, str.c_str(), sizeof(daemonDefault.config_file));
	}

	if (node.assignValue("executable", str)) {
		strncpy(daemonDefault.executable, str.c_str(), sizeof(daemonDefault.executable));
	}

	if (node.assignValue("description", str)) {
		strncpy(daemonDefault.description, str.c_str(), sizeof(daemonDefault.description));
	}

	/* Build list of daemons. */
	config->assignValues("", v);
	for (v_it = v.begin(); v_it != v.end(); ++v_it) {
		/* Ignore the system section. */
		if (!v_it->compare("system") || !v_it->compare("policy-options")) {
			continue;
		}

		config->assignValues(*v_it, dv);
		for (dv_it = dv.begin(); dv_it != dv.end(); ++dv_it) {
			d = new daemon_t;
			memset(d, 0, sizeof(daemon_t));
			strncpy(d->parent, v_it->c_str(), sizeof(d->parent));
			snprintf(d->config_path, sizeof(d->config_path), "%s %s", v_it->c_str(), dv_it->c_str());

			config->assignNode(*v_it, subnode);

			/* Check for disable keyword at parents level. */
			d->disable = subnode.isNode("disable");

			/* Check for launch_delay keyword at parents level. */
			subnode.assignValue("launch-delay", d->launch_delay, daemonDefault.launch_delay);

			/* Check for delay keyword at parents level. */
			subnode.assignValue("schedule delay", d->delay, daemonDefault.delay);

			/* Check for startup keyword at parents level. */
			d->startup = subnode.isNode("schedule startup");

			/* Attempt to pull scheduler from parents level. */
			if (!readCron(subnode, d->cron, sizeof(d->cron))) {
				strncpy(d->cron, daemonDefault.cron, sizeof(d->cron));
			}

			config->assignNode(d->config_path, subnode);

			/* Use local scheduler, if available. */
			readCron(subnode, d->cron, sizeof(d->cron));

			if (subnode.assignValue("directory", str)) {
				strncpy(d->dir, str.c_str(), sizeof(d->dir));
			} else {
				strcpy(d->dir, daemonDefault.dir);
			}

			if (subnode.assignValue("environment", str)) {
				strncpy(d->env, str.c_str(), sizeof(d->env));
			} else {
				strcpy(d->env, daemonDefault.env);
			}

			if (subnode.assignValue("executable", str)) {
				strncpy(d->executable, str.c_str(), sizeof(d->executable));
			} else {
				strcpy(d->executable, daemonDefault.executable);
			}

			if (!d->disable && !(d->disable = subnode.isNode("disable"))) {
				d->disable = daemonDefault.disable;
			}

			/* Verify this is a valid daemon, before continuing on. */
			if (strlen(d->executable) < 1) { /* No executable specified, or isn't a module config. */
				TRACE(TRACE_INTERNAL, "executable not configured for '%s %s' (skipping)", v_it->c_str(), dv_it->c_str());

				SAFE_DELETE(d);
				continue;
			} else if (d->disable) { /* Disabled daemon. */
				LMSG("module '%s' disabled", d->config_path);

				SAFE_DELETE(d);
				continue;
			} else {
				snprintf(buf, sizeof(buf), "%s/%s", d->dir, d->executable);
				if (stat(buf, &stat_buf) == 0) {
					if (!S_ISREG(stat_buf.st_mode)) {
						/* Disallow non-regular files as daemons. */
						LMSG("executable '%s' for '%s' is not a regular file (disabling)", buf, d->config_path);

						SAFE_DELETE(d);
						continue;
					}
				} else {
					/* Not a valid filename. */
					LMSG("executable '%s' for '%s' was not found (disabling)", buf, d->config_path);

					SAFE_DELETE(d);
					continue;
				}
			}

			if (subnode.assignValue("config", str)) {
				strncpy(d->config_file, str.c_str(), sizeof(d->config_file));
			} else if (strlen(daemonDefault.config_file) > 0) {
				strcpy(d->config_file, daemonDefault.config_file);
			} else {
				strncpy(d->config_file, DEFAULT_CONFIG_FILE, sizeof(d->config_file));
			}

			if (subnode.assignValue("pid-file", str)) {
				strncpy(d->pid_file, str.c_str(), sizeof(d->pid_file));
			}

			if (subnode.assignValue("stat-file", str)) {
				strncpy(d->stat_file, str.c_str(), sizeof(d->stat_file));
			}

			if (subnode.assignValue("description", str)) {
				strncpy(d->description, str.c_str(), sizeof(d->description));
			} else {
				strcpy(d->description, daemonDefault.description);
			}

			if (subnode.assignValue("user", str)) {
				strncpy(d->user, str.c_str(), sizeof(d->user));
			} else {
				strcpy(d->user, daemonDefault.user);
			}

			if (subnode.assignValue("group", str)) {
				strncpy(d->group, str.c_str(), sizeof(d->group));
			} else {
				strcpy(d->group, daemonDefault.group);
			}

			if (!subnode.assignValue("nice", d->nice)) {
				d->nice = daemonDefault.nice;
			}

			subnode.assignValue("order", d->order, daemonDefault.order);

			subnode.assignValue("restart-limit", d->restart_limit, daemonDefault.restart_limit);

			subnode.assignValue("schedule delay", d->delay, d->delay);

			if (!d->startup && !(d->startup = subnode.isNode("schedule startup"))) {
				d->startup = daemonDefault.startup;
			}

			if (subnode.isNode("no-restart")) {
				d->restart = false;
			} else {
				d->restart = daemonDefault.restart;
			}

			/* Set the initial start time. */
			d->start = nextStartTime(d);

			daemons.push_front(d);
		}
	}

#ifdef USE_STORAGE
	/* Configure Storage */
	if (storage.setConfig(*config) != SUCCESS) {
		result = FAILURE;
	}
#endif

	printConfig();

	return result;
}

bool Watchdog::readCron(ConfigNode &node, char *cron, size_t len) {
	int count = 0;
	string str, buf;

	if (!(count = (int) node.assignValue("schedule cron", buf))) {
		count = (int) node.assignValue("schedule second", str, "*");
		buf.append(str + " ");

		count += (int) node.assignValue("schedule minute", str, "*");
		buf.append(str + " ");

		count += (int) node.assignValue("schedule hour", str, "*");
		buf.append(str + " ");

		count += (int) node.assignValue("schedule dayofmonth", str, "*");
		buf.append(str + " ");

		count += (int) node.assignValue("schedule month", str, "*");
		buf.append(str + " ");

		count += (int) node.assignValue("schedule dayofweek", str, "*");
		buf.append(str);
	}

	if (count > 0) {
		strncpy(cron, buf.c_str(), len);
		return true;
	}

	return false;
}

void Watchdog::printConfig() {
	string str;
	vector<string> v;
	vector<string>::iterator v_it;

	slist<struct daemon_t*>::iterator it;

	if (!TRACE_ISSET(TRACE_CONFIG)) {
		return;
	}

	TRACE(TRACE_CONFIG, "default nice: %lu", daemonDefault.nice);
	TRACE(TRACE_CONFIG, "default order: %lu", daemonDefault.order);
	TRACE(TRACE_CONFIG, "default launch-delay: %lu sec", daemonDefault.launch_delay / WATCHDOG_LAUNCHDELAY_MLT);
	TRACE(TRACE_CONFIG, "default restart-limit: %lu", daemonDefault.restart_limit);
	TRACE(TRACE_CONFIG, "default disable: %s", daemonDefault.disable == false ? "false" : "true");
	TRACE(TRACE_CONFIG, "default schedule delay: %lu sec", daemonDefault.delay);
	TRACE(TRACE_CONFIG, "default schedule startup: %lu", daemonDefault.startup);
	TRACE(TRACE_CONFIG, "default no-restart: %s", daemonDefault.restart == false ? "false" : "true");
	TRACE(TRACE_CONFIG, "default user: %s", daemonDefault.user);
	TRACE(TRACE_CONFIG, "default group: %s", daemonDefault.group);
	TRACE(TRACE_CONFIG, "default directory: %s", daemonDefault.dir);
	TRACE(TRACE_CONFIG, "default environment: %s", daemonDefault.env);
	TRACE(TRACE_CONFIG, "default config: %s", daemonDefault.config_file);
	TRACE(TRACE_CONFIG, "default executable: %s", daemonDefault.executable);
	TRACE(TRACE_CONFIG, "default description: %s", daemonDefault.description);
	TRACE(TRACE_CONFIG, "default facility: %s", daemonDefault.facility);

	return;
}

RESULT Watchdog::execute() {
	RESULT result = SUCCESS;

	pid_t pid;
	time_t now;
	int status;
	hash_map<string, long> order;
	slist<struct daemon_t*>::iterator it;

	TRACE(TRACE_THREAD, "Watchdog::execute() starting");

	/* Initialize order structure to 0 for all parents. */
	for (it = daemons.begin(); it != daemons.end(); ++it) {
		order[(*it)->parent] = 0;
	}

	while (!interrupt && daemons.size() > 0) {
		now = time(NULL);

		/* Print out all daemon states. */
		if (TRACE_ISSET(TRACE_DUMP)) {
			TRACE(TRACE_DUMP, "[daemon states]");
			for (it = daemons.begin(); it != daemons.end(); ++it) {
				TRACE(TRACE_DUMP, "daemon: %s order: %d state: %d start: %d", (*it)->executable, (*it)->order, (*it)->state, (*it)->start);
			}
		}

		/* Examine RUNNING daemons */
		for (it = daemons.begin(); it != daemons.end(); ++it) {
			/* Identify failed daemons. */
			if ((*it)->state == STATE_RUNNING) {
				if (!checkRunning(*it)) {
					TRACE(TRACE_DEBUG, "%s is no longer running", (*it)->executable);

					(*it)->state = STATE_IDLE;
				} else if (!checkWarn(*it)) {
					LMSG("%s has reached warning level", (*it)->executable);
				} else if (!checkKill(*it)) {
					LMSG("%s has reached kill level", (*it)->executable);

					if (!killDaemon(*it)) {
						LMSG("failed to kill %s pid %d", (*it)->executable, (*it)->pid);
					} else {
						(*it)->state = STATE_IDLE;
					}
				}
			}
		}

		/* Update order */
		for (hash_map<string, long>::iterator h_it = order.begin(); h_it != order.end(); ++h_it) {
			bool order_done = true;
			int order_count = 0, order_new = MAXINT;
			TRACE(TRACE_INTERNAL, "parent '%s' moved to order %d", h_it->first.c_str(), h_it->second);

			/* Verify all daemons (for this parent) at the current order are IDLE. */
			for (it = daemons.begin(); it != daemons.end(); ++it) {
				if (!h_it->first.compare((*it)->parent) && h_it->second == (*it)->order && h_it->second > 0) {
					if ((*it)->state != STATE_DISABLED) {
						order_count++;

						/* Check for non-IDLE daemons. */
						if ((*it)->state != STATE_IDLE) {
							order_done = false;
						}
					}
				}
			}

			/* If the order was complete, or there are no daemons at this order. */
			if (order_done || order_count < 1) {
				/* Find next order, for this parent. */
				for (it = daemons.begin(); it != daemons.end(); ++it) {
					if (!h_it->first.compare((*it)->parent) && (*it)->state == STATE_IDLE) {
						if ((*it)->order > h_it->second && (*it)->order < order_new) {
							order_new = (*it)->order;
						}
					}
				}

				if (order_new == MAXINT) {
					h_it->second = 0;
				} else {
					h_it->second = order_new;
				}
			}
		}

		/* Update daemon state. Move from IDLE to PENDING or DISABLED. */
		for (it = daemons.begin(); it != daemons.end(); ++it) {
			if ((*it)->state == STATE_IDLE) {
				if ((*it)->disable) { /* Daemon is confiugred as disabled */
					(*it)->state = STATE_DISABLED;
				} else if ((*it)->restart && (*it)->restart_count >= (*it)->restart_limit) { /* restart limit reached */
					(*it)->state = STATE_DISABLED;
				} else if (!(*it)->restart && (*it)->restart_count >= 1) { /* non-restartable daemon */
					(*it)->state = STATE_DISABLED;
				} else if ((*it)->order == 0 || (*it)->order == order[(*it)->parent]) { /* If order 0 or current order, consider for PENDING state. */
					(*it)->state = STATE_PENDING;
				}
			}
		}

		/* Start all PENDING daemons. */
		for (it = daemons.begin(); it != daemons.end(); ++it) {
			/* If pending, and scheduled to start, start daemon. */
			if ((*it)->state == STATE_PENDING && now >= (*it)->start) {
				(*it)->restart_count++;
				(*it)->start = nextStartTime(*it); /* Set next start time. */

				if (((*it)->pid = launchDaemon(*it)) > 0) {
					LMSG("%s pid %d started", (*it)->executable, (*it)->pid);

					(*it)->state = STATE_RUNNING;

					/* Provide daemon a delay before starting the next. */
					usleep((*it)->launch_delay);
				} else {
					LMSG("failed to start %s (disabling)", (*it)->executable);

					(*it)->state = STATE_DISABLED;
				}
			}
		}

		if (!interrupt) {
			usleep(WATCHDOG_CHECK_INTERVAL);
		}
	}

	/* Cleanup children. */
	LMSG("asking children to shutdown (waiting up to %d sec)...", WATCHDOG_CLEANUP_DELAY);

#if 1
	killAllDaemons(SIGTERM);
	time_t wait = time(NULL) + WATCHDOG_CLEANUP_DELAY;
	while ((pid = waitpid(0, &status, WNOHANG)) != -1 && wait >= time(NULL)) {
		usleep(USLEEP_NORMLOOP_TIME);
	}

	if (pid != -1) {
		LMSG("slaughtering children that failed to shutdown...");
		killAllDaemons(SIGKILL);
	}
#else
	#error "This doesn't work correctly... fix me."
	killAllDaemons(SIGTERM);
	time_t wait = time(NULL) + WATCHDOG_CLEANUP_DELAY;
	while (wait >= time(NULL)) {
		for (it = daemons.begin(); it != daemons.end(); ++it) {
			if ((*it)->state == STATE_RUNNING && !checkRunning(*it)) {
				LMSG("%s pid %d has shutdown", (*it)->executable, (*it)->pid);
				(*it)->state = STATE_IDLE;
			}
		}
		usleep(USLEEP_NORMLOOP_TIME);
	}

	int count = 0;
	for (it = daemons.begin(); it != daemons.end(); ++it) {
		if ((*it)->state == STATE_RUNNING) {
			LMSG("%s (%d) failed to shutdown", (*it)->executable, (*it)->pid);
			count++;
		}
	}

	if (count > 0) {
		LMSG("slaughtering children that failed to shutdown...");
		killAllDaemons(SIGKILL);
	}
#endif

	TRACE(TRACE_THREAD, "Watchdog::execute() exiting");

	return result;
}

pid_t Watchdog::launchDaemon(const daemon_t *d) {
	pid_t pid;
	int err, arg = 0;
	char cmd[512], *args[32], buf[255], var[64], val[64], *toka, *tokb, *pa, *pb;

	snprintf(buf, sizeof(buf), "%s", basename(d->executable));
	args[arg++] = strdup(buf);
	args[arg++] = strdup("-f");

	if (d->nice != 0) {
		snprintf(buf, sizeof(buf), "%lu", d->nice);
		args[arg++] = strdup("-n");
		args[arg++] = strdup(buf);
	}

	if (d->umask != 0) {
		snprintf(buf, sizeof(buf), "%lu", d->umask);
		args[arg++] = strdup("-m");
		args[arg++] = strdup(buf);
	}

	if (strlen(d->user) > 0) {
		args[arg++] = strdup("-u");
		args[arg++] = strdup(d->user);
	}

	if (strlen(d->group) > 0) {
		args[arg++] = strdup("-g");
		args[arg++] = strdup(d->group);
	}

	if (strlen(d->config_file) > 0) {
		args[arg++] = strdup("-c");
		args[arg++] = strdup(d->config_file);
	}

	if (strlen(d->config_path) > 0) {
		args[arg++] = strdup("-C");
		args[arg++] = strdup(d->config_path);
	}

	if (strlen(d->pid_file) > 0) {
		args[arg++] = strdup("-p");
		args[arg++] = strdup(d->pid_file);
	}

	if (strlen(d->stat_file) > 0) {
		args[arg++] = strdup("-s");
		args[arg++] = strdup(d->stat_file);
	}

	if (strlen(d->chroot) > 0) {
		args[arg++] = strdup("-r");
		args[arg++] = strdup(d->chroot);
	}

	if (strlen(d->facility) > 0) {
		args[arg++] = strdup("-F");
		args[arg++] = strdup(d->facility);
	}

	if (strlen(d->log_file) > 0) {
		args[arg++] = strdup("-l");
		args[arg++] = strdup(d->log_file);
	}

	if (d->debug != 0) {
		snprintf(buf, sizeof(buf), "%lu", d->debug);
		args[arg++] = strdup("-d");
		args[arg++] = strdup(buf);
	}

	args[arg] = NULL;

	/* Base command line. */
	snprintf(cmd, sizeof(cmd), "%s/%s", d->dir, d->executable);

	TRACE(TRACE_DEBUG, "executing: '%s'", cmd);
	if (TRACE_ISSET(TRACE_DEBUG)) {
		TRACE(TRACE_DEBUG, "[arguments]");
		for (int i = 0; i < arg; i++) {
			TRACE(TRACE_DEBUG, " arg[%d]: %s", i, args[i]);
		}
	}

	if ((pid = fork()) == 0) {
#if 0
		fclose(stdin); /* Will no longer accept input (ie: Ctrl-C) */
		fclose(stdout); /* Our output to stdout will go unheard. */
		fclose(stderr); /* Our output to stderr will go unheard. */
#endif

		/* Setup the environment. */
		strncpy(buf, d->env, sizeof(buf));
		for (toka = strtok_r(buf, ",;: \t", &pa); toka != NULL; toka = strtok_r(NULL, ",;: \t", &pa)) {
			strncpy(var, toka, sizeof(var));

			if ((tokb = strtok_r(var, "=", &pb)) != NULL) {
				if ((tokb = strtok_r(NULL, ";", &pb)) != NULL) {
					strncpy(val, tokb, sizeof(val));
				} else {
					val[0] = '\0';
				}

				TRACE(TRACE_DEBUG, "setting env variable '%s' to '%s'", var, val);
				if (setenv(var, val, 1) != 0) {
					LMSG("Failed to set env variable '%s' to '%s'", var, val);
				}
			}
      }

		if ((err = execv(cmd, args)) == -1) {
			LMSG("failed to execute '%s'", cmd);

			for (int i = 0; i < arg; i++) {
				SAFE_FREE(args[i]);
			}

			exit(EXIT_FAILURE);
		}

		/* execv should never return. */
		abort();
	} else if (pid == -1) {
		LMSG("failed to fork() - aborting");
		abort();
	}

	/* Free args memory. */
	for (int i = 0; i < arg; i++) {
		SAFE_FREE(args[i]);
	}

	return pid;
}

time_t Watchdog::nextStartTime(daemon_t *d) {
	struct tm go_tm;
	time_t next_start;
	char buf[64], *ptr, *end;

	if (d->startup) {
		d->startup = false; /* Only use 'startup' once. */
		next_start = 0;
	} else if (strlen(d->cron) >= 11) { /* Minimum valid cron is 11 chars. */
      next_start = time(NULL);
      localtime_r(&(next_start), &go_tm);

      memset(buf, 0, sizeof(buf));
      strncpy(buf, d->cron, sizeof(buf));
      ptr = end = buf;
      for (int i = 0; i < 6 && end != NULL; i++) {
         if ((end = strchr(ptr, ' ')) != NULL) {
            *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. */
         if (mktime(&go_tm) == -1) {
				LMSG("invalid schedule '%s' - using delay", d->cron);
				
				d->cron[0] = '\0'; /* Wipe cron. */
				next_start = time(NULL) + d->delay;

				return next_start;
			}

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

      /* Set next start based on cron. */
      next_start = 1 + mktime(&go_tm) + d->delay;

      /* Zero out delay so we don't use it again. */
      d->delay = 0;
	} else if (d->delay > 0 && d->restart_count > 0) {
		next_start = time(NULL) + d->delay;
	} else {
		next_start = 0;
	}

	return next_start;
}

void Watchdog::killAllDaemons(int sig) {
	slist<struct daemon_t*>::iterator it;
	for (it = daemons.begin(); it != daemons.end(); ++it) {
		if ((*it)->state == STATE_RUNNING) {
			kill((*it)->pid, sig);
		}
	}
}

bool Watchdog::checkRunning(const daemon_t *d) {
	pid_t pid;
	int status = 0;

	if (d->pid < 1) {
		LMSG("invalid pid passed to checkRunning()");
		return false;
	}

	pid = waitpid(d->pid, &status, WNOHANG);
	if (pid == -1) {
		switch (errno) {
			case EINVAL:
				LMSG("invalid option specified");
				break;

			case ECHILD:
				LMSG("process does not exist (or isn't mine)");
				break;

			default:
				LMSG("unknown error");
		}

		return false;
	} else if (pid > 0) {
		if (WIFEXITED(status)) {
			LMSG("%s pid %d exited with status %d", d->executable, d->pid, WEXITSTATUS(status));
#ifdef WCOREDUMP
		} else if (WCOREDUMP(status)) {
			if (WIFSIGNALED(status)) {
				LMSG("%s pid %d exited with a core dump (signal %d)", d->executable, d->pid, WTERMSIG(status));
			} else {
				LMSG("%s pid %d exited with a core dump", d->executable, d->pid);
			}
#endif
		} else if (WIFSTOPPED(status)) {
			LMSG("%s pid %d has stopped with signal %d", d->executable, d->pid, WSTOPSIG(status));

		} else if (WIFSIGNALED(status)) {
			LMSG("%s pid %d exited due to uncaught signal %d", d->executable, d->pid, WTERMSIG(status));
		} else {
			LMSG("%s pid %d exited (unknown reason - status %d)", d->executable, d->pid, status);
		}

		return false;
	}

	TRACE(TRACE_INTERNAL, "%s pid %d still running", d->executable, d->pid);

	return true;
}

bool Watchdog::checkWarn(const daemon_t *d) {
	return true;
}

bool Watchdog::checkKill(const daemon_t *d) {
	return true;
}

bool Watchdog::killDaemon(const daemon_t *d) {
	return true;
}

long Watchdog::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;
}

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