Crossfire Server, Branch 1.12  R12190
cflogger.c
Go to the documentation of this file.
00001 /*****************************************************************************/
00002 /* Logger plugin version 1.0 alpha.                                          */
00003 /* Contact:                                                                  */
00004 /*****************************************************************************/
00005 /* That code is placed under the GNU General Public Licence (GPL)            */
00006 /* (C)2007 by Weeger Nicolas (Feel free to deliver your complaints)          */
00007 /*****************************************************************************/
00008 /*  CrossFire, A Multiplayer game for X-windows                              */
00009 /*                                                                           */
00010 /*  Copyright (C) 2000 Mark Wedel                                            */
00011 /*  Copyright (C) 1992 Frank Tore Johansen                                   */
00012 /*                                                                           */
00013 /*  This program is free software; you can redistribute it and/or modify     */
00014 /*  it under the terms of the GNU General Public License as published by     */
00015 /*  the Free Software Foundation; either version 2 of the License, or        */
00016 /*  (at your option) any later version.                                      */
00017 /*                                                                           */
00018 /*  This program is distributed in the hope that it will be useful,          */
00019 /*  but WITHOUT ANY WARRANTY; without even the implied warranty of           */
00020 /*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            */
00021 /*  GNU General Public License for more details.                             */
00022 /*                                                                           */
00023 /*  You should have received a copy of the GNU General Public License        */
00024 /*  along with this program; if not, write to the Free Software              */
00025 /*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.                */
00026 /*                                                                           */
00027 /*****************************************************************************/
00028 
00048 #include <cflogger.h>
00049 #ifndef __CEXTRACT__
00050 #include <cflogger_proto.h>
00051 #endif
00052 /*#include <stdarg.h>*/
00053 
00054 #include <sqlite3.h>
00055 
00057 #define CFLOGGER_CURRENT_FORMAT 3
00058 
00060 static sqlite3 *database;
00061 
00063 static int last_stored_day = -1;
00064 
00080 static int check_tables_callback(void *param, int argc, char **argv, char **azColName) {
00081     int *format = (int *)param;
00082 
00083     *format = atoi(argv[0]);
00084     return 0;
00085 }
00086 
00102 static int do_sql(const char *sql) {
00103     int err;
00104     char *msg;
00105 
00106     if (!database)
00107         return -1;
00108 
00109     err = sqlite3_exec(database, sql, NULL, NULL, &msg);
00110     if (err != SQLITE_OK) {
00111         cf_log(llevError, " [%s] error: %d [%s] for sql = %s\n", PLUGIN_NAME, err, msg, sql);
00112         sqlite3_free(msg);
00113     }
00114     return err;
00115 }
00116 
00143 static int update_table_format(const char *table, const char *newschema,
00144                                const char *select_columns) {
00145     char *sql;
00146     int err;
00147 
00148     sql = sqlite3_mprintf("ALTER TABLE %s RENAME TO %s_old;", table, table);
00149     err = do_sql(sql);
00150     sqlite3_free(sql);
00151     if (err != SQLITE_OK)
00152         return err;
00153 
00154     sql = sqlite3_mprintf("CREATE TABLE %s(%s);", table, newschema);
00155     err = do_sql(sql);
00156     sqlite3_free(sql);
00157     if (err != SQLITE_OK)
00158         return err;
00159 
00160     sql = sqlite3_mprintf("INSERT INTO %s SELECT %s FROM %s_old;",
00161                           table, select_columns, table);
00162     err = do_sql(sql);
00163     sqlite3_free(sql);
00164     if (err != SQLITE_OK)
00165         return err;
00166 
00167     sql = sqlite3_mprintf("DROP TABLE %s_old;", table, table);
00168     err = do_sql(sql);
00169     sqlite3_free(sql);
00170     /* Final return. */
00171     return err;
00172 }
00173 
00180 #define DO_OR_ROLLBACK(sqlstring) \
00181     if (do_sql(sqlstring) != SQLITE_OK) { \
00182         do_sql("rollback transaction;"); \
00183         cf_log(llevError, " [%s] Logger database format update failed! Couldn't upgrade from format %d to fromat %d!. Won't log.\n", PLUGIN_NAME, format, CFLOGGER_CURRENT_FORMAT);\
00184         sqlite3_close(database); \
00185         database = NULL; \
00186         return; \
00187     }
00188 
00189 #define UPDATE_OR_ROLLBACK(tbl, newschema, select_columns) \
00190     if (update_table_format((tbl), (newschema), (select_columns)) != SQLITE_OK) { \
00191         do_sql("rollback transaction;"); \
00192         cf_log(llevError, " [%s] Logger database format update failed! Couldn't upgrade from format %d to fromat %d!. Won't log.\n", PLUGIN_NAME, format, CFLOGGER_CURRENT_FORMAT);\
00193         sqlite3_close(database); \
00194         database = NULL; \
00195         return; \
00196     }
00197 
00201 static void check_tables(void) {
00202     int format;
00203     int err;
00204     format = 0;
00205 
00206     err = sqlite3_exec(database, "select param_value from parameters where param_name = 'version';", check_tables_callback, &format, NULL);
00207 
00208     /* Safety check. */
00209     if (format > CFLOGGER_CURRENT_FORMAT) {
00210         cf_log(llevError, " [%s] Logger database format (%d) is newer than supported (%d) by this binary!. Won't log.\n", PLUGIN_NAME, format, CFLOGGER_CURRENT_FORMAT);
00211         /* This will disable using the db since do_sql() checks if database is
00212          * NULL.
00213          */
00214         sqlite3_close(database);
00215         database = NULL;
00216     }
00217 
00218     /* Check if we need to upgrade/create database. */
00219     if (format < 1) {
00220         cf_log(llevDebug, " [%s] Creating logger database schema (format 1).\n", PLUGIN_NAME);
00221         if (do_sql("BEGIN EXCLUSIVE TRANSACTION;") != SQLITE_OK) {
00222             cf_log(llevError, " [%s] Logger database format update failed! Couldn't acquire exclusive lock to database when upgrading from format %d to fromat %d!. Won't log.\n", PLUGIN_NAME, format, CFLOGGER_CURRENT_FORMAT);
00223             sqlite3_close(database);
00224             database = NULL;
00225             return;
00226         }
00227         DO_OR_ROLLBACK("create table living(liv_id integer primary key autoincrement, liv_name text, liv_is_player integer, liv_level integer);");
00228         DO_OR_ROLLBACK("create table region(reg_id integer primary key autoincrement, reg_name text);");
00229         DO_OR_ROLLBACK("create table map(map_id integer primary key autoincrement, map_path text, map_reg_id integer);");
00230         DO_OR_ROLLBACK("create table time(time_real integer, time_ingame text);");
00231 
00232         DO_OR_ROLLBACK("create table living_event(le_liv_id integer, le_time integer, le_code integer, le_map_id integer);");
00233         DO_OR_ROLLBACK("create table map_event(me_map_id integer, me_time integer, me_code integer, me_living_id integer);");
00234         DO_OR_ROLLBACK("create table kill_event(ke_time integer, ke_victim_id integer, ke_victim_level integer, ke_map_id integer , ke_killer_id integer, ke_killer_level integer);");
00235 
00236         DO_OR_ROLLBACK("create table parameters(param_name text, param_value text);");
00237         DO_OR_ROLLBACK("insert into parameters values( 'version', '1' );");
00238         do_sql("COMMIT TRANSACTION;");
00239     }
00240 
00241     /* Must be able to handle update from format 1. If we are creating a new
00242      * database, format 1 is still created first, then updated.
00243      *
00244      * This way is simpler than having to create two ways to make a format 2 db.
00245      */
00246     if (format < 2) {
00247         cf_log(llevDebug, " [%s] Upgrading logger database schema (to format 2).\n", PLUGIN_NAME);
00248         if (do_sql("BEGIN EXCLUSIVE TRANSACTION;") != SQLITE_OK) {
00249             cf_log(llevError, " [%s] Logger database format update failed! Couldn't acquire exclusive lock to database when upgrading from format %d to fromat %d!. Won't log.\n", PLUGIN_NAME, format, CFLOGGER_CURRENT_FORMAT);
00250             sqlite3_close(database);
00251             database = NULL;
00252             return;
00253         }
00254         /* Update schema for various tables. Why so complex? Because ALTER TABLE
00255          * can't add the "primary key" bit or other constraints...
00256          */
00257         UPDATE_OR_ROLLBACK("living", "liv_id INTEGER PRIMARY KEY AUTOINCREMENT, liv_name TEXT NOT NULL, liv_is_player INTEGER NOT NULL, liv_level INTEGER NOT NULL", "*");
00258         UPDATE_OR_ROLLBACK("region", "reg_id INTEGER PRIMARY KEY AUTOINCREMENT, reg_name TEXT UNIQUE NOT NULL", "*");
00259         UPDATE_OR_ROLLBACK("map",    "map_id INTEGER PRIMARY KEY AUTOINCREMENT, map_path TEXT NOT NULL, map_reg_id INTEGER NOT NULL, CONSTRAINT map_path_reg_id UNIQUE(map_path, map_reg_id)", "*");
00260 #if 0
00261         /* Turned out this was incorrect. And version 1 -> 3 directly works for this. */
00262         UPDATE_OR_ROLLBACK("time",   "time_real INTEGER PRIMARY KEY, time_ingame TEXT UNIQUE NOT NULL");
00263 #endif
00264         UPDATE_OR_ROLLBACK("living_event", "le_liv_id INTEGER NOT NULL, le_time INTEGER NOT NULL, le_code INTEGER NOT NULL, le_map_id INTEGER NOT NULL", "*");
00265         UPDATE_OR_ROLLBACK("map_event",    "me_map_id INTEGER NOT NULL, me_time INTEGER NOT NULL, me_code INTEGER NOT NULL, me_living_id INTEGER NOT NULL", "*");
00266         UPDATE_OR_ROLLBACK("kill_event",   "ke_time INTEGER NOT NULL, ke_victim_id INTEGER NOT NULL, ke_victim_level INTEGER NOT NULL, ke_map_id INTEGER NOT NULL, ke_killer_id INTEGER NOT NULL, ke_killer_level INTEGER NOT NULL", "*");
00267 
00268         /* Handle changed parameters table format: */
00269         /* Due to backward compatiblity "primary key" in SQLite doesn't imply
00270          * "not null" (http://www.sqlite.org/lang_createtable.html), unless it
00271          * is "integer primary key".
00272          *
00273          * We don't need to save anything stored in this in format 1, it was
00274          * only used for storing what format was used.
00275          */
00276         DO_OR_ROLLBACK("DROP TABLE parameters;");
00277         DO_OR_ROLLBACK("CREATE TABLE parameters(param_name TEXT NOT NULL PRIMARY KEY, param_value TEXT);");
00278         DO_OR_ROLLBACK("INSERT INTO parameters (param_name, param_value) VALUES( 'version', '2' );");
00279 
00280         /* Create various indexes. */
00281         DO_OR_ROLLBACK("CREATE INDEX living_name_player_level ON living(liv_name,liv_is_player,liv_level);");
00282 
00283         /* Newspaper module could make use of some indexes too: */
00284         DO_OR_ROLLBACK("CREATE INDEX kill_event_time ON kill_event(ke_time);");
00285         DO_OR_ROLLBACK("CREATE INDEX map_reg_id ON map(map_reg_id);");
00286 
00287         /* Finally commit the transaction. */
00288         do_sql("COMMIT TRANSACTION;");
00289     }
00290 
00291     if (format < 3) {
00292         cf_log(llevDebug, " [%s] Upgrading logger database schema (to format 3).\n", PLUGIN_NAME);
00293         if (do_sql("BEGIN EXCLUSIVE TRANSACTION;") != SQLITE_OK) {
00294             cf_log(llevError, " [%s] Logger database format update failed! Couldn't acquire exclusive lock to database when upgrading from format %d to fromat %d!. Won't log.\n", PLUGIN_NAME, format, CFLOGGER_CURRENT_FORMAT);
00295             sqlite3_close(database);
00296             database = NULL;
00297             return;
00298         }
00299         UPDATE_OR_ROLLBACK("time",   "time_ingame TEXT NOT NULL PRIMARY KEY, time_real INTEGER NOT NULL", "time_ingame, time_real");
00300         DO_OR_ROLLBACK("UPDATE parameters SET param_value = '3' WHERE param_name = 'version';");
00301         do_sql("COMMIT TRANSACTION;");
00302         /* After all these changes better vacuum... The tables could have been
00303          * huge, and since we recreated several of them above there could be a
00304          * lot of wasted space.
00305          */
00306         do_sql("VACUUM;");
00307     }
00308 }
00309 
00325 static int get_living_id(object *living) {
00326     char **line;
00327     char *sql;
00328     int nrow, ncolumn, id;
00329 
00330     if (living->type == PLAYER)
00331         sql = sqlite3_mprintf("select liv_id from living where liv_name='%q' and liv_is_player = 1", living->name);
00332     else
00333         sql = sqlite3_mprintf("select liv_id from living where liv_name='%q' and liv_is_player = 0 and liv_level = %d", living->name, living->level);
00334     sqlite3_get_table(database, sql, &line, &nrow, &ncolumn, NULL);
00335 
00336     if (nrow > 0)
00337         id = atoi(line[ncolumn]);
00338     else {
00339         sqlite3_free(sql);
00340         sql = sqlite3_mprintf("insert into living(liv_name, liv_is_player, liv_level) values('%q', %d, %d)", living->name, living->type == PLAYER ? 1 : 0, living->level);
00341         do_sql(sql);
00342         id = sqlite3_last_insert_rowid(database);
00343     }
00344     sqlite3_free(sql);
00345     sqlite3_free_table(line);
00346     return id;
00347 }
00348 
00359 static int get_region_id(region *reg) {
00360     char **line;
00361     char *sql;
00362     int nrow, ncolumn, id;
00363 
00364     if (!reg)
00365         return 0;
00366 
00367     sql = sqlite3_mprintf("select reg_id from region where reg_name='%q'", reg->name);
00368     sqlite3_get_table(database, sql, &line, &nrow, &ncolumn, NULL);
00369 
00370     if (nrow > 0)
00371         id = atoi(line[ncolumn]);
00372     else {
00373         sqlite3_free(sql);
00374         sql = sqlite3_mprintf("insert into region(reg_name) values( '%q' )", reg->name);
00375         do_sql(sql);
00376         id = sqlite3_last_insert_rowid(database);
00377     }
00378     sqlite3_free(sql);
00379     sqlite3_free_table(line);
00380     return id;
00381 }
00382 
00395 static int get_map_id(mapstruct *map) {
00396     char **line;
00397     char *sql;
00398     int nrow, ncolumn, id, reg_id;
00399     const char *path = map->path;
00400 
00401     if (strncmp(path, "/random/", 7) == 0)
00402         path = "/random/";
00403 
00404     reg_id = get_region_id(map->region);
00405     sql = sqlite3_mprintf("select map_id from map where map_path='%q' and map_reg_id = %d", path, reg_id);
00406     sqlite3_get_table(database, sql, &line, &nrow, &ncolumn, NULL);
00407 
00408     if (nrow > 0)
00409         id = atoi(line[ncolumn]);
00410     else {
00411         sqlite3_free(sql);
00412         sql = sqlite3_mprintf("insert into map(map_path, map_reg_id) values( '%q', %d)", path, reg_id);
00413         do_sql(sql);
00414         id = sqlite3_last_insert_rowid(database);
00415     }
00416     sqlite3_free(sql);
00417     sqlite3_free_table(line);
00418 
00419     return id;
00420 }
00421 
00428 static int store_time(void) {
00429     char **line;
00430     char *sql;
00431     int nrow, ncolumn;
00432     char date[50];
00433     time_t now;
00434     timeofday_t tod;
00435 
00436     cf_get_time(&tod);
00437     now = time(NULL);
00438 
00439     if (tod.day == last_stored_day)
00440         return 0;
00441     last_stored_day = tod.day;
00442 
00443     snprintf(date, 50, "%10d-%2d-%2d %2d:%2d", tod.year, tod.month, tod.day, tod.hour, tod.minute);
00444 
00445     sql = sqlite3_mprintf("select * from time where time_ingame='%q'", date);
00446     sqlite3_get_table(database, sql, &line, &nrow, &ncolumn, NULL);
00447     sqlite3_free(sql);
00448     sqlite3_free_table(line);
00449     if (nrow > 0)
00450         return 0;
00451 
00452     sql = sqlite3_mprintf("insert into time (time_ingame, time_real) values( '%s', %d )", date, now);
00453     do_sql(sql);
00454     sqlite3_free(sql);
00455     return 1;
00456 }
00457 
00466 static void add_player_event(object *pl, int event_code) {
00467     int id = get_living_id(pl);
00468     int map_id = 0;
00469     char *sql;
00470 
00471     if (pl->map)
00472         map_id = get_map_id(pl->map);
00473 
00474     sql = sqlite3_mprintf("insert into living_event values( %d, %d, %d, %d)", id, time(NULL), event_code, map_id);
00475     do_sql(sql);
00476     sqlite3_free(sql);
00477 }
00478 
00489 static void add_map_event(mapstruct *map, int event_code, object *pl) {
00490     int mapid;
00491     int playerid = 0;
00492     char *sql;
00493 
00494     if (pl && pl->type == PLAYER)
00495         playerid = get_living_id(pl);
00496 
00497     mapid = get_map_id(map);
00498     sql = sqlite3_mprintf("insert into map_event values( %d, %d, %d, %d)", mapid, time(NULL), event_code, playerid);
00499     do_sql(sql);
00500     sqlite3_free(sql);
00501 }
00502 
00513 static void add_death(object *victim, object *killer) {
00514     int vid, kid, map_id;
00515     char *sql;
00516 
00517     if (!victim || !killer)
00518         return;
00519     if (victim->type != PLAYER && killer->type != PLAYER) {
00520         /* Killer might be a bullet, which might be owned by the player. */
00521         object *owner = cf_object_get_object_property(killer, CFAPI_OBJECT_PROP_OWNER);
00522         if (owner != NULL && owner->type == PLAYER)
00523             killer = owner;
00524         else
00525             return;
00526     }
00527 
00528     vid = get_living_id(victim);
00529     kid = get_living_id(killer);
00530     map_id = get_map_id(victim->map);
00531     sql = sqlite3_mprintf("insert into kill_event values( %d, %d, %d, %d, %d, %d)", time(NULL), vid, victim->level, map_id, kid, killer->level);
00532     do_sql(sql);
00533     sqlite3_free(sql);
00534 }
00535 
00546 CF_PLUGIN int initPlugin(const char *iversion, f_plug_api gethooksptr) {
00547     cf_init_plugin(gethooksptr);
00548     cf_log(llevInfo, "%s init\n", PLUGIN_VERSION);
00549     return 0;
00550 }
00551 
00562 CF_PLUGIN void *getPluginProperty(int *type, ...) {
00563     va_list args;
00564     const char *propname;
00565     char *buf;
00566     int size;
00567 
00568     va_start(args, type);
00569     propname = va_arg(args, const char *);
00570 
00571     if (!strcmp(propname, "Identification")) {
00572         buf = va_arg(args, char *);
00573         size = va_arg(args, int);
00574         va_end(args);
00575         snprintf(buf, size, PLUGIN_NAME);
00576         return NULL;
00577     } else if (!strcmp(propname, "FullName")) {
00578         buf = va_arg(args, char *);
00579         size = va_arg(args, int);
00580         va_end(args);
00581         snprintf(buf, size, PLUGIN_VERSION);
00582         return NULL;
00583     }
00584     va_end(args);
00585     return NULL;
00586 }
00587 
00598 CF_PLUGIN int cflogger_runPluginCommand(object *op, char *params) {
00599     return -1;
00600 }
00601 
00610 void *eventListener(int *type, ...) {
00611     static int rv = 0;
00612 
00613     return &rv;
00614 }
00615 
00624 CF_PLUGIN void *cflogger_globalEventListener(int *type, ...) {
00625     va_list args;
00626     static int rv = 0;
00627     player *pl;
00628     object *op;
00629     int event_code;
00630     mapstruct *map;
00631 
00632     va_start(args, type);
00633     event_code = va_arg(args, int);
00634 
00635     switch (event_code) {
00636     case EVENT_BORN:
00637     case EVENT_PLAYER_DEATH:
00638     case EVENT_REMOVE:
00639     case EVENT_MUZZLE:
00640     case EVENT_KICK:
00641         op = va_arg(args, object *);
00642         add_player_event(op, event_code);
00643         break;
00644 
00645     case EVENT_LOGIN:
00646     case EVENT_LOGOUT:
00647         pl = va_arg(args, player *);
00648         add_player_event(pl->ob, event_code);
00649         break;
00650 
00651     case EVENT_MAPENTER:
00652     case EVENT_MAPLEAVE:
00653         op = va_arg(args, object *);
00654         map = va_arg(args, mapstruct *);
00655         add_map_event(map, event_code, op);
00656         break;
00657 
00658     case EVENT_MAPLOAD:
00659     case EVENT_MAPUNLOAD:
00660     case EVENT_MAPRESET:
00661         map = va_arg(args, mapstruct *);
00662         add_map_event(map, event_code, NULL);
00663         break;
00664 
00665     case EVENT_GKILL: {
00666             object *killer;
00667             op = va_arg(args, object *);
00668             killer = va_arg(args, object *);
00669             add_death(op, killer);
00670         }
00671         break;
00672 
00673     case EVENT_CLOCK:
00674         store_time();
00675         break;
00676     }
00677     va_end(args);
00678 
00679     return &rv;
00680 }
00681 
00690 CF_PLUGIN int postInitPlugin(void) {
00691     char path[500];
00692     const char *dir;
00693 
00694     cf_log(llevInfo, "%s post init\n", PLUGIN_VERSION);
00695 
00696     dir = cf_get_directory(4);
00697     snprintf(path, sizeof(path), "%s/cflogger.db", dir);
00698     cf_log(llevDebug, " [%s] database file: %s\n", PLUGIN_NAME, path);
00699 
00700     if (sqlite3_open(path, &database) != SQLITE_OK) {
00701         cf_log(llevError, " [%s] database error!\n", PLUGIN_NAME);
00702         sqlite3_close(database);
00703         database = NULL;
00704         return 0;
00705     }
00706 
00707     check_tables();
00708 
00709     store_time();
00710 
00711     cf_system_register_global_event(EVENT_BORN, PLUGIN_NAME, cflogger_globalEventListener);
00712     cf_system_register_global_event(EVENT_REMOVE, PLUGIN_NAME, cflogger_globalEventListener);
00713     cf_system_register_global_event(EVENT_GKILL, PLUGIN_NAME, cflogger_globalEventListener);
00714     cf_system_register_global_event(EVENT_LOGIN, PLUGIN_NAME, cflogger_globalEventListener);
00715     cf_system_register_global_event(EVENT_LOGOUT, PLUGIN_NAME, cflogger_globalEventListener);
00716 
00717     cf_system_register_global_event(EVENT_PLAYER_DEATH, PLUGIN_NAME, cflogger_globalEventListener);
00718 
00719     cf_system_register_global_event(EVENT_MAPENTER, PLUGIN_NAME, cflogger_globalEventListener);
00720     cf_system_register_global_event(EVENT_MAPLEAVE, PLUGIN_NAME, cflogger_globalEventListener);
00721     cf_system_register_global_event(EVENT_MAPRESET, PLUGIN_NAME, cflogger_globalEventListener);
00722     cf_system_register_global_event(EVENT_MAPLOAD, PLUGIN_NAME, cflogger_globalEventListener);
00723     cf_system_register_global_event(EVENT_MAPUNLOAD, PLUGIN_NAME, cflogger_globalEventListener);
00724 
00725     cf_system_register_global_event(EVENT_MUZZLE, PLUGIN_NAME, cflogger_globalEventListener);
00726     cf_system_register_global_event(EVENT_KICK, PLUGIN_NAME, cflogger_globalEventListener);
00727 
00728     cf_system_register_global_event(EVENT_CLOCK, PLUGIN_NAME, cflogger_globalEventListener);
00729 
00730     return 0;
00731 }
00732 
00741 CF_PLUGIN int closePlugin(void) {
00742     cf_log(llevInfo, "%s closing.\n", PLUGIN_VERSION);
00743     if (database) {
00744         sqlite3_close(database);
00745         database = NULL;
00746     }
00747     return 0;
00748 }