📄 mysql.c
字号:
/* This file is part of GNUnet. (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008 Christian Grothoff (and other contributing authors) GNUnet is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNUnet; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.*//** * @file applications/sqstore_mysql/mysql.c * @author Igor Wronsky and Christian Grothoff * * Database: MySQL * * NOTE: This db module does NOT work with mysql prior to 4.1 since * it uses prepared statements. MySQL 5.0.46 promises to fix a bug * in MyISAM that is causing us grief. At the time of this writing, * that version is yet to be released. In anticipation, the code * will use MyISAM with 5.0.46 (and higher). If you run such a * version, please run "make check" to verify that the MySQL bug * was actually fixed in your version (and if not, change the * code below to use MyISAM for gn071). * * HIGHLIGHTS * * Pros * + On up-to-date hardware where mysql can be used comfortably, this * module will have better performance than the other db choices * (according to our tests). * + Its often possible to recover the mysql database from internal * inconsistencies. The other db choices do not support repair! * Cons * - Memory usage (Comment: "I have 1G and it never caused me trouble") * - Manual setup * * MANUAL SETUP INSTRUCTIONS * * 1) in /etc/gnunet.conf, set * <pre> * * sqstore = "sqstore_mysql" * * </pre> * 2) Then access mysql as root, * <pre> * * $ mysql -u root -p * * </pre> * and do the following. [You should replace $USER with the username * that will be running the gnunetd process]. * <pre> * CREATE DATABASE gnunet; GRANT select,insert,update,delete,create,alter,drop,create temporary tables ON gnunet.* TO $USER@localhost; SET PASSWORD FOR $USER@localhost=PASSWORD('$the_password_you_like'); FLUSH PRIVILEGES; * * </pre> * 3) In the $HOME directory of $USER, create a ".my.cnf" file * with the following lines * <pre> [client] user=$USER password=$the_password_you_like * </pre> * * Thats it. Note that .my.cnf file is a security risk unless its on * a safe partition etc. The $HOME/.my.cnf can of course be a symbolic * link. Even greater security risk can be achieved by setting no * password for $USER. Luckily $USER has only priviledges to mess * up GNUnet's tables, nothing else (unless you give him more, * of course).<p> * * 4) Still, perhaps you should briefly try if the DB connection * works. First, login as $USER. Then use, * * <pre> * $ mysql -u $USER -p $the_password_you_like * mysql> use gnunet; * </pre> * * If you get the message "Database changed" it probably works. * * [If you get "ERROR 2002: Can't connect to local MySQL server * through socket '/tmp/mysql.sock' (2)" it may be resolvable by * "ln -s /var/run/mysqld/mysqld.sock /tmp/mysql.sock" * so there may be some additional trouble depending on your mysql setup.] * * REPAIRING TABLES * * - Its probably healthy to check your tables for inconsistencies * every now and then. * - If you get odd SEGVs on gnunetd startup, it might be that the mysql * databases have been corrupted. * - The tables can be verified/fixed in two ways; * 1) by running mysqlcheck -A, or * 2) by executing (inside of mysql using the GNUnet database): * mysql> REPAIR TABLE gn080; * mysql> REPAIR TABLE gn072; * * PROBLEMS? * * If you have problems related to the mysql module, your best * friend is probably the mysql manual. The first thing to check * is that mysql is basically operational, that you can connect * to it, create tables, issue queries etc. * */#include "platform.h"#include "gnunet_util.h"#include "gnunet_protocols.h"#include "gnunet_sqstore_service.h"#include "gnunet_stats_service.h"#include "gnunet_state_service.h"#include <mysql/mysql.h>#define DEBUG_MYSQL GNUNET_NO#define DEBUG_TIME_MYSQL GNUNET_NO#define MAX_DATUM_SIZE 65536/** * Die with an error message that indicates * a failure of the command 'cmd' with the message given * by strerror(errno). */#define DIE_MYSQL(cmd, dbh) do { GNUNET_GE_LOG(ectx, GNUNET_GE_FATAL | GNUNET_GE_ADMIN | GNUNET_GE_IMMEDIATE, _("`%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, mysql_error((dbh)->dbf)); abort(); } while(0);/** * Log an error message at log-level 'level' that indicates * a failure of the command 'cmd' on file 'filename' * with the message given by strerror(errno). */#define LOG_MYSQL(level, cmd, dbh) do { GNUNET_GE_LOG(ectx, level, _("`%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, mysql_error((dbh)->dbf)); } while(0);static GNUNET_Stats_ServiceAPI *stats;static GNUNET_CoreAPIForPlugins *coreAPI;static unsigned int stat_size;/** * Size of the mysql database on disk. */static unsigned long long content_size;/** * Lock for updating content_size */static struct GNUNET_Mutex *lock;static struct GNUNET_GE_Context *ectx;/** * @brief mysql wrapper */typedef struct{ MYSQL *dbf; char *cnffile; int valid; /* stuff dealing with gn072 table */#define SELECT_VALUE "SELECT value FROM gn072 WHERE vkey=?" MYSQL_STMT *select_value;#define DELETE_VALUE "DELETE FROM gn072 WHERE vkey=?" MYSQL_STMT *delete_value;#define INSERT_VALUE "INSERT INTO gn072 (value) VALUES (?)" MYSQL_STMT *insert_value; /* stuff dealing with gn080 table */#define INSERT_ENTRY "INSERT INTO gn080 (size,type,prio,anonLevel,expire,hash,vhash,vkey) VALUES (?,?,?,?,?,?,?,?)" MYSQL_STMT *insert_entry;#define DELETE_ENTRY_BY_VKEY "DELETE FROM gn080 WHERE vkey=?" MYSQL_STMT *delete_entry_by_vkey;#define SELECT_ENTRY_BY_HASH "SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX (hash_vkey) WHERE hash=? AND vkey > ? ORDER BY vkey ASC LIMIT 1 OFFSET ?" MYSQL_STMT *select_entry_by_hash;#define SELECT_ENTRY_BY_HASH_AND_VHASH "SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX (hash_vhash_vkey) WHERE hash=? AND vhash=? AND vkey > ? ORDER BY vkey ASC LIMIT 1 OFFSET ?" MYSQL_STMT *select_entry_by_hash_and_vhash;#define SELECT_ENTRY_BY_HASH_AND_TYPE "SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX (hash_vkey) WHERE hash=? AND vkey > ? AND type=? ORDER BY vkey ASC LIMIT 1 OFFSET ?" MYSQL_STMT *select_entry_by_hash_and_type;#define SELECT_ENTRY_BY_HASH_VHASH_AND_TYPE "SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX (hash_vhash_vkey) WHERE hash=? AND vhash=? AND vkey > ? AND type=? ORDER BY vkey ASC LIMIT 1 OFFSET ?" MYSQL_STMT *select_entry_by_hash_vhash_and_type;#define COUNT_ENTRY_BY_HASH "SELECT count(*) FROM gn080 FORCE INDEX (hash) WHERE hash=?" MYSQL_STMT *count_entry_by_hash;#define COUNT_ENTRY_BY_HASH_AND_VHASH "SELECT count(*) FROM gn080 FORCE INDEX (hash_vhash_vkey) WHERE hash=? AND vhash=?" MYSQL_STMT *count_entry_by_hash_and_vhash;#define COUNT_ENTRY_BY_HASH_AND_TYPE "SELECT count(*) FROM gn080 FORCE INDEX (hash) WHERE hash=? AND type=?" MYSQL_STMT *count_entry_by_hash_and_type;#define COUNT_ENTRY_BY_HASH_VHASH_AND_TYPE "SELECT count(*) FROM gn080 FORCE INDEX (hash_vhash) WHERE hash=? AND vhash=? AND type=?" MYSQL_STMT *count_entry_by_hash_vhash_and_type;#define UPDATE_ENTRY "UPDATE gn080 SET prio=prio+?,expire=IF(expire>=?,expire,?) WHERE vkey=?" MYSQL_STMT *update_entry;#if 0 /* old, easier to read statments -- do not use, C code no longer works with these! */#define SELECT_IT_LOW_PRIORITY "SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 WHERE ( (prio = ? AND vkey > ?) OR (prio > ? AND vkey != ?) )"\ "ORDER BY prio ASC,vkey ASC LIMIT 1"#define SELECT_IT_NON_ANONYMOUS "SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 WHERE ( (prio = ? AND vkey < ?) OR (prio < ? AND vkey != ?) ) "\ "AND anonLevel=0 AND type != 0xFFFFFFFF "\ "ORDER BY prio DESC,vkey DESC LIMIT 1"#define SELECT_IT_EXPIRATION_TIME "SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 WHERE ( (expire = ? AND vkey > ?) OR (expire > ? AND vkey != ?) ) "\ "ORDER BY expire ASC,vkey ASC LIMIT 1"#define SELECT_IT_MIGRATION_ORDER "SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 WHERE ( (expire = ? AND vkey < ?) OR (expire < ? AND vkey != ?) ) "\ "AND expire > ? AND type!=3 "\ "ORDER BY expire DESC,vkey DESC LIMIT 1"#endif/* warning, slighly crazy mysql statements ahead. Essentially, MySQL does not handle "OR" very well, so we need to use UNION instead. And UNION does not automatically apply a LIMIT on the outermost clause, so we need to repeat ourselves quite a bit. All hail the performance gods (and thanks to #mysql on freenode) */#define SELECT_IT_LOW_PRIORITY "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(prio) WHERE (prio = ? AND vkey > ?) "\ "ORDER BY prio ASC,vkey ASC LIMIT 1) "\ "UNION "\ "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(prio) WHERE (prio > ? AND vkey != ?)"\ "ORDER BY prio ASC,vkey ASC LIMIT 1)"\ "ORDER BY prio ASC,vkey ASC LIMIT 1"#define SELECT_IT_NON_ANONYMOUS "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(prio) WHERE (prio = ? AND vkey < ?)"\ " AND anonLevel=0 AND type != 0xFFFFFFFF ORDER BY prio DESC,vkey DESC LIMIT 1) "\ "UNION "\ "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(prio) WHERE (prio < ? AND vkey != ?)"\ " AND anonLevel=0 AND type != 0xFFFFFFFF ORDER BY prio DESC,vkey DESC LIMIT 1) "\ "ORDER BY prio DESC,vkey DESC LIMIT 1"#define SELECT_IT_EXPIRATION_TIME "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(expire) WHERE (expire = ? AND vkey > ?) "\ "ORDER BY expire ASC,vkey ASC LIMIT 1) "\ "UNION "\ "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(expire) WHERE (expire > ? AND vkey != ?) "\ "ORDER BY expire ASC,vkey ASC LIMIT 1)"\ "ORDER BY expire ASC,vkey ASC LIMIT 1"#define SELECT_IT_MIGRATION_ORDER "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(expire) WHERE (expire = ? AND vkey < ?)"\ " AND expire > ? AND type!=3"\ " ORDER BY expire DESC,vkey DESC LIMIT 1) "\ "UNION "\ "(SELECT size,type,prio,anonLevel,expire,hash,vkey FROM gn080 FORCE INDEX(expire) WHERE (expire < ? AND vkey != ?)"\ " AND expire > ? AND type!=3"\ " ORDER BY expire DESC,vkey DESC LIMIT 1)"\ "ORDER BY expire DESC,vkey DESC LIMIT 1" MYSQL_STMT *iter[4];} mysqlHandle;#define SELECT_SIZE "SELECT sum(size) FROM gn080"static mysqlHandle *dbh;/** * Close the database connection. */static inticlose (){#define PEND(h) if (h != NULL) { mysql_stmt_close(h); h = NULL; } else {} if (dbh->dbf == NULL) return GNUNET_SYSERR; PEND (dbh->select_value); PEND (dbh->delete_value); PEND (dbh->insert_value); PEND (dbh->insert_entry); PEND (dbh->delete_entry_by_vkey); PEND (dbh->select_entry_by_hash); PEND (dbh->select_entry_by_hash_and_vhash); PEND (dbh->select_entry_by_hash_and_type); PEND (dbh->select_entry_by_hash_vhash_and_type); PEND (dbh->count_entry_by_hash); PEND (dbh->count_entry_by_hash_and_vhash); PEND (dbh->count_entry_by_hash_and_type); PEND (dbh->count_entry_by_hash_vhash_and_type); PEND (dbh->update_entry); PEND (dbh->iter[0]); PEND (dbh->iter[1]); PEND (dbh->iter[2]); PEND (dbh->iter[3]); mysql_close (dbh->dbf); dbh->dbf = NULL; dbh->valid = GNUNET_NO; return GNUNET_OK;}/** * Initiate the database connection. Uses dbh->cnffile for the * configuration, so that must be set already. * * @return GNUNET_OK on success */static intiopen (){ char *dbname; my_bool reconnect = 0; unsigned int timeout = 60; /* in seconds */ if (dbh->cnffile == NULL) return GNUNET_SYSERR; dbh->dbf = mysql_init (NULL); if (dbh->dbf == NULL) return GNUNET_SYSERR; mysql_options (dbh->dbf, MYSQL_READ_DEFAULT_FILE, dbh->cnffile); mysql_options (dbh->dbf, MYSQL_READ_DEFAULT_GROUP, "client"); mysql_options (dbh->dbf, MYSQL_OPT_RECONNECT, &reconnect); mysql_options (dbh->dbf, MYSQL_OPT_CONNECT_TIMEOUT, (const void *) &timeout); mysql_options (dbh->dbf, MYSQL_OPT_READ_TIMEOUT, (const void *) &timeout); mysql_options (dbh->dbf, MYSQL_OPT_WRITE_TIMEOUT, (const void *) &timeout); dbname = NULL; GNUNET_GC_get_configuration_value_string (coreAPI->cfg, "MYSQL", "DATABASE", "gnunet", &dbname); GNUNET_GE_ASSERT (ectx, dbname != NULL); mysql_real_connect (dbh->dbf, NULL, NULL, NULL, dbname, 0, NULL, 0); GNUNET_free (dbname); if (mysql_error (dbh->dbf)[0]) { LOG_MYSQL (GNUNET_GE_ERROR | GNUNET_GE_ADMIN | GNUNET_GE_BULK, "mysql_real_connect", dbh); iclose (); return GNUNET_SYSERR; } mysql_query (dbh->dbf, "SET SESSION net_read_timeout=60, SESSION net_write_timeout=60"); if (mysql_error (dbh->dbf)[0]) { LOG_MYSQL (GNUNET_GE_ERROR | GNUNET_GE_ADMIN | GNUNET_GE_BULK, "mysql_query", dbh); iclose (); return GNUNET_SYSERR; } /* MySQL 5.0.46 fixes a bug in MyISAM (presumably); earlier versions have issues with INDEX over BINARY data,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -