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