/*
 * 
 * Copyright (C) 2005-2006 NFG Net Facilities Group BV support@nfg.nl
 * Copyright (C) 2006 Aaron Stone aaron@serendipity.cx
 *
 *
 * pool.c Management of process pool
 * 
 * 
 * TODO:
 *
 * general: statistics bookkeeping
 * 
 * 
 */

#include "dbmail.h"
#define THIS_MODULE "server"

static volatile Scoreboard_t *scoreboard;
static int shmid;
static int sb_lockfd;
static FILE *scoreFD;

extern volatile sig_atomic_t GeneralStopRequested;
extern ChildInfo_t childinfo;

static void state_reset(child_state_t *child); 
static int set_lock(int type);
static pid_t reap_child(void);

/*
 *
 *
 * Scoreboard
 *
 *
 */

void state_reset(child_state_t *s)
{
	s->pid = -1;
	s->ctime = time(0);
	s->status = STATE_NOOP;
	s->count = 0;
	// FIXME: valgrind is complaining about s->user going 2 bytes past the structure.
	memset(s->client, '\0', 128);
	memset(s->user, '\0', 128);
}

int set_lock(int type)
{
	int result, serr;
	struct flock lock;
	static int retry = 0;
	lock.l_type = type; /* F_RDLCK, F_WRLCK, F_UNLOCK */
	lock.l_start = 0;
	lock.l_whence = 0;
	lock.l_len = 1;
	result = fcntl(sb_lockfd, F_SETLK, &lock);
	if (result == -1) {
		serr = errno;
		switch (serr) {
			case EACCES:
			case EAGAIN:
			case EDEADLK:
				if (retry++ > 2)
					TRACE(TRACE_WARNING, "Error setting lock. Still trying...");
				usleep(10);
				set_lock(type);
				break;
			default:
				// ignore the rest
				retry = 0;
				break;
		}
		errno = serr;
	} else {
		retry = 0;
	}
	return result;
}

void scoreboard_new(serverConfig_t * conf)
{
	int serr;
	if ((shmid = shmget(IPC_PRIVATE,
			(sizeof(child_state_t) * HARD_MAX_CHILDREN),
			0644 | IPC_CREAT)) == -1) {
		serr = errno;
		TRACE(TRACE_FATAL, "shmget failed [%s]",
				strerror(serr));
	}
	scoreboard = shmat(shmid, (void *) 0, 0);
	serr=errno;
	if (scoreboard == (Scoreboard_t *) (-1)) {
		TRACE(TRACE_FATAL, "scoreboard init failed [%s]",
		      strerror(serr));
		scoreboard_delete();
	}
	scoreboard_lock_new();
	scoreboard->conf = conf;
	scoreboard_setup();
	scoreboard_conf_check();

	/* Make sure that we clean up our shared memory segments when we exit
	 * normally (i.e. not by kill -9, if you do that, you get to clean this
	 * up yourself!)
	 * */
	atexit(scoreboard_delete); }

char * scoreboard_lock_getfilename(void)
{
	return g_strdup_printf("%s_%d.LCK", SCOREBOARD_LOCK_FILE, getpid());
}

void scoreboard_lock_new(void)
{
	gchar *statefile = scoreboard_lock_getfilename();
	if ( (sb_lockfd = open(statefile,O_EXCL|O_RDWR|O_CREAT|O_TRUNC,0600)) < 0) {
		TRACE(TRACE_FATAL, "Could not open lockfile [%s]", statefile);
	}
	g_free(statefile);
}

void scoreboard_setup(void) {	
	int i;
	scoreboard_wrlck();
	for (i = 0; i < HARD_MAX_CHILDREN; i++)
		state_reset((child_state_t *)&scoreboard->child[i]);
	scoreboard_unlck();
}

void scoreboard_conf_check(void)
{
	/* some sanity checks on boundaries */
	scoreboard_wrlck();
	if (scoreboard->conf->maxChildren > HARD_MAX_CHILDREN) {
		TRACE(TRACE_WARNING, "MAXCHILDREN too large. Decreasing to [%d]",
		      HARD_MAX_CHILDREN);
		scoreboard->conf->maxChildren = HARD_MAX_CHILDREN;
	} else if (scoreboard->conf->maxChildren < scoreboard->conf->startChildren) {
		TRACE(TRACE_WARNING, "MAXCHILDREN too small. Increasing to NCHILDREN [%d]",
		      scoreboard->conf->startChildren);
		scoreboard->conf->maxChildren = scoreboard->conf->startChildren;
	}

	if (scoreboard->conf->maxSpareChildren > scoreboard->conf->maxChildren) {
		TRACE(TRACE_WARNING, "MAXSPARECHILDREN too large. Decreasing to MAXCHILDREN [%d]",
		      scoreboard->conf->maxChildren);
		scoreboard->conf->maxSpareChildren = scoreboard->conf->maxChildren;
	} else if (scoreboard->conf->maxSpareChildren < scoreboard->conf->minSpareChildren) {
		TRACE(TRACE_WARNING, "MAXSPARECHILDREN too small. Increasing to MINSPARECHILDREN [%d]",
		      scoreboard->conf->minSpareChildren);
		scoreboard->conf->maxSpareChildren = scoreboard->conf->minSpareChildren;
	}
	scoreboard_unlck();
}

static unsigned scoreboard_cleanup(void)
{
	/* return the number of children still registered as being alive */
	unsigned count = 0;
	int i, status = 0;
	pid_t chpid = 0;
	for (i = 0; i < scoreboard->conf->maxChildren; i++) {
		
		scoreboard_rdlck();
		chpid = scoreboard->child[i].pid;
		status = scoreboard->child[i].status;
		scoreboard_unlck();
		
		if (chpid <= 0)
			continue;
		
		count++;
		
		if (status == STATE_WAIT) {
			if (waitpid(chpid, NULL, WNOHANG | WUNTRACED) == chpid)
				scoreboard_release(chpid);			
		}
	}
	return count;
}

void scoreboard_release(pid_t pid)
{
	int key;
	key = getKey(pid);

	if (key == -1) 
		return;
	
	scoreboard_wrlck();
	state_reset((child_state_t *)&scoreboard->child[key]);
	scoreboard_unlck();
}

void scoreboard_delete(void)
{
	gchar *statefile;
	extern int isGrandChildProcess;

	/* The middle process removes the scoreboards, so only bail out
	 * if we are a grandchild / connection handler process. */
	if (isGrandChildProcess)
		return;

	if (shmdt((const void *)scoreboard) == -1)
		TRACE(TRACE_ERROR, "detach shared mem failed");
	if (shmctl(shmid, IPC_RMID, NULL) == -1)
		TRACE(TRACE_ERROR, "delete shared mem segment failed");
	
	statefile = scoreboard_lock_getfilename();
	if (unlink(statefile) == -1) 
		TRACE(TRACE_ERROR, "error deleting scoreboard lock file [%s]", 
		      statefile);
	g_free(statefile);
	
	return;
}


int count_spare_children(void)
{
	int i, count;
	count = 0;
	
	scoreboard_rdlck();
	for (i = 0; i < scoreboard->conf->maxChildren; i++) {
		if (scoreboard->child[i].pid > 0
		    && scoreboard->child[i].status == STATE_IDLE)
			count++;
	}
	scoreboard_unlck();
	return count;
}

int count_children(void)
{
	int i, count;
	count = 0;
	
	scoreboard_rdlck();
	for (i = 0; i < scoreboard->conf->maxChildren; i++) {
		if (scoreboard->child[i].pid > 0)
			count++;
	}
	scoreboard_unlck();
	
	return count;
}

pid_t get_idle_spare(void)
{
	int i;
	pid_t idlepid = (pid_t) -1;
	/* get the last-in-first-out idle process */	
	scoreboard_rdlck();
	for (i = scoreboard->conf->maxChildren - 1; i >= 0; i--) {
		if ((scoreboard->child[i].pid > 0) && (scoreboard->child[i].status == STATE_IDLE)) {
			idlepid = scoreboard->child[i].pid;
			break;
		}
	}
	scoreboard_unlck();
	
	return idlepid;
}

int getKey(pid_t pid)
{
	int i;
	scoreboard_rdlck();
	for (i = 0; i < scoreboard->conf->maxChildren; i++) {
		if (scoreboard->child[i].pid == pid) {
			scoreboard_unlck();
			return i;
		}
	}
	scoreboard_unlck();
	TRACE(TRACE_ERROR, "pid NOT found on scoreboard [%d]", pid);
	return -1;
}

/*
 *
 *
 * Child
 *
 *
 */

int child_register(void)
{
	int i;
	pid_t chpid = getpid();
	
	TRACE(TRACE_MESSAGE, "register child [%d]", chpid);
	
	scoreboard_rdlck();
	for (i = 0; i < scoreboard->conf->maxChildren; i++) {
		if (scoreboard->child[i].pid == -1)
			break;
		if (scoreboard->child[i].pid == chpid) {
			TRACE(TRACE_ERROR, "child already registered.");
			scoreboard_unlck();
			return -1;
		}
	}
	scoreboard_unlck();
	
	if (i == scoreboard->conf->maxChildren) {
		TRACE(TRACE_WARNING, "no empty slot found");
		return -1;
	}
	
	scoreboard_wrlck();
	scoreboard->child[i].pid = chpid;
	scoreboard->child[i].status = STATE_IDLE;
	scoreboard_unlck();

	TRACE(TRACE_INFO, "initializing child_state [%d] using slot [%d]",
		chpid, i);
	return 0;
}

void child_reg_connected(void)
{
	int key;
	pid_t pid;
	
	if (! scoreboard) // cli server
		return;

	pid = getpid();
	key = getKey(pid);
	
	if (key == -1) 
		TRACE(TRACE_FATAL, "unable to find this pid on the scoreboard");
	
	scoreboard_wrlck();
	scoreboard->child[key].status = STATE_CONNECTED;
	scoreboard->child[key].count++;
	scoreboard_unlck();
}

void child_reg_connected_client(const char *ip, const char *name)
{
	int key;
	pid_t pid;
	
	if (! scoreboard) // cli server
		return;

	pid = getpid();
	key = getKey(pid);
	
	if (key == -1) 
		TRACE(TRACE_FATAL, "unable to find this pid on the scoreboard");
	
	scoreboard_wrlck();
	if (scoreboard->child[key].status == STATE_CONNECTED) {
		if (name && name[0])
			strncpy(scoreboard->child[key].client, name, 127);
		else
			strncpy(scoreboard->child[key].client, ip, 127);
	} else {
		TRACE(TRACE_MESSAGE, "client disconnected before status detail was logged");
	}
	scoreboard_unlck();
}

void child_reg_connected_user(char *user)
{
	int key;
	pid_t pid;
	
	if (! scoreboard) // cli server
		return;

	pid = getpid();
	key = getKey(pid);
	
	if (key == -1) 
		TRACE(TRACE_FATAL, "unable to find this pid on the scoreboard");
	
	scoreboard_wrlck();
	if (scoreboard->child[key].status == STATE_CONNECTED) {
		strncpy(scoreboard->child[key].user, user, 127);
	} else {
		TRACE(TRACE_MESSAGE, "client disconnected before status detail was logged");
	}
	scoreboard_unlck();
}

void child_reg_disconnected(void)
{
	int key;
	pid_t pid;
	
	pid = getpid();
	key = getKey(pid);

	if (key == -1) 
		TRACE(TRACE_FATAL, "unable to find this pid on the scoreboard");
	
	scoreboard_wrlck();
	scoreboard->child[key].status = STATE_IDLE;
	memset(scoreboard->child[key].client, '\0', 128);
	memset(scoreboard->child[key].user, '\0', 128);
	scoreboard_unlck();
}

void child_unregister(void)
{
	/*
	 *
	 * Set the state for this slot to WAIT
	 * so the parent process can do a waitpid()
	 *
	 */
	int key;
	pid_t pid;
	
	if (! scoreboard) // cli server
		return;

	pid = getpid();
	key = getKey(pid);
	
	if (key == -1)
		TRACE(TRACE_FATAL, "unable to find this pid on the scoreboard");
	
	scoreboard_wrlck();
	scoreboard->child[key].status = STATE_WAIT;
	scoreboard_unlck();
}


/*
 *
 *
 * Server
 *
 *
 */

/* Start the first batch of forked processes */
void manage_start_children(void)
{
	int i;
	for (i = 0; i < scoreboard->conf->startChildren; i++) {
		if (CreateChild(&childinfo) > -1)
			continue;
		manage_stop_children();
		TRACE(TRACE_FATAL, "could not create children.");
		exit(0);
	}

	scoreboard_state();
}

void manage_stop_children(void)
{
	/* 
	 *
	 * cleanup all remaining forked processes
	 *
	 */
	int alive = 0;
	int i, cnt = 0;
	pid_t chpid;
	
	TRACE(TRACE_MESSAGE, "General stop requested. Killing children...");

	for (i=0; i < scoreboard->conf->maxChildren; i++) {
		scoreboard_rdlck();
		chpid = scoreboard->child[i].pid;
		scoreboard_unlck();
		
		if (chpid < 0)
			continue;
		if (kill(chpid, SIGTERM))
			TRACE(TRACE_ERROR, "Cannot send SIGTERM to child [%d], error [%s]",
				(int)chpid, strerror(errno));
	}
	
	alive = scoreboard_cleanup();
	while (alive > 0 && cnt++ < 10) {
		alive = scoreboard_cleanup();
		sleep(1);
	}
	
	if (alive) {
		TRACE(TRACE_INFO, "[%d] children alive after SIGTERM, sending SIGKILL",
		      alive);

		for (i = 0; i < scoreboard->conf->maxChildren; i++) {
			scoreboard_rdlck();
			chpid = scoreboard->child[i].pid;
			scoreboard_unlck();
			
			if (chpid < 0)
				continue;
			kill(chpid, SIGKILL);;
			if (waitpid(chpid, NULL, WNOHANG | WUNTRACED) == chpid)
				scoreboard_release(chpid);
		}
	}
}

static pid_t reap_child(void)
{
	pid_t chpid=0;

	if ((chpid = get_idle_spare()) < 0)
		return 0; // no idle children

	if (kill(chpid, SIGTERM)) {
		TRACE(TRACE_ERROR, "Cannot send SIGTERM to child [%d], error [%s]",
			(int) chpid, strerror(errno));
		return -1;
	}
		
	return 0;
}

void manage_spare_children(void)
{
	/* 
	 *
	 * manage spare children while running. One child more/less for each run.
	 *
	 */
	pid_t chpid = 0;
	int spares, children;
	int changes = 0;
	int minchildren, maxchildren;
	int minspares, maxspares;
	
	if (GeneralStopRequested)
		return;

	// cleanup
	scoreboard_cleanup();

	children = count_children();
	spares = count_spare_children();
	
	/* scale up */
	minchildren = scoreboard->conf->startChildren;
	maxchildren = scoreboard->conf->maxChildren;
	minspares = scoreboard->conf->minSpareChildren;
	maxspares = scoreboard->conf->maxSpareChildren;

	if ((children < minchildren || spares < minspares) && children < maxchildren)  {
		if ((chpid = CreateChild(&childinfo)) < 0) 
			return;
		changes++;
	}
	/* scale down */
	else if (children > minchildren && spares > maxspares) {
		reap_child();
		changes++;
	}

	if (changes)
		scoreboard_state();

	children = count_children();
}

void scoreboard_state(void)
{
	char *state;
	int i;

	unsigned children = count_children();
	unsigned spares = count_spare_children();

	state = g_strdup_printf("Scoreboard state: children [%d/%d], spares [%d (%d - %d)]",
	      children,
	      scoreboard->conf->maxChildren,
	      spares,
	      scoreboard->conf->minSpareChildren,
	      scoreboard->conf->maxSpareChildren);

	/* Log it. */
	TRACE(TRACE_MESSAGE, "%s", state);

	/* Top it. */
	rewind(scoreFD);
	int printlen, scorelen = 0; // Tally up how much data has been written out.
	if ((printlen = fprintf(scoreFD, "%s\n", state)) <= 0
	  || !(scorelen += printlen)) {
		TRACE(TRACE_ERROR, "Couldn't write scoreboard state to top file [%s].",
			strerror(errno));
	}

	// Fixed 78 char output width.
	if ((printlen = fprintf(scoreFD, "%8s%8s%8s%8s%22s%22s\n\n",
		"Child", "PID", "Status", "Count", "Client", "User") <= 0)
	  || !(scorelen += printlen)) {
		TRACE(TRACE_ERROR, "Couldn't write scoreboard state to top file [%s].",
			strerror(errno));
	}

	for (i = 0; i < scoreboard->conf->maxChildren; i++) {
		int chpid, status;
		char *client, *user;
		unsigned count;
		
		scoreboard_rdlck();
		chpid = scoreboard->child[i].pid;
		status = scoreboard->child[i].status;
		count = scoreboard->child[i].count;
		client = scoreboard->child[i].client;
		user = scoreboard->child[i].user;
		scoreboard_unlck();

		// Matching 78 char fixed width as above.
		// Long hostnames may make the output a little messy.
		if ((printlen = fprintf(scoreFD, "%8d%8d%8d%8u%22s%22s\n",
			i, chpid, status, count, client, user)) <= 0
		  || !(scorelen += printlen)) {
			TRACE(TRACE_ERROR, "Couldn't write scoreboard state to top file [%s].",
				strerror(errno));
			break;
		}
	}
	scorelen += fprintf(scoreFD, "\n");
	fflush(scoreFD);
	ftruncate(fileno(scoreFD), scorelen);

	g_free(state);
}

static FILE *statefile_to_close;
static char *statefile_to_remove;

static void statefile_remove(void)
{
	int res;
	extern int isChildProcess;

	if (isChildProcess)
		return;

	if (statefile_to_close) {
		res = fclose(statefile_to_close);
		if (res) TRACE(TRACE_ERROR, "Error closing statefile: [%s].",
			strerror(errno));
		statefile_to_close = NULL;
	}

	if (statefile_to_remove) {
		res = unlink(statefile_to_remove);
		if (res) TRACE(TRACE_ERROR, "Error unlinking statefile [%s]: [%s].",
			statefile_to_remove, strerror(errno));
		g_free(statefile_to_remove);
		statefile_to_remove = NULL;
	}

}

void statefile_create(char *scoreFile)
{
	TRACE(TRACE_DEBUG, "Creating scoreboard at [%s].", scoreFile);
	if (!(scoreFD = fopen(scoreFile, "w"))) {
		TRACE(TRACE_ERROR, "Cannot open scorefile [%s], error was [%s]",
			scoreFile, strerror(errno));
	}
	chmod(scoreFile, 0644);
	if (scoreFD == NULL) {
		TRACE(TRACE_ERROR, "Could not create scoreboard [%s].", scoreFile );
	}

	atexit(statefile_remove);

	statefile_to_close = scoreFD;
	statefile_to_remove = g_strdup(scoreFile);
}



syntax highlighted by Code2HTML, v. 0.9.1