/** * \file sql.c * * \brief Code to support PennMUSH connection to SQL databases. * * */ #include "copyrite.h" #include "config.h" #ifdef I_UNISTD #include <unistd.h> #endif #include <string.h> #include "conf.h" #ifdef WIN32 #include <stdlib.h> #define sleep Sleep #endif #ifdef HAS_MYSQL #include <mysql/mysql.h> #include <mysql/errmsg.h> #endif #include "externs.h" #include "log.h" #include "parse.h" #include "command.h" #include "function.h" #include "mushdb.h" #include "dbdefs.h" #include "conf.h" #include "confmagic.h" #ifdef HAS_MYSQL static MYSQL *mysql_struct = NULL; #define MYSQL_RETRY_TIMES 3 static int safe_sql_query(dbref player, char *q_string, char *rowsep, char *fieldsep, char *buff, char **bp); static int sql_init(void); void sql_shutdown(void) { if (!mysql_struct) return; mysql_close(mysql_struct); mysql_struct = NULL; } static int sql_init(void) { int retries = MYSQL_RETRY_TIMES; /* If we are already connected, drop and retry the connection, in * case for some reason the server went away. */ if (mysql_struct) sql_shutdown(); if (!strcasecmp(SQL_PLATFORM, "mysql")) { while (retries && !mysql_struct) { /* Try to connect to the database host. If we have specified * localhost, use the Unix domain socket instead. */ mysql_struct = mysql_init(NULL); if (!mysql_real_connect (mysql_struct, SQL_HOST, SQL_USER, SQL_PASS, SQL_DB, 3306, 0, 0)) { do_rawlog(LT_ERR, "Failed mysql connection: %s\n", mysql_error(mysql_struct)); sql_shutdown(); sleep(1); } retries--; } } if (mysql_struct) return 1; else return 0; } static int safe_sql_query(dbref player, char *q_string, char *rowsep, char *fieldsep, char *buff, char **bp) { MYSQL_RES *qres; MYSQL_ROW row_p; int num_rows, got_rows, got_fields; int i, j; /* If we have no connection, and we don't have auto-reconnect on * (or we try to auto-reconnect and we fail), this is an error * generating a #-1. Notify the player, too, and set the return code. */ if (!mysql_struct) { sql_init(); if (!mysql_struct) { notify(player, "No SQL database connection."); if (buff) safe_str("#-1", buff, bp); return -1; } } if (!q_string || !*q_string) return 0; /* Send the query. */ got_rows = mysql_real_query(mysql_struct, q_string, strlen(q_string)); if (got_rows && (mysql_errno(mysql_struct) == CR_SERVER_GONE_ERROR)) { /* We got this error because the server died unexpectedly * and it shouldn't have. Try repeatedly to reconnect before * giving up and failing. This induces a few seconds of lag, * depending on number of retries */ sql_init(); if (mysql_struct) got_rows = mysql_real_query(mysql_struct, q_string, strlen(q_string)); } if (got_rows) { notify(player, mysql_error(mysql_struct)); if (buff) safe_str("#-1", buff, bp); return -1; } /* Get results. A silent query (INSERT, UPDATE, etc.) will return NULL */ qres = mysql_use_result(mysql_struct); if (!qres) { if (!mysql_field_count(mysql_struct)) { /* We didn't expect data back, so see if we modified anything */ num_rows = mysql_affected_rows(mysql_struct); notify_format(player, "SQL: %d rows affected.", num_rows); return 0; } else { /* Oops, we should have had data! */ notify_format(player, "SQL: Error: %s", mysql_error(mysql_struct)); return -1; } } got_fields = mysql_num_fields(qres); /* Construct properly-delimited data. */ if (buff) { i = 0; while ((row_p = mysql_fetch_row(qres)) != NULL) { if (i++ > 0) { safe_str(rowsep, buff, bp); } for (j = 0; j < got_fields; j++) { if (j > 0) { safe_str(fieldsep, buff, bp); } if (row_p[j] && *row_p[j]) if (safe_str(row_p[j], buff, bp)) goto finished; /* We filled the buffer, best stop */ } } } else { char *cell; MYSQL_FIELD *fields = mysql_fetch_fields(qres); i = 0; while ((row_p = mysql_fetch_row(qres)) != NULL) { i++; if (got_fields > 0) { for (j = 0; j < got_fields; j++) { cell = row_p[j]; notify_format(player, "Row %d, Field %s: %s", i, fields[j].name, (cell && *cell) ? cell : "NULL"); } } else notify_format(player, "Row %d: NULL", i); } } finished: /* Eat the rest of the results. The MySQL documentation implies this has to be done. */ while ((row_p = mysql_fetch_row(qres)) != NULL) (void) 0; mysql_free_result(qres); return 0; } FUNCTION(fun_sql) { char *rowsep = (char *) " "; char *fieldsep = (char *) " "; if (!Sql_Ok(executor)) { safe_str(T(e_perm), buff, bp); return; } if (nargs >= 2) { /* we have a row separator in args[1]. */ rowsep = args[1]; } if (nargs >= 3) { /* we have a field separator in args[2]. Got to parse it */ fieldsep = args[2]; } safe_sql_query(executor, args[0], rowsep, fieldsep, buff, bp); } FUNCTION(fun_sql_escape) { char bigbuff[BUFFER_LEN * 2 + 1]; if (!Sql_Ok(executor)) { safe_str(T(e_perm), buff, bp); return; } if (!args[0] || !*args[0]) return; if (!mysql_struct) { sql_init(); if (!mysql_struct) { notify(executor, "No SQL database connection."); safe_str("#-1", buff, bp); return; } } if (mysql_real_escape_string(mysql_struct, bigbuff, args[0], strlen(args[0])) < BUFFER_LEN) safe_str(bigbuff, buff, bp); else safe_str("#-1 TOO LONG", buff, bp); } COMMAND (cmd_sql) { safe_sql_query(player, arg_left, NULL, NULL, NULL, NULL); } #else /* Oops, no SQL */ FUNCTION(fun_sql) { safe_str(T(e_disabled), buff, bp); } FUNCTION(fun_sql_escape) { safe_str(T(e_disabled), buff, bp); } void sql_shutdown(void) { return; } #endif