/*
This file is part of GNUnet.
(C) 2001, 2002, 2003, 2004 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.
*/
/**
* @author Igor Wronsky
* @file applications/afs/module/high_mysql.c
*
* Database: MySQL
*
* NOTE: This db module does NOT work with mysql v3.23.49 due to a bug
* in mysql. All later versions should be fine, including the 4.0.x
* series. Current devel version is 4.0.16-log on debian/unstable.
*
* 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
* (gnunet-check cannot fix problems internal to the dbmgr!).
* For example, we have seen several cases where power failure
* has ruined a gdbm database beyond 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
*
*
* DATABASETYPE = "mysql"
*
*
* 2) Then access mysql as root,
*
*
* # mysql -u root -p
*
*
* and do the following. [You should replace $USER with the username
* that will be running the gnunetd process].
*
*
CREATE DATABASE gnunet;
GRANT select,insert,update,delete,create,alter,drop
ON gnunet.* TO $USER@localhost;
SET PASSWORD FOR $USER@localhost=PASSWORD('$the_password_you_like');
FLUSH PRIVILEGES;
*
*
* 3) In the $HOME directory of $USER, create a ".my.cnf" file
* with the following lines
*
[client]
user=$USER
password=$the_password_you_like
*
*
* 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).
*
* 4) Still, perhaps you should briefly try if the DB connection
* works. First, login as $USER. Then use,
*
*
* # mysql -u $USER
* mysql> use gnunet;
*
*
* 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 shutting down mysqld (mandatory!) and running
* # myisamchk -r *.MYI
* in /var/lib/mysql/gnunet/ (or wherever the tables are stored).
* Another repair command is "mysqlcheck". The usable command
* may depend on your mysql build/version. Or,
* 2) by executing
* mysql> REPAIR TABLE data1024of
* for each table in the gnunet database (USE gnunet; SHOW TABLES;)
*
* EFFICIENCY ISSUES
*
* If you suffer from too slow index/insert speeds,
* you might try to define /etc/gnunet.conf option
*
* [AFS]
* MYSQL_DELAYED = YES
*
* for small efficiency boost. The option will let MySQL bundle multiple
* inserts before actually writing them to disk. You shouldn't use this
* option unless you're an (my)sql expert and really know what you're doing.
* Especially, if you run into any trouble due to this, you're on your own.
*
* 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 "high_backend.h"
#include "platform.h"
#include
#define DEBUG_MYSQL NO
#define DEBUG_TIME_MYSQL NO
/**
* Popularity of _known_ 3hash keywords can be tracked by creating
* the following table and enabling TRACK_3HASH_QUERIES. In addition,
* you'll have to fill the table with pairs yourself
* (not provided). Tracking is not generally recommended as
* it may harm your deniability.
*
USE gnunet;
CREATE TABLE `dictionary` (
`name` tinyblob NOT NULL,
`hash` varchar(40) binary NOT NULL default '',
`hits` smallint(5) unsigned NOT NULL default '0',
PRIMARY KEY (`hash`),
UNIQUE KEY `unique_name` (`name`(32))
) TYPE=MyISAM
*
*/
#define TRACK_3HASH_QUERIES NO
/**
* 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 { errexit(_("'%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, mysql_error(dbh->dbf)); } 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 { LOG(level, _("'%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, mysql_error(dbh->dbf)); } while(0);
/**
* @brief mysql wrapper
*/
typedef struct {
MYSQL * dbf;
unsigned int i; /* database index */
unsigned int n; /* total number of databases */
Mutex DATABASE_Lock_;
int avgLength_ID; /* which column contains the Avg_row_length
* in SHOW TABLE STATUS resultset */
int useDelayed; /* use potentially unsafe delayed inserts? */
} mysqlHandle;
/**
* @param i index of the database
* @param n total number of databases
*/
HighDBHandle initContentDatabase(unsigned int i,
unsigned int n) {
MYSQL_RES * sql_res;
mysqlHandle * dbh;
char * scratch;
char * cnffile;
FILE * fp;
struct passwd * pw;
size_t nX;
/* verify that .my.cnf can be found */
pw = getpwuid(getuid());
if(!pw)
DIE_STRERROR("getpwuid");
nX = strlen(pw->pw_dir)+1024;
cnffile = MALLOC(nX);
SNPRINTF(cnffile, nX, "%s/.my.cnf", pw->pw_dir);
LOG(LOG_DEBUG,
_("Trying to use file '%s' for MySQL configuration.\n"),
cnffile);
fp = FOPEN(cnffile, "r");
if (!fp) {
LOG_FILE_STRERROR(LOG_ERROR, "fopen", cnffile);
return NULL;
} else {
fclose(fp);
}
dbh = MALLOC(sizeof(mysqlHandle));
dbh->dbf = mysql_init(NULL);
dbh->i = i;
dbh->n = n;
if(dbh->dbf == NULL) {
LOG(LOG_ERROR,
_("Unable to initialize MySQL.\n"));
FREE(dbh);
return NULL;
}
if(testConfigurationString("AFS",
"MYSQL_DELAYED",
"YES"))
dbh->useDelayed = YES;
else
dbh->useDelayed = NO;
mysql_options(dbh->dbf,
MYSQL_READ_DEFAULT_FILE,
cnffile);
mysql_options(dbh->dbf,
MYSQL_READ_DEFAULT_GROUP,
"client");
mysql_real_connect(dbh->dbf,
NULL,
NULL,
NULL,
"gnunet",
0,
NULL,
0);
if (mysql_error(dbh->dbf)[0]) {
LOG_MYSQL(LOG_ERROR,
"mysql_real_connect",
dbh);
FREE(dbh);
return NULL;
}
scratch = MALLOC(1024);
SNPRINTF(scratch,
1024,
"CREATE TABLE IF NOT EXISTS data%uof%u ("
" hash tinyblob NOT NULL default'',"
" priority int(11) NOT NULL default 0,"
" type tinyint NOT NULL default 0,"
" fileIndex smallint NOT NULL default 0,"
" fileOffset int(11) NOT NULL default 0,"
" doubleHash tinyblob NOT NULL default '',"
" content mediumblob NOT NULL default '',"
" PRIMARY KEY (hash(20)),"
" KEY priority (priority)"
") TYPE=MyISAM",
dbh->n,
dbh->i);
mysql_query(dbh->dbf,
scratch);
if (mysql_error(dbh->dbf)[0]) {
LOG_MYSQL(LOG_ERROR,
"mysql_query",
dbh);
FREE(dbh);
FREE(scratch);
return NULL;
}
MUTEX_CREATE_RECURSIVE(&dbh->DATABASE_Lock_);
/* Find out which column contains the avg row length field and assume
* that mysqld always gives it in the same order across calls :) */
SNPRINTF(scratch,
1024,
"SHOW TABLE STATUS FROM gnunet LIKE 'data%uof%u'",
dbh->n,
dbh->i);
mysql_query(dbh->dbf,
scratch);
FREE(scratch);
if (mysql_error(dbh->dbf)[0]) {
LOG_MYSQL(LOG_ERROR,
"mysql_query",
dbh);
FREE(dbh);
return NULL;
}
if((sql_res=mysql_store_result(dbh->dbf))) {
MYSQL_FIELD * sql_fields;
int num_fields;
int j;
int found = NO;
num_fields=mysql_num_fields(sql_res);
sql_fields=mysql_fetch_fields(sql_res);
dbh->avgLength_ID = -1;
for(j=0;javgLength_ID=j;
break;
}
}
GNUNET_ASSERT(dbh->avgLength_ID != -1);
mysql_free_result(sql_res);
if (found == NO) {
BREAK();
/* avg_row_length not found in SHOW TABLE STATUS */
FREE(dbh);
return NULL;
}
/* FIXME: mysql manual doesn't mention if sql_fields should be freed?*/
}
return dbh;
}
/**
* Normal shutdown of the storage module
*
* @param handle the database
*/
void doneContentDatabase(HighDBHandle handle) {
mysqlHandle * dbh = handle;
MUTEX_DESTROY(&dbh->DATABASE_Lock_);
mysql_close(dbh->dbf);
FREE(dbh);
}
/**
* Call a method for each key in the database and
* call the callback method on it.
*
* @param handle the database
* @param callback the callback method
* @param data second argument to all callback calls
* @return the number of items stored in the content database
*/
int forEachEntryInDatabase(HighDBHandle handle,
EntryCallback callback,
void * data) {
mysqlHandle * dbh = handle;
MYSQL_RES *sql_res;
MYSQL_ROW sql_row;
ContentIndex ce;
void * result;
char * scratch;
int count = 0;
int len;
MUTEX_LOCK(&dbh->DATABASE_Lock_);
scratch = MALLOC(256);
SNPRINTF(scratch,
256,
"SELECT content,type,priority,doubleHash,fileOffset,fileIndex,hash "
"FROM data%uof%u",
dbh->n,
dbh->i);
mysql_query(dbh->dbf,
scratch);
FREE(scratch);
if (mysql_error(dbh->dbf)[0]) {
LOG_MYSQL(LOG_ERROR, "mysql_query", dbh);
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
return(SYSERR);
}
if (!(sql_res=mysql_use_result(dbh->dbf))) {
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
return(SYSERR);
}
while ((sql_row=mysql_fetch_row(sql_res))) {
len = mysql_fetch_lengths(sql_res)[0];
if (len > 0) {
result = MALLOC(len);
memcpy(result,
sql_row[0],
len);
} else
result = NULL;
ce.type = htons(atol(sql_row[1]));
ce.importance = htonl(atol(sql_row[2]));
if (ntohs(ce.type)==LOOKUP_TYPE_3HASH) {
if (mysql_fetch_lengths(sql_res)[3] == sizeof(HashCode160))
memcpy(&ce.hash,
sql_row[3],
sizeof(HashCode160));
} else {
memcpy(&ce.hash,
sql_row[6],
sizeof(HashCode160));
}
ce.fileOffset = htonl(atol(sql_row[4]));
ce.fileNameIndex = htons(atol(sql_row[5]));
callback((HashCode160*)sql_row[6],
&ce,
result, /* freed by callback */
len,
data);
count++;
}
mysql_free_result(sql_res);
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
return count;
}
/**
* Get the number of entries in the database.
*
* @param handle the database
* @return the number of entries
*/
int countContentEntries(HighDBHandle handle) {
mysqlHandle * dbh = handle;
MYSQL_RES * sql_res;
MYSQL_ROW sql_row;
char * scratch;
int count=0;
MUTEX_LOCK(&dbh->DATABASE_Lock_);
scratch = MALLOC(128);
SNPRINTF(scratch,
128,
"SELECT count(*) FROM data%uof%u",
dbh->n,
dbh->i);
mysql_query(dbh->dbf,
scratch);
FREE(scratch);
if (!(sql_res=mysql_store_result(dbh->dbf))) {
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
return(SYSERR);
}
if ((sql_row=mysql_fetch_row(sql_res))) {
count = atol(sql_row[0]);
}
mysql_free_result(sql_res);
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
return count;
}
/**
* Read the contents of a bucket to a buffer.
*
* @param handle the database
* @param query the hashcode representing the entry
* @param ce the meta-data of the entry (set)
* @param result the buffer to write the result to
* (*result should be NULL, sufficient space is allocated)
* @param prio by how much should the priority of the content be changed
* (if it is found)
* @return the number of bytes read on success, -1 on failure
*/
int readContent(HighDBHandle handle,
const HashCode160 * query,
ContentIndex * ce,
void ** result,
int prio) {
mysqlHandle * dbh = handle;
MYSQL_RES * sql_res;
MYSQL_ROW sql_row;
char * escapedHash;
char * scratch;
int len;
MUTEX_LOCK(&dbh->DATABASE_Lock_);
escapedHash = MALLOC(sizeof(HashCode160)*2+1);
mysql_escape_string(escapedHash,
(char *)query,
sizeof(HashCode160));
scratch = MALLOC(256);
SNPRINTF(scratch,
256,
"SELECT content,type,priority,doubleHash,fileOffset,fileIndex "
"FROM data%uof%u WHERE hash='%s'",
dbh->n,
dbh->i,
escapedHash);
mysql_query(dbh->dbf,
scratch);
if (mysql_error(dbh->dbf)[0]) {
LOG_MYSQL(LOG_ERROR, "mysql_query", dbh);
FREE(escapedHash);
FREE(scratch);
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
return SYSERR;
}
if (!(sql_res=mysql_store_result(dbh->dbf))) {
LOG_MYSQL(LOG_ERROR, "mysql_store_result", dbh);
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
FREE(escapedHash);
FREE(scratch);
return SYSERR;
}
if (!(sql_row=mysql_fetch_row(sql_res))) {
/* not error, just data not found */
mysql_free_result(sql_res);
FREE(escapedHash);
FREE(scratch);
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
return SYSERR;
}
len = mysql_fetch_lengths(sql_res)[0];
if (len > 0) {
*result = MALLOC(len);
memcpy(*result,
sql_row[0],
len);
} else
*result = NULL;
ce->type = htons(atol(sql_row[1]));
ce->importance = htonl(atol(sql_row[2]));
if (ntohs(ce->type)==LOOKUP_TYPE_3HASH) {
if (mysql_fetch_lengths(sql_res)[3] == sizeof(HashCode160))
memcpy(&ce->hash,
sql_row[3],
sizeof(HashCode160));
} else {
memcpy(&ce->hash,
query,
sizeof(HashCode160));
}
ce->fileOffset = htonl(atol(sql_row[4]));
ce->fileNameIndex = htons(atol(sql_row[5]));
mysql_free_result(sql_res);
#if TRACK_3HASH_QUERIES
if (ntohs(ce->type)==LOOKUP_TYPE_3HASH) {
HexName hex;
hash2hex(query,
&hex);
SNPRINTF(scratch,
256,
"UPDATE dictionary SET hits=hits+1 WHERE hash='%s'",
(char*)&hex);
mysql_query(dbh->dbf,scratch);
}
#endif
if (prio != 0) {
SNPRINTF(scratch,
256,
"UPDATE data%uof%u SET priority=priority+%d WHERE hash='%s'",
dbh->n,
dbh->i,
prio,
escapedHash);
mysql_query(dbh->dbf,scratch);
}
FREE(escapedHash);
FREE(scratch);
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
return len;
}
/**
* Write content to the db. Overwrites existing data.
* If ce->type is LOOKUP_TYPE_3HASH, ce->hash will contain
* a double hash which must be converted to 3HASH, later to be
* retrievable by 3HASH, but the 2HASH must be stored so it can
* be retrieved by readContent(). For indexed content,
* ce->fileOffset and ce->fileNameIndex must be stored.
* Note that block can be NULL for on-demand encoded content
* (in this case, len must also be 0).
*
* @param handle the database
* @param ce the meta-data for the entry
* @param len the size of the block
* @param block the data to store
* @return SYSERR on error, OK if ok.
*/
int writeContent(HighDBHandle handle,
const ContentIndex * ce,
unsigned int len,
const void * block) {
mysqlHandle * dbh = handle;
HashCode160 tripleHash;
char * doubleHash;
char * escapedBlock;
char * escapedHash;
char * scratch;
int n;
MUTEX_LOCK(&dbh->DATABASE_Lock_);
escapedHash = MALLOC(2*sizeof(HashCode160)+1);
if(ntohs(ce->type) == LOOKUP_TYPE_3HASH) {
hash(&ce->hash,
sizeof(HashCode160),
&tripleHash);
mysql_escape_string(escapedHash,
(char *)&tripleHash,
sizeof(HashCode160));
doubleHash = MALLOC(2*sizeof(HashCode160)+1);
mysql_escape_string(doubleHash,
(char *)&ce->hash,
sizeof(HashCode160));
} else {
doubleHash = NULL;
mysql_escape_string(escapedHash,
(char *)&ce->hash,
sizeof(HashCode160));
}
escapedBlock = MALLOC(2*len+1);
mysql_escape_string(escapedBlock,
(char *)block,
len);
n = len*2+sizeof(HexName)*2+sizeof(HashCode160)*2+100+1;
scratch = MALLOC(n);
SNPRINTF(scratch,
n,
"REPLACE %s INTO data%uof%u "
"(content,hash,priority,fileOffset,fileIndex,doubleHash,type)"
" VALUES ('%s','%s','%u','%u','%u','%s',%u)",
(dbh->useDelayed == YES ? "DELAYED" : ""),
dbh->n,
dbh->i,
(len > 0 ? escapedBlock : ""),
escapedHash,
ntohl(ce->importance),
ntohl(ce->fileOffset),
ntohs(ce->fileNameIndex),
(doubleHash ? doubleHash : ""),
ntohs(ce->type));
mysql_query(dbh->dbf, scratch);
FREE(scratch);
FREE(escapedBlock);
FREE(escapedHash);
FREENONNULL(doubleHash);
if(mysql_error(dbh->dbf)[0]) {
LOG_MYSQL(LOG_ERROR, "mysql_query", dbh);
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
return SYSERR;
}
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
return OK;
}
/**
* Free space in the database by removing one block
*
* @param handle the database
* @param name hashcode for the block to be deleted
*/
int unlinkFromDB(HighDBHandle handle,
const HashCode160 * name) {
mysqlHandle * dbh = handle;
char * escapedHash;
char * scratch;
size_t n;
MUTEX_LOCK(&dbh->DATABASE_Lock_);
escapedHash = MALLOC(2*sizeof(HashCode160)+1);
mysql_escape_string(escapedHash, (char *)name, sizeof(HashCode160));
n = sizeof(HashCode160)*2+100+1;
scratch=MALLOC(n);
SNPRINTF(scratch,
n,
"DELETE FROM data%uof%u WHERE hash='%s'",
dbh->n,
dbh->i,
escapedHash);
mysql_query(dbh->dbf,scratch);
FREE(escapedHash);
FREE(scratch);
if(mysql_error(dbh->dbf)[0]) {
LOG_MYSQL(LOG_ERROR, "mysql_query", dbh);
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
return SYSERR;
}
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
return OK;
}
/**
* Get a random content block from MySQL database.
* Tries to use indexes efficiently.
*
* Code supplied by H. Pagenhardt
*
* @param handle the database
* @param ce the meta-data of the random content (set)
* @return OK on success, SYSERR on error
*/
int getRandomContent(HighDBHandle handle,
ContentIndex * ce,
CONTENT_Block ** data) {
mysqlHandle * dbh = handle;
MYSQL_RES * sql_res;
MYSQL_ROW sql_row;
char * escapedHash;
char * hash;
char * scratch;
int i;
int found;
size_t n;
#if DEBUG_TIME_MYSQL
cron_t startTime;
cron_t endTime;
static cron_t spentTime=0;
static int calls = 0;
calls++;
cronTime(&startTime);
#endif
MUTEX_LOCK(&dbh->DATABASE_Lock_);
hash = MALLOC(sizeof(HashCode160));
escapedHash = MALLOC(2*sizeof(HashCode160)+1);
n = 2*sizeof(HashCode160)+256+1;
scratch = MALLOC(n);
found = NO;
for (i=0;i= '%s' "
"AND (type = %d OR type = %d) "
"LIMIT 1",
dbh->n,
dbh->i,
escapedHash,
LOOKUP_TYPE_CHK,
LOOKUP_TYPE_CHKS);
mysql_query(dbh->dbf, scratch);
if(mysql_error(dbh->dbf)[0]) {
LOG_MYSQL(LOG_ERROR, "mysql_query", dbh);
FREE(scratch);
FREE(escapedHash);
FREE(hash);
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
return SYSERR;
}
if(!(sql_res=mysql_store_result(dbh->dbf))) {
LOG_MYSQL(LOG_ERROR, "mysql_store_result", dbh);
FREE(scratch);
FREE(escapedHash);
FREE(hash);
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
return SYSERR;
}
if (mysql_num_rows(sql_res)==0) {
mysql_free_result(sql_res);
SNPRINTF(scratch,
n,
"SELECT hash,type,priority,fileOffset,fileIndex,content "
"FROM data%uof%u "
"WHERE hash >= '' "
"AND (type = %d OR type = %d) "
"LIMIT 1",
dbh->n,
dbh->i,
LOOKUP_TYPE_CHK,
LOOKUP_TYPE_CHKS);
mysql_query(dbh->dbf, scratch);
if(mysql_error(dbh->dbf)[0]) {
LOG_MYSQL(LOG_ERROR, "mysql_query", dbh);
FREE(scratch);
FREE(escapedHash);
FREE(hash);
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
return SYSERR;
}
if(!(sql_res=mysql_store_result(dbh->dbf))) {
LOG_MYSQL(LOG_ERROR, "mysql_store_result", dbh);
FREE(scratch);
FREE(escapedHash);
FREE(hash);
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
return SYSERR;
}
}
if(mysql_num_rows(sql_res)>0) {
if(!(sql_row=mysql_fetch_row(sql_res))) {
LOG_MYSQL(LOG_ERROR, "mysql_num_rows", dbh);
FREE(scratch);
FREE(escapedHash);
FREE(hash);
mysql_free_result(sql_res);
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
return SYSERR;
}
memcpy(&ce->hash,
sql_row[0],
sizeof(HashCode160));
ce->type = htons(atol(sql_row[1]));
ce->importance = htonl(atol(sql_row[2]));
ce->fileOffset = htonl(atol(sql_row[3]));
ce->fileNameIndex = htons(atol(sql_row[4]));
if(ntohs(ce->fileNameIndex)==0) {
*data = MALLOC(sizeof(CONTENT_Block));
memcpy(*data,
sql_row[5],
sizeof(CONTENT_Block));
}
found = YES;
mysql_free_result(sql_res);
}
FREE(scratch);
FREE(escapedHash);
FREE(hash);
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
#if DEBUG_TIME_MYSQL
cronTime(&endTime);
spentTime = spentTime + (endTime-startTime);
LOG(LOG_DEBUG,
"Spent total %lldms / %d calls\n", spentTime, calls);
#endif
if(found==YES) {
return OK;
} else {
LOG(LOG_DEBUG,
"'%s' did not find anything!\n",
__FUNCTION__);
return SYSERR;
}
}
/**
* Get the lowest priority value of all content in the store.
*
* @param handle the database
* @return the lowest priority
*/
unsigned int getMinimumPriority(HighDBHandle handle) {
mysqlHandle * dbh = handle;
MYSQL_RES * sql_res;
MYSQL_ROW sql_row;
char * scratch;
unsigned int minPrio = 0;
MUTEX_LOCK(&dbh->DATABASE_Lock_);
scratch = MALLOC(256);
SNPRINTF(scratch,
256,
"SELECT MIN(priority) FROM data%uof%u",
dbh->n,
dbh->i);
mysql_query(dbh->dbf, scratch);
FREE(scratch);
if (!(sql_res=mysql_store_result(dbh->dbf))) {
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
return 0;
}
if (mysql_num_rows(sql_res)==0) {
mysql_free_result(sql_res);
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
return 0; /* no entries in DB */
}
if (NULL != (sql_row=mysql_fetch_row(sql_res))) {
if (sql_row[0] != NULL)
minPrio = atol(sql_row[0]);
else
minPrio = 0; /* error? */
}
mysql_free_result(sql_res);
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
return minPrio;
}
/**
* Deletes some least important content
*
* @param handle the database
* @param count the number of entries to delete
* @param callback method to call on each deleted entry
* @param closure extra argument to callback
* @return OK on success, SYSERR on error
*/
int deleteContent(HighDBHandle handle,
unsigned int count,
EntryCallback callback,
void * closure) {
mysqlHandle * dbh = handle;
MYSQL_RES * sql_res;
MYSQL_ROW sql_row;
HashCode160 * deleteThese;
char * escapedHash;
char * scratch;
int i=0;
MUTEX_LOCK(&dbh->DATABASE_Lock_);
scratch = MALLOC(256);
/* Collect hashes to delete */
SNPRINTF(scratch,
256,
"SELECT hash FROM data%uof%u "
"ORDER BY priority ASC LIMIT %d",
dbh->n,
dbh->i,
count);
mysql_query(dbh->dbf, scratch);
if (mysql_error(dbh->dbf)[0]) {
LOG_MYSQL(LOG_ERROR, "mysql_query", dbh);
FREE(scratch);
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
return SYSERR;
}
if (!(sql_res=mysql_use_result(dbh->dbf))) {
FREE(scratch);
LOG_MYSQL(LOG_ERROR, "mysql_use_result", dbh);
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
return SYSERR;
}
deleteThese = MALLOC(count*sizeof(HashCode160));
i=0;
while ((sql_row=mysql_fetch_row(sql_res))) {
memcpy(&deleteThese[i++],
sql_row[0],
sizeof(HashCode160));
}
mysql_free_result(sql_res);
/* Delete collected hashes */
count=i;
escapedHash = MALLOC(2*sizeof(HashCode160)+1);
for(i=0;i= 0) {
if (callback != NULL) {
callback(&deleteThese[i],
&ce,
data,
dlen,
closure);
} else {
FREENONNULL(data);
}
}
mysql_escape_string(escapedHash,
(char *)&deleteThese[i],
sizeof(HashCode160));
SNPRINTF(scratch,
256,
"DELETE FROM data%uof%u WHERE hash='%s'",
dbh->n,
dbh->i,
escapedHash);
mysql_query(dbh->dbf,scratch);
if(mysql_error(dbh->dbf)[0])
LOG_MYSQL(LOG_ERROR, "mysql_query", dbh);
}
FREE(escapedHash);
FREE(scratch);
FREE(deleteThese);
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
return OK;
}
/**
* Estimate how many blocks can be stored in the DB
* before the quota is reached.
*
* NOTE: this function can not be performed relying on
* Data_length+Index_length from "SHOW TABLE STATUS" because
* those values seem not to be decreasing in real time.
* On mysql 4.0.16, Avg_row_len seems to be updating in real
* time w.r.t. insertions and deletions.
*
* @param handle the database
* @param quota the number of kb available for the DB
* @return number of blocks left
*/
int estimateAvailableBlocks(HighDBHandle handle,
unsigned int quota) {
mysqlHandle * dbh = handle;
char * scratch;
MYSQL_RES * sql_res;
MYSQL_ROW sql_row;
long long avgRowLen = -1;
long long rowsInTable = 0;
unsigned int kbUsed;
MUTEX_LOCK(&dbh->DATABASE_Lock_);
/* find out average row length in bytes */
/* FIXME: probably unnecessary to check avg row length every time */
scratch = MALLOC(512);
SNPRINTF(scratch,
512,
"SHOW TABLE STATUS FROM gnunet LIKE 'data%uof%u'",
dbh->n,
dbh->i);
mysql_query(dbh->dbf,
scratch);
if (mysql_error(dbh->dbf)[0]) {
DIE_MYSQL("mysql_query", dbh); /* this MUST not fail... */
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
FREE(scratch);
return SYSERR; /* shouldn't get here */
}
if ((sql_res=mysql_store_result(dbh->dbf))) {
int rows = mysql_num_fields(sql_res);
sql_row = mysql_fetch_row(sql_res);
if (sql_row == NULL) {
LOG(LOG_ERROR,
_("Query '%s' had no results.\n"),
scratch);
FREE(scratch);
GNUNET_ASSERT(0); /* not allowed to fail*/
return SYSERR; /* shouldn't get here */
}
GNUNET_ASSERT( (dbh->avgLength_ID < rows) &&
(dbh->avgLength_ID >= 0) );
if (sql_row[dbh->avgLength_ID] != NULL)
avgRowLen = atoll(sql_row[dbh->avgLength_ID]);
else
avgRowLen = -1;
mysql_free_result(sql_res);
}
GNUNET_ASSERT(avgRowLen >= 0);
/* find number of entries (rows) */
SNPRINTF(scratch,
512,
"SELECT count(*) FROM data%uof%u",
dbh->n,
dbh->i);
mysql_query(dbh->dbf,
scratch);
FREE(scratch);
if (!(sql_res=mysql_store_result(dbh->dbf)))
DIE_MYSQL("mysql_store_result", dbh);
if ((sql_row=mysql_fetch_row(sql_res))) {
int cols = mysql_num_fields(sql_res);
GNUNET_ASSERT(cols > 0);
if (sql_row[0] != NULL)
rowsInTable = atoll(sql_row[0]);
else
rowsInTable = 0;
}
mysql_free_result(sql_res);
MUTEX_UNLOCK(&dbh->DATABASE_Lock_);
kbUsed = (unsigned int)((rowsInTable * avgRowLen) / 1024);
#if DEBUG_MYSQL
LOG(LOG_DEBUG,
"estimateContentAvailable (q=%d,u=%ud,rem=%d)\n",
quota,
kbUsed,
quota-kbUsed);
#endif
return quota - kbUsed;
}
/**
* Close and delete the database.
*
* @param handle the database
*/
void deleteDatabase(HighDBHandle handle) {
mysqlHandle * dbh = handle;
char * scratch;
MUTEX_DESTROY(&dbh->DATABASE_Lock_);
scratch = MALLOC(128);
SNPRINTF(scratch,
128,
"DROP TABLE data%uof%u",
dbh->n,
dbh->i);
mysql_query(dbh->dbf,
scratch);
FREE(scratch);
mysql_close(dbh->dbf);
FREE(dbh);
}
/* end of high_mysql.c */