diff options
author | Christian Grothoff <christian@grothoff.org> | 2013-10-16 19:32:52 +0000 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2013-10-16 19:32:52 +0000 |
commit | 6308c2556c54ea8a19b33bfe16bd2f81eae65e86 (patch) | |
tree | 2017381fa55744868e3664b59a46d60cce8c2433 /src/namecache/plugin_namecache_postgres.c | |
parent | e71d2567fc6d2634c503587ba481cc92f5f5e60e (diff) |
-copied block-related functions from namestore to namecache
Diffstat (limited to 'src/namecache/plugin_namecache_postgres.c')
-rw-r--r-- | src/namecache/plugin_namecache_postgres.c | 440 |
1 files changed, 440 insertions, 0 deletions
diff --git a/src/namecache/plugin_namecache_postgres.c b/src/namecache/plugin_namecache_postgres.c new file mode 100644 index 0000000000..72cfcf243d --- /dev/null +++ b/src/namecache/plugin_namecache_postgres.c @@ -0,0 +1,440 @@ + /* + * This file is part of GNUnet + * (C) 2009-2013 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 3, 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 namecache/plugin_namecache_postgres.c + * @brief postgres-based namecache backend + * @author Christian Grothoff + */ +#include "platform.h" +#include "gnunet_namecache_plugin.h" +#include "gnunet_namecache_service.h" +#include "gnunet_gnsrecord_lib.h" +#include "gnunet_postgres_lib.h" +#include "namecache.h" + + +/** + * After how many ms "busy" should a DB operation fail for good? + * A low value makes sure that we are more responsive to requests + * (especially PUTs). A high value guarantees a higher success + * rate (SELECTs in iterate can take several seconds despite LIMIT=1). + * + * The default value of 1s should ensure that users do not experience + * huge latencies while at the same time allowing operations to succeed + * with reasonable probability. + */ +#define BUSY_TIMEOUT_MS 1000 + + +/** + * 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_POSTGRES(db, level, cmd) do { GNUNET_log_from (level, "namecache-postgres", _("`%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, sqlite3_errmsg(db->dbh)); } while(0) + +#define LOG(kind,...) GNUNET_log_from (kind, "namecache-postgres", __VA_ARGS__) + + +/** + * Context for all functions in this plugin. + */ +struct Plugin +{ + + const struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Native Postgres database handle. + */ + PGconn *dbh; + +}; + + +/** + * Create our database indices. + * + * @param dbh handle to the database + */ +static void +create_indices (PGconn * dbh) +{ + /* create indices */ + if ( (GNUNET_OK != + GNUNET_POSTGRES_exec (dbh, + "CREATE INDEX ir_query_hash ON ns096blocks (query,expiration_time)")) || + (GNUNET_OK != + GNUNET_POSTGRES_exec (dbh, + "CREATE INDEX ir_block_expiration ON ns096blocks (expiration_time)")) ) + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Failed to create indices\n")); +} + + +/** + * Initialize the database connections and associated + * data structures (create tables and indices + * as needed as well). + * + * @param plugin the plugin context (state for this module) + * @return GNUNET_OK on success + */ +static int +database_setup (struct Plugin *plugin) +{ + PGresult *res; + + plugin->dbh = GNUNET_POSTGRES_connect (plugin->cfg, + "namecache-postgres"); + if (NULL == plugin->dbh) + return GNUNET_SYSERR; + if (GNUNET_YES == + GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg, + "namecache-postgres", + "TEMPORARY_TABLE")) + { + res = + PQexec (plugin->dbh, + "CREATE TEMPORARY TABLE ns096blocks (" + " query BYTEA NOT NULL DEFAULT ''," + " block BYTEA NOT NULL DEFAULT ''," + " expiration_time BIGINT NOT NULL DEFAULT 0" + ")" "WITH OIDS"); + } + else + { + res = + PQexec (plugin->dbh, + "CREATE TABLE ns096blocks (" + " query BYTEA NOT NULL DEFAULT ''," + " block BYTEA NOT NULL DEFAULT ''," + " expiration_time BIGINT NOT NULL DEFAULT 0" + ")" "WITH OIDS"); + } + if ( (NULL == res) || + ((PQresultStatus (res) != PGRES_COMMAND_OK) && + (0 != strcmp ("42P07", /* duplicate table */ + PQresultErrorField + (res, + PG_DIAG_SQLSTATE))))) + { + (void) GNUNET_POSTGRES_check_result (plugin->dbh, res, + PGRES_COMMAND_OK, "CREATE TABLE", + "ns096blocks"); + PQfinish (plugin->dbh); + plugin->dbh = NULL; + return GNUNET_SYSERR; + } + if (PQresultStatus (res) == PGRES_COMMAND_OK) + create_indices (plugin->dbh); + PQclear (res); + + if ((GNUNET_OK != + GNUNET_POSTGRES_prepare (plugin->dbh, + "cache_block", + "INSERT INTO ns096blocks (query, block, expiration_time) VALUES " + "($1, $2, $3)", 3)) || + (GNUNET_OK != + GNUNET_POSTGRES_prepare (plugin->dbh, + "expire_blocks", + "DELETE FROM ns096blocks WHERE expiration_time<$1", 1)) || + (GNUNET_OK != + GNUNET_POSTGRES_prepare (plugin->dbh, + "delete_block", + "DELETE FROM ns096blocks WHERE query=$1 AND expiration_time<=$2", 2)) || + (GNUNET_OK != + GNUNET_POSTGRES_prepare (plugin->dbh, + "lookup_block", + "SELECT block FROM ns096blocks WHERE query=$1" + " ORDER BY expiration_time DESC LIMIT 1", 1))) + { + PQfinish (plugin->dbh); + plugin->dbh = NULL; + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Removes any expired block. + * + * @param plugin the plugin + */ +static void +namecache_postgres_expire_blocks (struct Plugin *plugin) +{ + struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); + struct GNUNET_TIME_AbsoluteNBO now_be = GNUNET_TIME_absolute_hton (now); + const char *paramValues[] = { + (const char *) &now_be + }; + int paramLengths[] = { + sizeof (now_be) + }; + const int paramFormats[] = { 1 }; + PGresult *res; + + res = + PQexecPrepared (plugin->dbh, "expire_blocks", 1, + paramValues, paramLengths, paramFormats, 1); + if (GNUNET_OK != + GNUNET_POSTGRES_check_result (plugin->dbh, + res, + PGRES_COMMAND_OK, + "PQexecPrepared", + "expire_blocks")) + return; + PQclear (res); +} + + +/** + * Delete older block in the datastore. + * + * @param the plugin + * @param query query for the block + * @param expiration time how old does the block have to be for deletion + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static void +delete_old_block (struct Plugin *plugin, + const struct GNUNET_HashCode *query, + struct GNUNET_TIME_AbsoluteNBO expiration_time) +{ + const char *paramValues[] = { + (const char *) query, + (const char *) &expiration_time + }; + int paramLengths[] = { + sizeof (*query), + sizeof (expiration_time) + }; + const int paramFormats[] = { 1, 1 }; + PGresult *res; + + res = + PQexecPrepared (plugin->dbh, "delete_block", 2, + paramValues, paramLengths, paramFormats, 1); + if (GNUNET_OK != + GNUNET_POSTGRES_check_result (plugin->dbh, + res, + PGRES_COMMAND_OK, + "PQexecPrepared", + "delete_block")) + return; + PQclear (res); +} + + +/** + * Cache a block in the datastore. + * + * @param cls closure (internal context for the plugin) + * @param block block to cache + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +namecache_postgres_cache_block (void *cls, + const struct GNUNET_NAMESTORE_Block *block) +{ + struct Plugin *plugin = cls; + struct GNUNET_HashCode query; + size_t block_size = ntohl (block->purpose.size) + + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) + + sizeof (struct GNUNET_CRYPTO_EcdsaSignature); + const char *paramValues[] = { + (const char *) &query, + (const char *) block, + (const char *) &block->expiration_time + }; + int paramLengths[] = { + sizeof (query), + (int) block_size, + sizeof (block->expiration_time) + }; + const int paramFormats[] = { 1, 1, 1 }; + PGresult *res; + + namecache_postgres_expire_blocks (plugin); + GNUNET_CRYPTO_hash (&block->derived_key, + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey), + &query); + if (block_size > 64 * 65536) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + delete_old_block (plugin, &query, block->expiration_time); + + res = + PQexecPrepared (plugin->dbh, "cache_block", 3, + paramValues, paramLengths, paramFormats, 1); + if (GNUNET_OK != + GNUNET_POSTGRES_check_result (plugin->dbh, + res, + PGRES_COMMAND_OK, + "PQexecPrepared", + "cache_block")) + return GNUNET_SYSERR; + PQclear (res); + return GNUNET_OK; +} + + +/** + * Get the block for a particular zone and label in the + * datastore. Will return at most one result to the iterator. + * + * @param cls closure (internal context for the plugin) + * @param query hash of public key derived from the zone and the label + * @param iter function to call with the result + * @param iter_cls closure for @a iter + * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error + */ +static int +namecache_postgres_lookup_block (void *cls, + const struct GNUNET_HashCode *query, + GNUNET_NAMECACHE_BlockCallback iter, void *iter_cls) +{ + struct Plugin *plugin = cls; + const char *paramValues[] = { + (const char *) query + }; + int paramLengths[] = { + sizeof (*query) + }; + const int paramFormats[] = { 1 }; + PGresult *res; + unsigned int cnt; + size_t bsize; + const struct GNUNET_NAMESTORE_Block *block; + + res = PQexecPrepared (plugin->dbh, + "lookup_block", 1, + paramValues, paramLengths, paramFormats, + 1); + if (GNUNET_OK != + GNUNET_POSTGRES_check_result (plugin->dbh, res, PGRES_TUPLES_OK, + "PQexecPrepared", + "lookup_block")) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Failing lookup (postgres error)\n"); + return GNUNET_SYSERR; + } + if (0 == (cnt = PQntuples (res))) + { + /* no result */ + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Ending iteration (no more results)\n"); + PQclear (res); + return GNUNET_NO; + } + GNUNET_assert (1 == cnt); + GNUNET_assert (1 != PQnfields (res)); + bsize = PQgetlength (res, 0, 0); + block = (const struct GNUNET_NAMESTORE_Block *) PQgetvalue (res, 0, 0); + if ( (bsize < sizeof (*block)) || + (bsize != ntohl (block->purpose.size) + + sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) + + sizeof (struct GNUNET_CRYPTO_EcdsaSignature)) ) + { + GNUNET_break (0); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Failing lookup (corrupt block)\n"); + PQclear (res); + return GNUNET_SYSERR; + } + iter (iter_cls, block); + PQclear (res); + return GNUNET_OK; +} + + +/** + * Shutdown database connection and associate data + * structures. + * + * @param plugin the plugin context (state for this module) + */ +static void +database_shutdown (struct Plugin *plugin) +{ + PQfinish (plugin->dbh); + plugin->dbh = NULL; +} + + +/** + * Entry point for the plugin. + * + * @param cls the "struct GNUNET_NAMECACHE_PluginEnvironment*" + * @return NULL on error, othrewise the plugin context + */ +void * +libgnunet_plugin_namecache_postgres_init (void *cls) +{ + static struct Plugin plugin; + const struct GNUNET_CONFIGURATION_Handle *cfg = cls; + struct GNUNET_NAMECACHE_PluginFunctions *api; + + if (NULL != plugin.cfg) + return NULL; /* can only initialize once! */ + memset (&plugin, 0, sizeof (struct Plugin)); + plugin.cfg = cfg; + if (GNUNET_OK != database_setup (&plugin)) + { + database_shutdown (&plugin); + return NULL; + } + api = GNUNET_new (struct GNUNET_NAMECACHE_PluginFunctions); + api->cls = &plugin; + api->cache_block = &namecache_postgres_cache_block; + api->lookup_block = &namecache_postgres_lookup_block; + LOG (GNUNET_ERROR_TYPE_INFO, + _("Postgres database running\n")); + return api; +} + + +/** + * Exit point from the plugin. + * + * @param cls the plugin context (as returned by "init") + * @return always NULL + */ +void * +libgnunet_plugin_namecache_postgres_done (void *cls) +{ + struct GNUNET_NAMECACHE_PluginFunctions *api = cls; + struct Plugin *plugin = api->cls; + + database_shutdown (plugin); + plugin->cfg = NULL; + GNUNET_free (api); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "postgres plugin is finished\n"); + return NULL; +} + +/* end of plugin_namecache_postgres.c */ |