- Tests did not clean up: TEST_HOME with namestore db was not removed after test
[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, "CREATE INDEX it_iter ON ns097records (rvalue)")) ||
90        (GNUNET_OK !=
91         GNUNET_POSTGRES_exec (dbh, "CREATE INDEX ir_label ON ns097records (label)")) )
92     LOG (GNUNET_ERROR_TYPE_ERROR,
93          _("Failed to create indices\n"));
94 }
95
96
97 /**
98  * Initialize the database connections and associated
99  * data structures (create tables and indices
100  * as needed as well).
101  *
102  * @param plugin the plugin context (state for this module)
103  * @return GNUNET_OK on success
104  */
105 static int
106 database_setup (struct Plugin *plugin)
107 {
108   PGresult *res;
109
110   plugin->dbh = GNUNET_POSTGRES_connect (plugin->cfg,
111                                          "namestore-postgres");
112   if (NULL == plugin->dbh)
113     return GNUNET_SYSERR;
114   if (GNUNET_YES ==
115       GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg,
116                                             "namestore-postgres",
117                                             "TEMPORARY_TABLE"))
118   {
119     res =
120       PQexec (plugin->dbh,
121               "CREATE TEMPORARY TABLE ns097records ("
122               " zone_private_key BYTEA NOT NULL DEFAULT '',"
123               " pkey BYTEA DEFAULT '',"
124               " rvalue BYTEA NOT NULL DEFAULT '',"
125               " record_count INTEGER NOT NULL DEFAULT 0,"
126               " record_data BYTEA NOT NULL DEFAULT '',"
127               " label TEXT NOT NULL DEFAULT ''"
128               ")" "WITH OIDS");
129   }
130   else
131   {
132     res =
133       PQexec (plugin->dbh,
134               "CREATE TABLE ns097records ("
135               " zone_private_key BYTEA NOT NULL DEFAULT '',"
136               " pkey BYTEA DEFAULT '',"
137               " rvalue BYTEA NOT NULL DEFAULT '',"
138               " record_count INTEGER NOT NULL DEFAULT 0,"
139               " record_data BYTEA NOT NULL DEFAULT '',"
140               " label TEXT NOT NULL DEFAULT ''"
141               ")" "WITH OIDS");
142   }
143   if ( (NULL == res) ||
144        ((PQresultStatus (res) != PGRES_COMMAND_OK) &&
145         (0 != strcmp ("42P07",    /* duplicate table */
146                       PQresultErrorField
147                       (res,
148                        PG_DIAG_SQLSTATE)))))
149   {
150     (void) GNUNET_POSTGRES_check_result (plugin->dbh, res,
151                                          PGRES_COMMAND_OK, "CREATE TABLE",
152                                          "ns097records");
153     PQfinish (plugin->dbh);
154     plugin->dbh = NULL;
155     return GNUNET_SYSERR;
156   }
157   create_indices (plugin->dbh);
158
159   if ((GNUNET_OK !=
160        GNUNET_POSTGRES_prepare (plugin->dbh,
161                                 "store_records",
162                                 "INSERT INTO ns097records (zone_private_key, pkey, rvalue, record_count, record_data, label) VALUES "
163                                 "($1, $2, $3, $4, $5, $6)", 6)) ||
164       (GNUNET_OK !=
165        GNUNET_POSTGRES_prepare (plugin->dbh,
166                                 "delete_records",
167                                 "DELETE FROM ns097records WHERE zone_private_key=$1 AND label=$2", 2)) ||
168       (GNUNET_OK !=
169        GNUNET_POSTGRES_prepare (plugin->dbh,
170                                 "zone_to_name",
171                                 "SELECT record_count,record_data,label FROM ns097records"
172                                 " WHERE zone_private_key=$1 AND pkey=$2", 2)) ||
173       (GNUNET_OK !=
174        GNUNET_POSTGRES_prepare (plugin->dbh,
175                                 "iterate_zone",
176                                 "SELECT record_count, record_data, label FROM ns097records"
177                                 " WHERE zone_private_key=$1 ORDER BY rvalue LIMIT 1 OFFSET $2", 2)) ||
178       (GNUNET_OK !=
179        GNUNET_POSTGRES_prepare (plugin->dbh,
180                                 "iterate_all_zones",
181                                 "SELECT record_count,record_data,label,zone_private_key"
182                                 " FROM ns097records ORDER BY rvalue LIMIT 1 OFFSET $1", 1)) ||
183       (GNUNET_OK !=
184        GNUNET_POSTGRES_prepare (plugin->dbh,
185                               "lookup_label",
186                               "SELECT record_count,record_data,label,zone_private_key"
187                               " FROM ns097records WHERE records zone_private_key=$1 AND label=$2", 2)))
188   {
189     PQfinish (plugin->dbh);
190     plugin->dbh = NULL;
191     return GNUNET_SYSERR;
192   }
193   return GNUNET_OK;
194 }
195
196
197 /**
198  * Store a record in the datastore.  Removes any existing record in the
199  * same zone with the same name.
200  *
201  * @param cls closure (internal context for the plugin)
202  * @param zone_key private key of the zone
203  * @param label name that is being mapped (at most 255 characters long)
204  * @param rd_count number of entries in @a rd array
205  * @param rd array of records with data to store
206  * @return #GNUNET_OK on success, else #GNUNET_SYSERR
207  */
208 static int
209 namestore_postgres_store_records (void *cls,
210                                   const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone_key,
211                                   const char *label,
212                                   unsigned int rd_count,
213                                   const struct GNUNET_GNSRECORD_Data *rd)
214 {
215   struct Plugin *plugin = cls;
216   struct GNUNET_CRYPTO_EcdsaPublicKey pkey;
217   uint64_t rvalue;
218   uint32_t rd_count_nbo = htonl ((uint32_t) rd_count);
219   size_t data_size;
220   unsigned int i;
221
222   memset (&pkey, 0, sizeof (pkey));
223   for (i=0;i<rd_count;i++)
224     if (GNUNET_GNSRECORD_TYPE_PKEY == rd[i].record_type)
225     {
226       GNUNET_break (sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) == rd[i].data_size);
227       memcpy (&pkey,
228               rd[i].data,
229               rd[i].data_size);
230       break;
231     }
232   rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX);
233   data_size = GNUNET_GNSRECORD_records_get_size (rd_count, rd);
234   if (data_size > 64 * 65536)
235   {
236     GNUNET_break (0);
237     return GNUNET_SYSERR;
238   }
239   {
240     char data[data_size];
241     const char *paramValues[] = {
242       (const char *) zone_key,
243       (const char *) &pkey,
244       (const char *) &rvalue,
245       (const char *) &rd_count_nbo,
246       (const char *) data,
247       label
248     };
249     int paramLengths[] = {
250       sizeof (*zone_key),
251       sizeof (pkey),
252       sizeof (rvalue),
253       sizeof (rd_count_nbo),
254       data_size,
255       strlen (label)
256     };
257     const int paramFormats[] = { 1, 1, 1, 1, 1, 1 };
258     PGresult *res;
259
260     if (data_size != GNUNET_GNSRECORD_records_serialize (rd_count, rd,
261                                                          data_size, data))
262     {
263       GNUNET_break (0);
264       return GNUNET_SYSERR;
265     }
266
267     res =
268       PQexecPrepared (plugin->dbh, "store_records", 6,
269                       paramValues, paramLengths, paramFormats, 1);
270     if (GNUNET_OK !=
271         GNUNET_POSTGRES_check_result (plugin->dbh,
272                                       res,
273                                       PGRES_COMMAND_OK,
274                                       "PQexecPrepared",
275                                       "store_records"))
276       return GNUNET_SYSERR;
277     PQclear (res);
278     return GNUNET_OK;
279   }
280 }
281
282
283 /**
284  * A statement has been run.  We should evaluate the result, and if possible
285  * call the given @a iter with the result.
286  *
287  * @param plugin plugin context
288  * @param res result from the statement that was run (to be cleaned up)
289  * @param zone_key private key of the zone, could be NULL, in which case we should
290  *        get the zone from @a res
291  * @param iter iterator to call with the result
292  * @param iter_cls closure for @a iter
293  * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
294  */
295 static int
296 get_record_and_call_iterator (struct Plugin *plugin,
297                               PGresult *res,
298                               const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone_key,
299                               GNUNET_NAMESTORE_RecordIterator iter, void *iter_cls)
300 {
301   const char *data;
302   size_t data_size;
303   uint32_t record_count;
304   const char *label;
305   size_t label_len;
306   unsigned int cnt;
307
308   if (GNUNET_OK !=
309       GNUNET_POSTGRES_check_result (plugin->dbh, res, PGRES_TUPLES_OK,
310                                     "PQexecPrepared",
311                                     "iteration"))
312   {
313     LOG (GNUNET_ERROR_TYPE_DEBUG,
314          "Failing lookup (postgres error)\n");
315     return GNUNET_SYSERR;
316   }
317   if (0 == (cnt = PQntuples (res)))
318   {
319     /* no result */
320     LOG (GNUNET_ERROR_TYPE_DEBUG,
321          "Ending iteration (no more results)\n");
322     PQclear (res);
323     return GNUNET_NO;
324   }
325   GNUNET_assert (1 == cnt);
326   GNUNET_assert (3 + ((NULL == zone_key) ? 1 : 0) == PQnfields (res));
327   if (NULL == zone_key)
328   {
329     if (sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey) != PQgetlength (res, 0, 3))
330     {
331       GNUNET_break (0);
332       PQclear (res);
333       return GNUNET_SYSERR;
334     }
335     zone_key = (const struct GNUNET_CRYPTO_EcdsaPrivateKey *) PQgetvalue (res, 0, 3);
336   }
337   if (sizeof (uint32_t) != PQfsize (res, 0))
338   {
339     GNUNET_break (0);
340     PQclear (res);
341     return GNUNET_SYSERR;
342   }
343
344   record_count = ntohl (*(uint32_t *) PQgetvalue (res, 0, 0));
345   data = PQgetvalue (res, 0, 1);
346   data_size = PQgetlength (res, 0, 1);
347   label = PQgetvalue (res, 0, 2);
348   label_len = PQgetlength (res, 0, 1);
349   if (record_count > 64 * 1024)
350   {
351     /* sanity check, don't stack allocate far too much just
352        because database might contain a large value here */
353     GNUNET_break (0);
354     PQclear (res);
355     return GNUNET_SYSERR;
356   }
357   {
358     struct GNUNET_GNSRECORD_Data rd[record_count];
359     char buf[label_len + 1];
360
361     memcpy (buf, label, label_len);
362     buf[label_len] = '\0';
363     if (GNUNET_OK !=
364         GNUNET_GNSRECORD_records_deserialize (data_size, data,
365                                               record_count, rd))
366     {
367       GNUNET_break (0);
368       PQclear (res);
369       return GNUNET_SYSERR;
370     }
371     iter (iter_cls, zone_key, buf, record_count, rd);
372   }
373   PQclear (res);
374   return GNUNET_OK;
375 }
376
377
378 /**
379  * Lookup records in the datastore for which we are the authority.
380  *
381  * @param cls closure (internal context for the plugin)
382  * @param zone private key of the zone
383  * @param label name of the record in the zone
384  * @param iter function to call with the result
385  * @param iter_cls closure for @a iter
386  * @return #GNUNET_OK on success, else #GNUNET_SYSERR
387  */
388 static int
389 namestore_postgres_lookup_records (void *cls,
390     const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone, const char *label,
391     GNUNET_NAMESTORE_RecordIterator iter, void *iter_cls)
392 {
393   struct Plugin *plugin = cls;
394   const char *paramValues[] = {
395     (const char *) zone,
396     label
397   };
398   int paramLengths[] = {
399     sizeof (*zone),
400     strlen (label)
401   };
402   const int paramFormats[] = { 1, 1 };
403   PGresult *res;
404
405   res = PQexecPrepared (plugin->dbh,
406                         "lookup_label", 2,
407                         paramValues, paramLengths, paramFormats,
408                         1);
409   return get_record_and_call_iterator (plugin,
410                                        res,
411                                        zone,
412                                        iter, iter_cls);
413 }
414
415
416 /**
417  * Iterate over the results for a particular key and zone in the
418  * datastore.  Will return at most one result to the iterator.
419  *
420  * @param cls closure (internal context for the plugin)
421  * @param zone hash of public key of the zone, NULL to iterate over all zones
422  * @param offset offset in the list of all matching records
423  * @param iter function to call with the result
424  * @param iter_cls closure for @a iter
425  * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
426  */
427 static int
428 namestore_postgres_iterate_records (void *cls,
429                                     const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
430                                     uint64_t offset,
431                                     GNUNET_NAMESTORE_RecordIterator iter, void *iter_cls)
432 {
433   struct Plugin *plugin = cls;
434   uint64_t offset_be = GNUNET_htonll (offset);
435
436   if (NULL == zone)
437   {
438     const char *paramValues[] = {
439       (const char *) &offset_be
440     };
441     int paramLengths[] = {
442       sizeof (offset_be)
443     };
444     const int paramFormats[] = { 1 };
445     PGresult *res;
446
447     res = PQexecPrepared (plugin->dbh,
448                           "iterate_all_zones", 1,
449                           paramValues, paramLengths, paramFormats,
450                           1);
451     return get_record_and_call_iterator (plugin,
452                                          res,
453                                          NULL,
454                                          iter, iter_cls);
455   }
456   else
457   {
458     const char *paramValues[] = {
459       (const char *) zone,
460       (const char *) &offset_be
461     };
462     int paramLengths[] = {
463       sizeof (*zone),
464       sizeof (offset_be)
465     };
466     const int paramFormats[] = { 1, 1 };
467     PGresult *res;
468
469     res = PQexecPrepared (plugin->dbh,
470                           "iterate_zone", 2,
471                           paramValues, paramLengths, paramFormats,
472                           1);
473     return get_record_and_call_iterator (plugin,
474                                          res,
475                                          zone,
476                                          iter, iter_cls);
477   }
478 }
479
480
481 /**
482  * Look for an existing PKEY delegation record for a given public key.
483  * Returns at most one result to the iterator.
484  *
485  * @param cls closure (internal context for the plugin)
486  * @param zone private key of the zone to look up in, never NULL
487  * @param value_zone public key of the target zone (value), never NULL
488  * @param iter function to call with the result
489  * @param iter_cls closure for @a iter
490  * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
491  */
492 static int
493 namestore_postgres_zone_to_name (void *cls,
494                                  const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
495                                  const struct GNUNET_CRYPTO_EcdsaPublicKey *value_zone,
496                                  GNUNET_NAMESTORE_RecordIterator iter, void *iter_cls)
497 {
498   struct Plugin *plugin = cls;
499   const char *paramValues[] = {
500     (const char *) zone,
501     (const char *) value_zone
502   };
503   int paramLengths[] = {
504     sizeof (*zone),
505     sizeof (*value_zone)
506   };
507   const int paramFormats[] = { 1, 1 };
508   PGresult *res;
509
510   res = PQexecPrepared (plugin->dbh,
511                         "zone_to_name", 2,
512                         paramValues, paramLengths, paramFormats,
513                         1);
514   return get_record_and_call_iterator (plugin,
515                                        res,
516                                        zone,
517                                        iter, iter_cls);
518 }
519
520
521 /**
522  * Shutdown database connection and associate data
523  * structures.
524  *
525  * @param plugin the plugin context (state for this module)
526  */
527 static void
528 database_shutdown (struct Plugin *plugin)
529 {
530   PQfinish (plugin->dbh);
531   plugin->dbh = NULL;
532 }
533
534
535 /**
536  * Entry point for the plugin.
537  *
538  * @param cls the "struct GNUNET_NAMESTORE_PluginEnvironment*"
539  * @return NULL on error, othrewise the plugin context
540  */
541 void *
542 libgnunet_plugin_namestore_postgres_init (void *cls)
543 {
544   static struct Plugin plugin;
545   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
546   struct GNUNET_NAMESTORE_PluginFunctions *api;
547
548   if (NULL != plugin.cfg)
549     return NULL;                /* can only initialize once! */
550   memset (&plugin, 0, sizeof (struct Plugin));
551   plugin.cfg = cfg;
552   if (GNUNET_OK != database_setup (&plugin))
553   {
554     database_shutdown (&plugin);
555     return NULL;
556   }
557   api = GNUNET_new (struct GNUNET_NAMESTORE_PluginFunctions);
558   api->cls = &plugin;
559   api->store_records = &namestore_postgres_store_records;
560   api->iterate_records = &namestore_postgres_iterate_records;
561   api->zone_to_name = &namestore_postgres_zone_to_name;
562   api->lookup_records = &namestore_postgres_lookup_records;
563   LOG (GNUNET_ERROR_TYPE_INFO,
564        _("Postgres database running\n"));
565   return api;
566 }
567
568
569 /**
570  * Exit point from the plugin.
571  *
572  * @param cls the plugin context (as returned by "init")
573  * @return always NULL
574  */
575 void *
576 libgnunet_plugin_namestore_postgres_done (void *cls)
577 {
578   struct GNUNET_NAMESTORE_PluginFunctions *api = cls;
579   struct Plugin *plugin = api->cls;
580
581   database_shutdown (plugin);
582   plugin->cfg = NULL;
583   GNUNET_free (api);
584   LOG (GNUNET_ERROR_TYPE_DEBUG,
585        "postgres plugin is finished\n");
586   return NULL;
587 }
588
589 /* end of plugin_namestore_postgres.c */