-fix leaks
[oweals/gnunet.git] / src / namestore / plugin_namestore_postgres.c
1  /*
2   * This file is part of GNUnet
3   * (C) 2009-2013 Christian Grothoff (and other contributing authors)
4   *
5   * GNUnet is free software; you can redistribute it and/or modify
6   * it under the terms of the GNU General Public License as published
7   * by the Free Software Foundation; either version 3, or (at your
8   * option) any later version.
9   *
10   * GNUnet is distributed in the hope that it will be useful, but
11   * WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   * General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with GNUnet; see the file COPYING.  If not, write to the
17   * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18   * Boston, MA 02111-1307, USA.
19   */
20
21 /**
22  * @file namestore/plugin_namestore_postgres.c
23  * @brief postgres-based namestore backend
24  * @author Christian Grothoff
25  */
26 #include "platform.h"
27 #include "gnunet_namestore_plugin.h"
28 #include "gnunet_namestore_service.h"
29 #include "gnunet_gnsrecord_lib.h"
30 #include "gnunet_postgres_lib.h"
31 #include "namestore.h"
32
33
34 /**
35  * After how many ms "busy" should a DB operation fail for good?
36  * A low value makes sure that we are more responsive to requests
37  * (especially PUTs).  A high value guarantees a higher success
38  * rate (SELECTs in iterate can take several seconds despite LIMIT=1).
39  *
40  * The default value of 1s should ensure that users do not experience
41  * huge latencies while at the same time allowing operations to succeed
42  * with reasonable probability.
43  */
44 #define BUSY_TIMEOUT_MS 1000
45
46
47 /**
48  * Log an error message at log-level 'level' that indicates
49  * a failure of the command 'cmd' on file 'filename'
50  * with the message given by strerror(errno).
51  */
52 #define LOG_POSTGRES(db, level, cmd) do { GNUNET_log_from (level, "namestore-postgres", _("`%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, sqlite3_errmsg(db->dbh)); } while(0)
53
54 #define LOG(kind,...) GNUNET_log_from (kind, "namestore-postgres", __VA_ARGS__)
55
56
57 /**
58  * Context for all functions in this plugin.
59  */
60 struct Plugin
61 {
62
63   const struct GNUNET_CONFIGURATION_Handle *cfg;
64
65   /**
66    * Native Postgres database handle.
67    */
68   PGconn *dbh;
69
70 };
71
72
73 /**
74  * Create our database indices.
75  *
76  * @param dbh handle to the database
77  */
78 static void
79 create_indices (PGconn * dbh)
80 {
81   /* create indices */
82   if ( (GNUNET_OK !=
83         GNUNET_POSTGRES_exec (dbh,
84                               "CREATE INDEX ir_pkey_reverse ON ns097records (zone_private_key,pkey)")) ||
85        (GNUNET_OK !=
86         GNUNET_POSTGRES_exec (dbh,
87                               "CREATE INDEX ir_pkey_iter ON ns097records (zone_private_key,rvalue)")) ||
88        (GNUNET_OK !=
89         GNUNET_POSTGRES_exec (dbh,
90                               "CREATE INDEX it_iter ON ns097records (rvalue)")) )
91     LOG (GNUNET_ERROR_TYPE_ERROR,
92          _("Failed to create indices\n"));
93 }
94
95
96 /**
97  * Initialize the database connections and associated
98  * data structures (create tables and indices
99  * as needed as well).
100  *
101  * @param plugin the plugin context (state for this module)
102  * @return GNUNET_OK on success
103  */
104 static int
105 database_setup (struct Plugin *plugin)
106 {
107   PGresult *res;
108
109   plugin->dbh = GNUNET_POSTGRES_connect (plugin->cfg,
110                                          "namestore-postgres");
111   if (NULL == plugin->dbh)
112     return GNUNET_SYSERR;
113   if (GNUNET_YES ==
114       GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg,
115                                             "namestore-postgres",
116                                             "TEMPORARY_TABLE"))
117   {
118     res =
119       PQexec (plugin->dbh,
120               "CREATE TEMPORARY TABLE ns097records ("
121               " zone_private_key BYTEA NOT NULL DEFAULT '',"
122               " pkey BYTEA DEFAULT '',"
123               " rvalue BYTEA NOT NULL DEFAULT '',"
124               " record_count INTEGER NOT NULL DEFAULT 0,"
125               " record_data BYTEA NOT NULL DEFAULT '',"
126               " label TEXT NOT NULL DEFAULT ''"
127               ")" "WITH OIDS");
128   }
129   else
130   {
131     res =
132       PQexec (plugin->dbh,
133               "CREATE TABLE ns097records ("
134               " zone_private_key BYTEA NOT NULL DEFAULT '',"
135               " pkey BYTEA DEFAULT '',"
136               " rvalue BYTEA NOT NULL DEFAULT '',"
137               " record_count INTEGER NOT NULL DEFAULT 0,"
138               " record_data BYTEA NOT NULL DEFAULT '',"
139               " label TEXT NOT NULL DEFAULT ''"
140               ")" "WITH OIDS");
141   }
142   if ( (NULL == res) ||
143        ((PQresultStatus (res) != PGRES_COMMAND_OK) &&
144         (0 != strcmp ("42P07",    /* duplicate table */
145                       PQresultErrorField
146                       (res,
147                        PG_DIAG_SQLSTATE)))))
148   {
149     (void) GNUNET_POSTGRES_check_result (plugin->dbh, res,
150                                          PGRES_COMMAND_OK, "CREATE TABLE",
151                                          "ns097records");
152     PQfinish (plugin->dbh);
153     plugin->dbh = NULL;
154     return GNUNET_SYSERR;
155   }
156   create_indices (plugin->dbh);
157
158   if ((GNUNET_OK !=
159        GNUNET_POSTGRES_prepare (plugin->dbh,
160                                 "store_records",
161                                 "INSERT INTO ns097records (zone_private_key, pkey, rvalue, record_count, record_data, label) VALUES "
162                                 "($1, $2, $3, $4, $5, $6)", 6)) ||
163       (GNUNET_OK !=
164        GNUNET_POSTGRES_prepare (plugin->dbh,
165                                 "delete_records",
166                                 "DELETE FROM ns097records WHERE zone_private_key=$1 AND label=$2", 2)) ||
167       (GNUNET_OK !=
168        GNUNET_POSTGRES_prepare (plugin->dbh,
169                                 "zone_to_name",
170                                 "SELECT record_count,record_data,label FROM ns097records"
171                                 " WHERE zone_private_key=$1 AND pkey=$2", 2)) ||
172       (GNUNET_OK !=
173        GNUNET_POSTGRES_prepare (plugin->dbh,
174                                 "iterate_zone",
175                                 "SELECT record_count, record_data, label FROM ns097records"
176                                 " WHERE zone_private_key=$1 ORDER BY rvalue LIMIT 1 OFFSET $2", 2)) ||
177       (GNUNET_OK !=
178        GNUNET_POSTGRES_prepare (plugin->dbh,
179                                 "iterate_all_zones",
180                                 "SELECT record_count,record_data,label,zone_private_key"
181                                 " FROM ns097records ORDER BY rvalue LIMIT 1 OFFSET $1", 1)))
182   {
183     PQfinish (plugin->dbh);
184     plugin->dbh = NULL;
185     return GNUNET_SYSERR;
186   }
187   return GNUNET_OK;
188 }
189
190
191 /**
192  * Store a record in the datastore.  Removes any existing record in the
193  * same zone with the same name.
194  *
195  * @param cls closure (internal context for the plugin)
196  * @param zone_key private key of the zone
197  * @param label name that is being mapped (at most 255 characters long)
198  * @param rd_count number of entries in @a rd array
199  * @param rd array of records with data to store
200  * @return #GNUNET_OK on success, else #GNUNET_SYSERR
201  */
202 static int
203 namestore_postgres_store_records (void *cls,
204                                   const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone_key,
205                                   const char *label,
206                                   unsigned int rd_count,
207                                   const struct GNUNET_GNSRECORD_Data *rd)
208 {
209   struct Plugin *plugin = cls;
210   struct GNUNET_CRYPTO_EcdsaPublicKey pkey;
211   uint64_t rvalue;
212   uint32_t rd_count_nbo = htonl ((uint32_t) rd_count);
213   size_t data_size;
214   unsigned int i;
215
216   memset (&pkey, 0, sizeof (pkey));
217   for (i=0;i<rd_count;i++)
218     if (GNUNET_GNSRECORD_TYPE_PKEY == rd[i].record_type)
219     {
220       GNUNET_break (sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) == rd[i].data_size);
221       memcpy (&pkey,
222               rd[i].data,
223               rd[i].data_size);
224       break;
225     }
226   rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX);
227   data_size = GNUNET_GNSRECORD_records_get_size (rd_count, rd);
228   if (data_size > 64 * 65536)
229   {
230     GNUNET_break (0);
231     return GNUNET_SYSERR;
232   }
233   {
234     char data[data_size];
235     const char *paramValues[] = {
236       (const char *) zone_key,
237       (const char *) &pkey,
238       (const char *) &rvalue,
239       (const char *) &rd_count_nbo,
240       (const char *) data,
241       label
242     };
243     int paramLengths[] = {
244       sizeof (*zone_key),
245       sizeof (pkey),
246       sizeof (rvalue),
247       sizeof (rd_count_nbo),
248       data_size,
249       strlen (label)
250     };
251     const int paramFormats[] = { 1, 1, 1, 1, 1, 1 };
252     PGresult *res;
253
254     if (data_size != GNUNET_GNSRECORD_records_serialize (rd_count, rd,
255                                                          data_size, data))
256     {
257       GNUNET_break (0);
258       return GNUNET_SYSERR;
259     }
260
261     res =
262       PQexecPrepared (plugin->dbh, "store_records", 6,
263                       paramValues, paramLengths, paramFormats, 1);
264     if (GNUNET_OK !=
265         GNUNET_POSTGRES_check_result (plugin->dbh,
266                                       res,
267                                       PGRES_COMMAND_OK,
268                                       "PQexecPrepared",
269                                       "store_records"))
270       return GNUNET_SYSERR;
271     PQclear (res);
272     return GNUNET_OK;
273   }
274 }
275
276
277 /**
278  * A statement has been run.  We should evaluate the result, and if possible
279  * call the given @a iter with the result.
280  *
281  * @param plugin plugin context
282  * @param res result from the statement that was run (to be cleaned up)
283  * @param zone_key private key of the zone, could be NULL, in which case we should
284  *        get the zone from @a res
285  * @param iter iterator to call with the result
286  * @param iter_cls closure for @a iter
287  * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
288  */
289 static int
290 get_record_and_call_iterator (struct Plugin *plugin,
291                               PGresult *res,
292                               const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone_key,
293                               GNUNET_NAMESTORE_RecordIterator iter, void *iter_cls)
294 {
295   const char *data;
296   size_t data_size;
297   uint32_t record_count;
298   const char *label;
299   size_t label_len;
300   unsigned int cnt;
301
302   if (GNUNET_OK !=
303       GNUNET_POSTGRES_check_result (plugin->dbh, res, PGRES_TUPLES_OK,
304                                     "PQexecPrepared",
305                                     "iteration"))
306   {
307     LOG (GNUNET_ERROR_TYPE_DEBUG,
308          "Failing lookup (postgres error)\n");
309     return GNUNET_SYSERR;
310   }
311   if (0 == (cnt = PQntuples (res)))
312   {
313     /* no result */
314     LOG (GNUNET_ERROR_TYPE_DEBUG,
315          "Ending iteration (no more results)\n");
316     PQclear (res);
317     return GNUNET_NO;
318   }
319   GNUNET_assert (1 == cnt);
320   GNUNET_assert (3 + ((NULL == zone_key) ? 1 : 0) == PQnfields (res));
321   if (NULL == zone_key)
322   {
323     if (sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey) != PQgetlength (res, 0, 3))
324     {
325       GNUNET_break (0);
326       PQclear (res);
327       return GNUNET_SYSERR;
328     }
329     zone_key = (const struct GNUNET_CRYPTO_EcdsaPrivateKey *) PQgetvalue (res, 0, 3);
330   }
331   if (sizeof (uint32_t) != PQfsize (res, 0))
332   {
333     GNUNET_break (0);
334     PQclear (res);
335     return GNUNET_SYSERR;
336   }
337
338   record_count = ntohl (*(uint32_t *) PQgetvalue (res, 0, 0));
339   data = PQgetvalue (res, 0, 1);
340   data_size = PQgetlength (res, 0, 1);
341   label = PQgetvalue (res, 0, 2);
342   label_len = PQgetlength (res, 0, 1);
343   if (record_count > 64 * 1024)
344   {
345     /* sanity check, don't stack allocate far too much just
346        because database might contain a large value here */
347     GNUNET_break (0);
348     PQclear (res);
349     return GNUNET_SYSERR;
350   }
351   {
352     struct GNUNET_GNSRECORD_Data rd[record_count];
353     char buf[label_len + 1];
354
355     memcpy (buf, label, label_len);
356     buf[label_len] = '\0';
357     if (GNUNET_OK !=
358         GNUNET_GNSRECORD_records_deserialize (data_size, data,
359                                               record_count, rd))
360     {
361       GNUNET_break (0);
362       PQclear (res);
363       return GNUNET_SYSERR;
364     }
365     iter (iter_cls, zone_key, buf, record_count, rd);
366   }
367   PQclear (res);
368   return GNUNET_OK;
369 }
370
371
372 /**
373  * Iterate over the results for a particular key and zone in the
374  * datastore.  Will return at most one result to the iterator.
375  *
376  * @param cls closure (internal context for the plugin)
377  * @param zone hash of public key of the zone, NULL to iterate over all zones
378  * @param offset offset in the list of all matching records
379  * @param iter function to call with the result
380  * @param iter_cls closure for @a iter
381  * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
382  */
383 static int
384 namestore_postgres_iterate_records (void *cls,
385                                     const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
386                                     uint64_t offset,
387                                     GNUNET_NAMESTORE_RecordIterator iter, void *iter_cls)
388 {
389   struct Plugin *plugin = cls;
390   uint64_t offset_be = GNUNET_htonll (offset);
391
392   if (NULL == zone)
393   {
394     const char *paramValues[] = {
395       (const char *) &offset_be
396     };
397     int paramLengths[] = {
398       sizeof (offset_be)
399     };
400     const int paramFormats[] = { 1 };
401     PGresult *res;
402
403     res = PQexecPrepared (plugin->dbh,
404                           "iterate_all_zones", 1,
405                           paramValues, paramLengths, paramFormats,
406                           1);
407     return get_record_and_call_iterator (plugin,
408                                          res,
409                                          NULL,
410                                          iter, iter_cls);
411   }
412   else
413   {
414     const char *paramValues[] = {
415       (const char *) zone,
416       (const char *) &offset_be
417     };
418     int paramLengths[] = {
419       sizeof (*zone),
420       sizeof (offset_be)
421     };
422     const int paramFormats[] = { 1, 1 };
423     PGresult *res;
424
425     res = PQexecPrepared (plugin->dbh,
426                           "iterate_zone", 2,
427                           paramValues, paramLengths, paramFormats,
428                           1);
429     return get_record_and_call_iterator (plugin,
430                                          res,
431                                          zone,
432                                          iter, iter_cls);
433   }
434 }
435
436
437 /**
438  * Look for an existing PKEY delegation record for a given public key.
439  * Returns at most one result to the iterator.
440  *
441  * @param cls closure (internal context for the plugin)
442  * @param zone private key of the zone to look up in, never NULL
443  * @param value_zone public key of the target zone (value), never NULL
444  * @param iter function to call with the result
445  * @param iter_cls closure for @a iter
446  * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
447  */
448 static int
449 namestore_postgres_zone_to_name (void *cls,
450                                  const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
451                                  const struct GNUNET_CRYPTO_EcdsaPublicKey *value_zone,
452                                  GNUNET_NAMESTORE_RecordIterator iter, void *iter_cls)
453 {
454   struct Plugin *plugin = cls;
455   const char *paramValues[] = {
456     (const char *) zone,
457     (const char *) value_zone
458   };
459   int paramLengths[] = {
460     sizeof (*zone),
461     sizeof (*value_zone)
462   };
463   const int paramFormats[] = { 1, 1 };
464   PGresult *res;
465
466   res = PQexecPrepared (plugin->dbh,
467                         "zone_to_name", 2,
468                         paramValues, paramLengths, paramFormats,
469                         1);
470   return get_record_and_call_iterator (plugin,
471                                        res,
472                                        zone,
473                                        iter, iter_cls);
474 }
475
476
477 /**
478  * Shutdown database connection and associate data
479  * structures.
480  *
481  * @param plugin the plugin context (state for this module)
482  */
483 static void
484 database_shutdown (struct Plugin *plugin)
485 {
486   PQfinish (plugin->dbh);
487   plugin->dbh = NULL;
488 }
489
490
491 /**
492  * Entry point for the plugin.
493  *
494  * @param cls the "struct GNUNET_NAMESTORE_PluginEnvironment*"
495  * @return NULL on error, othrewise the plugin context
496  */
497 void *
498 libgnunet_plugin_namestore_postgres_init (void *cls)
499 {
500   static struct Plugin plugin;
501   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
502   struct GNUNET_NAMESTORE_PluginFunctions *api;
503
504   if (NULL != plugin.cfg)
505     return NULL;                /* can only initialize once! */
506   memset (&plugin, 0, sizeof (struct Plugin));
507   plugin.cfg = cfg;
508   if (GNUNET_OK != database_setup (&plugin))
509   {
510     database_shutdown (&plugin);
511     return NULL;
512   }
513   api = GNUNET_new (struct GNUNET_NAMESTORE_PluginFunctions);
514   api->cls = &plugin;
515   api->store_records = &namestore_postgres_store_records;
516   api->iterate_records = &namestore_postgres_iterate_records;
517   api->zone_to_name = &namestore_postgres_zone_to_name;
518   LOG (GNUNET_ERROR_TYPE_INFO,
519        _("Postgres database running\n"));
520   return api;
521 }
522
523
524 /**
525  * Exit point from the plugin.
526  *
527  * @param cls the plugin context (as returned by "init")
528  * @return always NULL
529  */
530 void *
531 libgnunet_plugin_namestore_postgres_done (void *cls)
532 {
533   struct GNUNET_NAMESTORE_PluginFunctions *api = cls;
534   struct Plugin *plugin = api->cls;
535
536   database_shutdown (plugin);
537   plugin->cfg = NULL;
538   GNUNET_free (api);
539   LOG (GNUNET_ERROR_TYPE_DEBUG,
540        "postgres plugin is finished\n");
541   return NULL;
542 }
543
544 /* end of plugin_namestore_postgres.c */