aboutsummaryrefslogtreecommitdiff
path: root/src/namecache/plugin_namecache_postgres.c
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2013-10-16 19:32:52 +0000
committerChristian Grothoff <christian@grothoff.org>2013-10-16 19:32:52 +0000
commit6308c2556c54ea8a19b33bfe16bd2f81eae65e86 (patch)
tree2017381fa55744868e3664b59a46d60cce8c2433 /src/namecache/plugin_namecache_postgres.c
parente71d2567fc6d2634c503587ba481cc92f5f5e60e (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.c440
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 */