/* * 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 */