btmux-0.6-rc4/doc/
btmux-0.6-rc4/event/
btmux-0.6-rc4/game/
btmux-0.6-rc4/game/maps/
btmux-0.6-rc4/game/mechs/
btmux-0.6-rc4/game/text/help/
btmux-0.6-rc4/game/text/help/cat_faction/
btmux-0.6-rc4/game/text/help/cat_inform/
btmux-0.6-rc4/game/text/help/cat_misc/
btmux-0.6-rc4/game/text/help/cat_mux/
btmux-0.6-rc4/game/text/help/cat_mux/cat_commands/
btmux-0.6-rc4/game/text/help/cat_mux/cat_functions/
btmux-0.6-rc4/game/text/help/cat_templates/
btmux-0.6-rc4/game/text/wizhelp/
btmux-0.6-rc4/include/
btmux-0.6-rc4/misc/
btmux-0.6-rc4/python/
btmux-0.6-rc4/src/hcode/btech/
btmux-0.6-rc4/tree/
/*
 * sqlchild.c
 *
 * Copyright (c) 2004,2005 Martin Murray <mmurray@monkey.org>
 * All rights reserved.
 *
 */

#include "copyright.h"
#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dbi/dbi.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>

#include "externs.h"

/* /TODO
 * String sanitization.
 *  - dbi_driver_quote_string_copy ?
 * 
 * Handle Timezones in DATETIME
 *
 * Handle final write() failing.
 * 
 * Correct handling of TEXT data type.
 */

#define DEBUG_SQL
#ifdef DEBUG_SQL
#ifndef DEBUG
#define DEBUG
#endif
#endif
#include "debug.h"

#ifdef SQL_SUPPORT
#define MAX_QUERIES 8

static int running_queries = 0;
dbi_conn conn = NULL;

#define DBIS_EFAIL -1
#define DBIS_READY 0
#define DBIS_RESOURCE 1

static int dbi_initialized = 0;
static int dbi_state;
static int query_counter = 0;
static int recent = 0;

static struct query_state_t {
	dbref thing;
	int attr;
	char *preserve;
	char *query;
	struct event ev;
	char *rdelim;
	char *cdelim;
	struct query_state_t *next;
	int serial;
	struct timeval start;
	char slot;
	int fd;
	int pid;
} *running = NULL, *pending = NULL, *pending_tail = NULL, *recent_head =
	NULL, *recent_tail = NULL;

static struct timeval query_timeout = { 10, 0 };

void sqlchild_init();
void sqlchild_destruct();
static void sqlchild_kill_all();
int sqlchild_kill(int requestId);
void sqlchild_list(dbref thing);
static void sqlchild_kill_query(struct query_state_t *aqt);
static void sqlchild_child_abort_query(struct query_state_t *aqt,
									   char *error);
static void sqlchild_child_abort_query_dbi(struct query_state_t *aqt,
										   char *error);
static void sqlchild_child_execute_query(struct query_state_t *aqt);
static void sqlchild_finish_query(int fd, short events, void *arg);
static void sqlchild_check_queue();
int sqlchild_request(dbref thing, int attr, char slot, char *pres,
					 char *query, char *rdelim, char *cdelim);

void sqlchild_init()
{
	dbi_driver *driver;

	if(dbi_initialized)
		return;

	dprintk("initializing sqlchild.");

	if(dbi_initialize(NULL) == -1) {
		dprintk("dbi_initialized() failed.");
		dbi_state = DBIS_EFAIL;
		return;
	}

	dprintk("libdbi started.");

	driver = dbi_driver_list(NULL);
	while (driver != NULL) {
		dprintk("libdbi driver '%s' ready.", dbi_driver_get_name(driver));
		driver = dbi_driver_list(driver);
	}

	dbi_state = DBIS_READY;
}

void sqlchild_destruct()
{
	dprintk("shutting down.");
	dbi_state = DBIS_EFAIL;
	sqlchild_kill_all();
	dbi_shutdown();
}

/* do_query entrypoint */
int sqlchild_request(dbref thing, int attr, char slot, char *pres,
					 char *query, char *rdelim, char *cdelim)
{
	int fds[2];
	struct query_state_t *aqt;

	if(dbi_state < DBIS_READY)
		return -1;

	aqt = malloc(sizeof(struct query_state_t));
	aqt->thing = thing;
	aqt->attr = attr;
	aqt->preserve = strdup(pres);
	aqt->query = strdup(query);
	aqt->rdelim = strdup(rdelim);
	aqt->cdelim = strdup(cdelim);
	aqt->serial = query_counter++;
	aqt->slot = slot;

	if(pending == NULL) {
		aqt->next = NULL;
		pending = aqt;
		pending_tail = aqt;
	} else {
		pending_tail->next = aqt;
		aqt->next = NULL;
		pending_tail = aqt;
	}
	sqlchild_check_queue();
	return 1;
}

void sqlchild_kill_all()
{
	dprintk("shutting down.\n");
	if(!dbi_initialized)
		return;
	while (running) {
		sqlchild_kill_query(running);
	}
}

int sqlchild_kill(int requestId)
{
	int status;
	struct query_state_t *iter = NULL, *aqt = NULL;

	dprintk("received request to terminate %d", requestId);

	if(running)
		iter = running;
	while (iter)
		if(iter->serial == requestId) {
			sqlchild_kill_query(iter);
			return 1;
		} else
			iter = iter->next;
	if(pending)
		iter = pending;
	while (iter)
		if(iter->serial == requestId) {
			sqlchild_kill_query(iter);
			return 1;
		} else
			iter = iter->next;
	return 0;
}

static void sqlchild_kill_query(struct query_state_t *aqt)
{
	struct query_state_t *iter;

	dprintk("terminating query %d", aqt->serial);

	kill(aqt->pid, SIGTERM);
	waitpid(aqt->pid, NULL, 0);

	iter = running;
	if(running == aqt) {
		running = aqt->next;
	} else {
		while (iter) {
			if(iter->next == aqt) {
				iter->next = aqt->next;
				break;
			}
			iter = iter->next;
		}
	}
	if(aqt->preserve)
		free(aqt->preserve);
	if(aqt->query)
		free(aqt->query);
	if(aqt->rdelim)
		free(aqt->rdelim);
	if(aqt->cdelim)
		free(aqt->cdelim);
	close(aqt->fd);
	free(aqt);
	running_queries--;
	sqlchild_check_queue();
};

void sqlchild_list(dbref thing)
{
	int nactive = 0, npending = 0;
	int nrecent = recent;
	struct query_state_t *aqt;
	notify(thing, "/--------------------------- Recent Queries");
	if(recent) {
		aqt = recent_head;
		while (aqt) {
			notify_printf(thing, "%08d #%8d %40s", aqt->serial, aqt->thing,
						  aqt->query);
			aqt = aqt->next;
		}
	}
	notify(thing, "/--------------------------- Running Queries");
	if(running) {
		aqt = running;
		while (aqt) {
			notify_printf(thing, "%08d #%8d %40s", aqt->serial, aqt->thing,
						  aqt->query);
			aqt = aqt->next;
			nactive++;
		}
	} else {
		notify(thing, "- No active queries.");
	}
	notify(thing, "/--------------------------- Pending Queries");
	if(pending) {
		aqt = pending;
		while (aqt) {
			notify_printf(thing, "%08d #%-8d %40s", aqt->serial, aqt->thing,
						  aqt->query);
			aqt = aqt->next;
			npending++;
		}
	} else {
		notify(thing, "- No pending queries.");
	}
	notify_printf(thing, "%d active and %d pending queries.", nactive,
				  pending);
}

struct query_response {
	int status;
	int n_chars;
};

/* HOST FUNCTIONS */

static void sqlchild_finish_query(int fd, short events, void *arg)
{
	pid_t pchild;
	int status;
	char *argv[5];
	struct query_state_t *aqt = (struct query_state_t *) arg, *iter;
	struct query_response resp = { -1, 0 };
	char buffer[LBUF_SIZE];
	buffer[0] = '\0';

	dprintk("receiving response for query %d", aqt->serial);

	if(read(aqt->fd, &resp, sizeof(struct query_response)) < 0) {
		log_perror("SQL", "FAIL", NULL, "sqlchild_finish_query");
		argv[0] = "-1";
		argv[1] = "";
		argv[3] = "serious braindamage";
		goto fail;

	}

	if(resp.n_chars >= LBUF_SIZE) {
		resp.n_chars = LBUF_SIZE - 1;
	}

	if(resp.n_chars) {
		if(read(aqt->fd, buffer, resp.n_chars) < 0) {
			log_perror("SQL", "FAIL", NULL, "sqlchild_finish_query");
			argv[0] = "-1";
			argv[1] = "";
			argv[3] = "serious braindamage";
			goto fail;
		}
	}

	if(resp.status == 0) {
		argv[0] = "1";
		argv[1] = buffer;
		argv[3] = "Success";
	} else {
		argv[0] = "0";
		argv[1] = "";
		if(resp.n_chars) {
			argv[3] = buffer;
		} else {
			argv[3] = "minor braindamage, no error and no result reported.";
		}
	}

  fail:
	argv[2] = aqt->preserve;
	did_it(GOD, aqt->thing, 0, NULL, 0, NULL, aqt->attr, argv, 4);

  hardfail:
	if(running == aqt) {
		running = aqt->next;
	} else {
		iter = running;
		while (iter) {
			if(iter->next == aqt) {
				iter->next = aqt->next;
				break;
			}
			iter = iter->next;
		}
	}

	close(aqt->fd);
	dprintk("waiting on %d.", aqt->pid);
	pchild = waitpid(aqt->pid, &status, WNOHANG);
	if(pchild) {
		dprintk("%d exited normally.", aqt->pid);
	}
	recent++;
	if(recent_tail == NULL) {
		aqt->next = NULL;
		recent_head = recent_tail = aqt;
	} else {
		recent_tail->next = aqt;
		recent_tail = aqt;
	}

	if(recent > 20) {
		aqt = recent_head;
		recent_head = aqt->next;
		if(aqt->preserve) {
			free(aqt->preserve);
			aqt->preserve = NULL;
		}
		if(aqt->query) {
			free(aqt->query);
			aqt->query = NULL;
		}
		if(aqt->rdelim) {
			free(aqt->rdelim);
			aqt->rdelim = NULL;
		}
		if(aqt->cdelim) {
			free(aqt->cdelim);
			aqt->cdelim = NULL;
		}
		free(aqt);
		aqt = NULL;
		recent--;
	}
	running_queries--;
	sqlchild_check_queue();
	return;
}

static void sqlchild_check_queue()
{
	int fds[2];
	struct query_state_t *aqt;
	if(running_queries >= mudconf.sqlDB_max_queries)
		return;
	if(pending == NULL)
		return;
	if(dbi_state != DBIS_READY)
		return;

	aqt = pending;
	pending = aqt->next;
	if(pending == NULL)
		pending_tail = NULL;

	if(pipe(fds) < 0) {
		log_perror("SQL", "FAIL", NULL, "pipe");
		return;
	}

	if((aqt->pid = fork()) == 0) {
		aqt->fd = fds[1];
        close(fds[0]);
		unbind_signals();
		sqlchild_child_execute_query(aqt);
		exit(0);
	} else {
		running_queries++;
		aqt->fd = fds[0];
		close(fds[1]);
	}
	dprintk("waiting on sqlchild pid %d executing request %d", aqt->pid,
			aqt->serial);

	if(running)
		aqt->next = running;
	running = aqt;

	event_set(&aqt->ev, aqt->fd, EV_READ, sqlchild_finish_query, aqt);
	event_add(&aqt->ev, &query_timeout);
	return;
}

/* CHILD FUNCTIONS */

static void sqlchild_child_abort_query(struct query_state_t *aqt, char *error)
{
	struct query_response resp = { DBIS_EFAIL, 0 };
	if(error) {
		resp.n_chars = strlen(error) + 1;
		write(aqt->fd, &resp, sizeof(resp));
		write(aqt->fd, error, resp.n_chars);
	} else {
		write(aqt->fd, &resp, sizeof(resp));
	}
	close(aqt->fd);
	exit(0);
	return;
}

static void sqlchild_child_abort_query_dbi(struct query_state_t *aqt,
										   char *error)
{
	char *error_ptr;
	if(dbi_conn_error(conn, (const char **) &error_ptr) != -1)
		sqlchild_child_abort_query(aqt, error_ptr);
	else
		sqlchild_child_abort_query(aqt, error);
	return;
}

static void sqlchild_make_connection(char db_slot)
{
	char *db_type, *db_hostname, *db_username, *db_password, *db_database;
	switch (db_slot) {
	case 'A':
		db_type = mudconf.sqlDB_type_A;
		db_hostname = mudconf.sqlDB_hostname_A;
		db_username = mudconf.sqlDB_username_A;
		db_password = mudconf.sqlDB_password_A;
		db_database = mudconf.sqlDB_dbname_A;
		break;
	case 'B':
		db_type = mudconf.sqlDB_type_B;
		db_hostname = mudconf.sqlDB_hostname_B;
		db_username = mudconf.sqlDB_username_B;
		db_password = mudconf.sqlDB_password_B;
		db_database = mudconf.sqlDB_dbname_B;
		break;
	case 'C':
		db_type = mudconf.sqlDB_type_C;
		db_hostname = mudconf.sqlDB_hostname_C;
		db_username = mudconf.sqlDB_username_C;
		db_password = mudconf.sqlDB_password_C;
		db_database = mudconf.sqlDB_dbname_C;
		break;
	case 'D':
		db_type = mudconf.sqlDB_type_D;
		db_hostname = mudconf.sqlDB_hostname_D;
		db_username = mudconf.sqlDB_username_D;
		db_password = mudconf.sqlDB_password_D;
		db_database = mudconf.sqlDB_dbname_D;
		break;
	case 'E':
		db_type = mudconf.sqlDB_type_E;
		db_hostname = mudconf.sqlDB_hostname_E;
		db_username = mudconf.sqlDB_username_E;
		db_password = mudconf.sqlDB_password_E;
		db_database = mudconf.sqlDB_dbname_E;
		break;
	default:
		return;
	}
	conn = dbi_conn_new(db_type);
	if(!conn) {
		dprintk("dbi_conn_new() failed with db_type %s.", db_type);
		dbi_state = DBIS_EFAIL;
		return;
	}
	if(strncmp(db_type, "mysql", 128) == 0
	   && strnlen(mudconf.sqlDB_mysql_socket, 128) > 0
	   && dbi_conn_set_option(conn, "mysql_unix_socket",
							  mudconf.sqlDB_mysql_socket)) {
		dprintk("failed to set mysql_unix_socket");
		dbi_state = DBIS_EFAIL;
		return;
	}
	if(strncmp(db_type, "sqlite", 128) == 0
	   && strnlen(mudconf.sqlDB_sqlite_dbdir, 128) > 0
	   && dbi_conn_set_option(conn, "sqlite_dbdir",
							  mudconf.sqlDB_sqlite_dbdir)) {
		dprintk("failed to set sqlite dir.");
		dbi_state = DBIS_EFAIL;
		return;
	}
	if(db_hostname && dbi_conn_set_option(conn, "host", db_hostname)) {
		dprintk("failed to set hostname");
		dbi_state = DBIS_EFAIL;
		return;
	}
	if(db_username && dbi_conn_set_option(conn, "username", db_username)) {
		dprintk("failed to set username");
		dbi_state = DBIS_EFAIL;
		return;
	}
	if(db_password && dbi_conn_set_option(conn, "password", db_password)) {
		dprintk("failed to set password");
		dbi_state = DBIS_EFAIL;
		return;
	}
	if(db_database && dbi_conn_set_option(conn, "dbname", db_database)) {
		dprintk("failed to set database");
		dbi_state = DBIS_EFAIL;
		return;
	}
	return;
}

static char *sqlchild_sanitize_string(char *input, int length)
{
	char *retval = malloc(length + 1);
	int i = 0;
	memset(retval, 0, length + 1);
	for(i = 0; input[i] && i < length; i++) {
		if(isprint(input[i])) {
			retval[i] = input[i];
		} else {
			retval[i] = ' ';
		}
	}
	dprintk("length: %d, i is %d, terimnal character is 0x%02x.", length, i,
			input[i]);
	free(input);
	return retval;
}

static void sqlchild_child_execute_query(struct query_state_t *aqt)
{
	struct query_response resp = { DBIS_READY, -1 };
	dbi_result result;
	int rows, fields, i, ii, retval;
	char output_buffer[LBUF_SIZE], *ptr, *eptr, *delim;
	int binary_length;
	char time_buffer[64];
	int length = 0;
	struct tm tm;

	long long type_int;
	double type_fp;
	char *type_string;
	time_t type_time;

	ptr = output_buffer;
	eptr = ptr + LBUF_SIZE;
	*ptr = '\0';

	dprintk("executing query %d.", aqt->serial);

	sqlchild_make_connection(aqt->slot);
	if(!conn) {
		sqlchild_child_abort_query(aqt, "failed to create connection");
		return;
	}
	if(dbi_state != DBIS_READY) {
		sqlchild_child_abort_query_dbi(aqt,
									   "unknown error in sqlchild_make_connection");
		return;
	}

	if(!conn) {
		sqlchild_child_abort_query_dbi(aqt,
									   "unknown error in sqlchild_make_connection");
		return;
	}

	if(dbi_conn_connect(conn) != 0) {
		sqlchild_child_abort_query(aqt, "dbi_conn_connect failed");
		return;
	}

	result = dbi_conn_query(conn, aqt->query);
	if(result == NULL) {
		sqlchild_child_abort_query_dbi(aqt,
									   "unknown error in dbi_conn_query");
		return;
	}

	rows = dbi_result_get_numrows(result);
	fields = dbi_result_get_numfields(result);

	delim = NULL;

	while (dbi_result_next_row(result)) {
		if(delim != NULL) {
			ptr += snprintf(ptr, eptr - ptr, aqt->rdelim);
		}
		for(i = 1; i <= fields; i++) {
			if(fields == i)
				delim = "";
			else
				delim = aqt->cdelim;
			// XXX: handle error values form snprintf()
			switch (dbi_result_get_field_type_idx(result, i)) {
			case DBI_TYPE_INTEGER:
				type_int = dbi_result_get_longlong_idx(result, i);
				ptr += snprintf(ptr, eptr - ptr, "%lld%s", type_int, delim);
				break;
			case DBI_TYPE_DECIMAL:
				type_fp = dbi_result_get_double_idx(result, i);
				ptr += snprintf(ptr, eptr - ptr, "%f%s", type_fp, delim);
				break;
			case DBI_TYPE_STRING:

				type_string = dbi_result_get_string_copy_idx(result, i);
				ptr += snprintf(ptr, eptr - ptr, "%s%s", type_string, delim);
				free(type_string);
				break;
			case DBI_TYPE_BINARY:
				binary_length = dbi_result_get_field_length_idx(result, i);
				if(binary_length) {
					type_string = (char *)dbi_result_get_binary_copy_idx(result, i);
					type_string = sqlchild_sanitize_string(type_string,
														   dbi_result_get_field_length_idx
														   (result, i));
					ptr +=
						snprintf(ptr, eptr - ptr, "%s%s", type_string, delim);
					free(type_string);
				} else {
					ptr += snprintf(ptr, eptr - ptr, "%s", delim);
				}
				break;
			case DBI_TYPE_DATETIME:
				type_time = dbi_result_get_datetime_idx(result, i);
				localtime_r(&type_time, &tm);
				asctime_r(&tm, time_buffer);
				ptr += snprintf(ptr, eptr - ptr, "%s%s", time_buffer, delim);
				break;
			default:
				sqlchild_child_abort_query(aqt, "unknown type");
				return;
			}
			if(eptr - ptr < 1) {
				sqlchild_child_abort_query(aqt, "result too large");
				return;
			}
		}
	}
	*ptr++ = '\0';
	resp.n_chars = eptr - ptr;
	eptr = ptr;
	// XXX: handle failure
	write(aqt->fd, &resp, sizeof(struct query_response));
	ptr = output_buffer;
	while (ptr < eptr) {
		retval = write(aqt->fd, ptr, eptr - ptr);
		ptr += retval;
	}
	close(aqt->fd);
	return;
}

#endif /* SQL_SUPPORT */