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