btmux/autom4te.cache/
btmux/doc/.svn/
btmux/event/.svn/
btmux/game/.svn/
btmux/game/bin/.svn/
btmux/game/data/.svn/
btmux/game/logs/.svn/
btmux/game/maps/
btmux/game/maps/.svn/
btmux/game/maps/.svn/prop-base/
btmux/game/maps/.svn/props/
btmux/game/maps/.svn/text-base/
btmux/game/maps/.svn/wcprops/
btmux/game/mechs/
btmux/game/mechs/.svn/
btmux/game/mechs/.svn/prop-base/
btmux/game/mechs/.svn/props/
btmux/game/mechs/.svn/text-base/
btmux/game/mechs/.svn/wcprops/
btmux/game/text/.svn/
btmux/include/.svn/
btmux/misc/
btmux/misc/.svn/
btmux/misc/.svn/prop-base/
btmux/misc/.svn/props/
btmux/misc/.svn/text-base/
btmux/misc/.svn/wcprops/
btmux/python/
btmux/python/.svn/
btmux/python/.svn/prop-base/
btmux/python/.svn/props/
btmux/python/.svn/text-base/
btmux/python/.svn/wcprops/
btmux/src/.svn/prop-base/
btmux/src/.svn/props/
btmux/src/.svn/text-base/
btmux/src/.svn/wcprops/
btmux/src/hcode/.svn/
btmux/src/hcode/btech/
btmux/src/hcode/btech/.svn/
btmux/src/hcode/btech/.svn/prop-base/
btmux/src/hcode/btech/.svn/props/
btmux/src/hcode/btech/.svn/text-base/
btmux/src/hcode/btech/.svn/wcprops/
btmux/src/hcode/include/.svn/
/*
 * sqlchild.c
 *
 * Copyright (c) 2004,2005 Martin Murray <mmurray@monkey.org>
 * All rights reserved.
 *
 * $Id: sqlchild.c 248 2005-10-27 17:39:11Z murrayma $
 */

#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"
#if 0
#include "db.h"
#include "file_c.h"
#include "interface.h"
#include "flags.h"
#include "powers.h"
#include "alloc.h"
#include "command.h"
#include "slave.h"
#include "attrs.h"
#endif 

/* TODO:
 * String sanitization.
 *  - dbi_driver_quote_string_copy ?
 * 
 * Handle Timezones in DATETIME
 *
 * Handle final write() failing.
 */

#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

int dbi_initialized = 0;
int dbi_state;
int query_counter = 0;

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;

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;
    struct query_state_t *aqt;
    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;
        }
    } 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;
        }
    } 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) {
    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) {
        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.n_chars < LBUF_SIZE) {
        if(strnlen(buffer, resp.n_chars) >= LBUF_SIZE) {
            buffer[LBUF_SIZE] = '\0';
        }
    } else {
        if(strnlen(buffer, resp.n_chars) >= LBUF_SIZE) {
            buffer[LBUF_SIZE] = '\0';
        }
    }
    
    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 reported.";
        }
    }
    
fail:
    argv[2] = aqt->preserve;
    did_it(GOD, aqt->thing, 0, NULL, 0, NULL, aqt->attr, argv, 4);
hardfail:
    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();
    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];
        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);
    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 && strlen(mudconf.sqlDB_mysql_socket) > 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(db_hostname && dbi_conn_set_option(conn, "host", db_hostname)) {
        dprintk("failed to set hostname");
        dbi_state = DBIS_EFAIL;
        return;
    }
    if(dbi_conn_set_option(conn, "username", db_username)) {
        dprintk("failed to set username");
        dbi_state = DBIS_EFAIL;
        return;
    }
    if(dbi_conn_set_option(conn, "password", db_password)) {
        dprintk("failed to set password");
        dbi_state = DBIS_EFAIL;
        return;
    } 
    if(dbi_conn_set_option(conn, "dbname", db_database)) {
        dprintk("failed to set database");
        dbi_state = DBIS_EFAIL;
        return;
    }
    return;
}
 
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;
    char time_buffer[64];
    int length = 0;

    long long type_int;
    double type_fp;
    const 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_idx(result, i);
                    ptr += snprintf(ptr, eptr-ptr, "%s%s", type_string, delim);
                    break;
                case DBI_TYPE_BINARY:
                    sqlchild_child_abort_query(aqt, "can't handle type BINARY");
                    return;
                case DBI_TYPE_DATETIME:
                    // HANDLE TIMEZONE
                    type_time = dbi_result_get_datetime_idx(result, i);
                    ctime_r(&type_time, 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 */