add statistic on namestore iteration delay
[oweals/gnunet.git] / src / namestore / plugin_namestore_postgres.c
1  /*
2   * This file is part of GNUnet
3   * Copyright (C) 2009-2013, 2016-2018 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_pq_lib.h"
31 #include "namestore.h"
32
33
34 #define LOG(kind,...) GNUNET_log_from (kind, "namestore-postgres", __VA_ARGS__)
35
36
37 /**
38  * Context for all functions in this plugin.
39  */
40 struct Plugin
41 {
42
43   /**
44    * Our configuration.
45    */
46   const struct GNUNET_CONFIGURATION_Handle *cfg;
47
48   /**
49    * Native Postgres database handle.
50    */
51   PGconn *dbh;
52
53 };
54
55
56 /**
57  * Initialize the database connections and associated
58  * data structures (create tables and indices
59  * as needed as well).
60  *
61  * @param plugin the plugin context (state for this module)
62  * @return #GNUNET_OK on success
63  */
64 static int
65 database_setup (struct Plugin *plugin)
66 {
67   struct GNUNET_PQ_ExecuteStatement es_temporary =
68     GNUNET_PQ_make_execute ("CREATE TEMPORARY TABLE IF NOT EXISTS ns097records ("
69                             " zone_private_key BYTEA NOT NULL DEFAULT '',"
70                             " pkey BYTEA DEFAULT '',"
71                             " rvalue BYTEA NOT NULL DEFAULT '',"
72                             " record_count INTEGER NOT NULL DEFAULT 0,"
73                             " record_data BYTEA NOT NULL DEFAULT '',"
74                             " label TEXT NOT NULL DEFAULT ''"
75                             ")"
76                             "WITH OIDS");
77   struct GNUNET_PQ_ExecuteStatement es_default =
78     GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS ns097records ("
79                             " zone_private_key BYTEA NOT NULL DEFAULT '',"
80                             " pkey BYTEA DEFAULT '',"
81                             " rvalue BYTEA NOT NULL DEFAULT '',"
82                             " record_count INTEGER NOT NULL DEFAULT 0,"
83                             " record_data BYTEA NOT NULL DEFAULT '',"
84                             " label TEXT NOT NULL DEFAULT ''"
85                             ")"
86                             "WITH OIDS");
87   const struct GNUNET_PQ_ExecuteStatement *cr;
88
89   plugin->dbh = GNUNET_PQ_connect_with_cfg (plugin->cfg,
90                                             "namestore-postgres");
91   if (NULL == plugin->dbh)
92     return GNUNET_SYSERR;
93   if (GNUNET_YES ==
94       GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg,
95                                             "namestore-postgres",
96                                             "ASYNC_COMMIT"))
97   {
98     struct GNUNET_PQ_ExecuteStatement es[] = {
99       GNUNET_PQ_make_try_execute ("SET synchronous_commit TO off"),
100       GNUNET_PQ_EXECUTE_STATEMENT_END
101     };
102
103     if (GNUNET_OK !=
104         GNUNET_PQ_exec_statements (plugin->dbh,
105                                    es))
106     {
107       PQfinish (plugin->dbh);
108       plugin->dbh = NULL;
109       return GNUNET_SYSERR;
110     }
111   }
112   if (GNUNET_YES ==
113       GNUNET_CONFIGURATION_get_value_yesno (plugin->cfg,
114                                             "namestore-postgres",
115                                             "TEMPORARY_TABLE"))
116   {
117     cr = &es_temporary;
118   }
119   else
120   {
121     cr = &es_default;
122   }
123
124   {
125     struct GNUNET_PQ_ExecuteStatement es[] = {
126       *cr,
127       GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS ir_pkey_reverse "
128                                   "ON ns097records (zone_private_key,pkey)"),
129       GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS ir_pkey_iter "
130                                   "ON ns097records (zone_private_key,rvalue)"),
131       GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS it_iter "
132                                   "ON ns097records (rvalue)"),
133       GNUNET_PQ_make_try_execute ("CREATE INDEX IF NOT EXISTS ir_label "
134                                   "ON ns097records (label)"),
135       GNUNET_PQ_EXECUTE_STATEMENT_END
136     };
137
138     if (GNUNET_OK !=
139         GNUNET_PQ_exec_statements (plugin->dbh,
140                                    es))
141     {
142       PQfinish (plugin->dbh);
143       plugin->dbh = NULL;
144       return GNUNET_SYSERR;
145     }
146   }
147
148   {
149     struct GNUNET_PQ_PreparedStatement ps[] = {
150       GNUNET_PQ_make_prepare ("store_records",
151                               "INSERT INTO ns097records (zone_private_key, pkey, rvalue, record_count, record_data, label) VALUES "
152                               "($1, $2, $3, $4, $5, $6)",
153                               6),
154       GNUNET_PQ_make_prepare ("delete_records",
155                               "DELETE FROM ns097records "
156                               "WHERE zone_private_key=$1 AND label=$2",
157                               2),
158       GNUNET_PQ_make_prepare ("zone_to_name",
159                               "SELECT record_count,record_data,label FROM ns097records"
160                               " WHERE zone_private_key=$1 AND pkey=$2",
161                               2),
162       GNUNET_PQ_make_prepare ("iterate_zone",
163                               "SELECT record_count,record_data,label FROM ns097records "
164                               "WHERE zone_private_key=$1 ORDER BY rvalue OFFSET $2 LIMIT $3",
165                               3),
166       GNUNET_PQ_make_prepare ("iterate_all_zones",
167                               "SELECT record_count,record_data,label,zone_private_key"
168                               " FROM ns097records ORDER BY rvalue OFFSET $1 LIMIT $2",
169                               2),
170       GNUNET_PQ_make_prepare ("lookup_label",
171                               "SELECT record_count,record_data,label "
172                               "FROM ns097records WHERE zone_private_key=$1 AND label=$2",
173                               2),
174       GNUNET_PQ_PREPARED_STATEMENT_END
175     };
176
177     if (GNUNET_OK !=
178         GNUNET_PQ_prepare_statements (plugin->dbh,
179                                       ps))
180     {
181       PQfinish (plugin->dbh);
182       plugin->dbh = NULL;
183       return GNUNET_SYSERR;
184     }
185   }
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_count32 = (uint32_t) rd_count;
213   size_t data_size;
214
215   memset (&pkey, 0, sizeof (pkey));
216   for (unsigned int i=0;i<rd_count;i++)
217     if (GNUNET_GNSRECORD_TYPE_PKEY == rd[i].record_type)
218     {
219       GNUNET_break (sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) == rd[i].data_size);
220       GNUNET_memcpy (&pkey,
221                      rd[i].data,
222                      rd[i].data_size);
223       break;
224     }
225   rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
226                                      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     struct GNUNET_PQ_QueryParam params[] = {
236       GNUNET_PQ_query_param_auto_from_type (zone_key),
237       GNUNET_PQ_query_param_auto_from_type (&pkey),
238       GNUNET_PQ_query_param_uint64 (&rvalue),
239       GNUNET_PQ_query_param_uint32 (&rd_count32),
240       GNUNET_PQ_query_param_fixed_size (data, data_size),
241       GNUNET_PQ_query_param_string (label),
242       GNUNET_PQ_query_param_end
243     };
244     enum GNUNET_DB_QueryStatus res;
245     ssize_t ret;
246
247     ret = GNUNET_GNSRECORD_records_serialize (rd_count,
248                                               rd,
249                                               data_size,
250                                               data);
251     if ( (ret < 0) ||
252          (data_size != (size_t) ret) )
253     {
254       GNUNET_break (0);
255       return GNUNET_SYSERR;
256     }
257
258     res = GNUNET_PQ_eval_prepared_non_select (plugin->dbh,
259                                               "store_records",
260                                               params);
261     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != res)
262       return GNUNET_SYSERR;
263   }
264   return GNUNET_OK;
265 }
266
267
268 /**
269  * Closure for #parse_result_call_iterator.
270  */
271 struct ParserContext
272 {
273   /**
274    * Function to call for each result.
275    */
276   GNUNET_NAMESTORE_RecordIterator iter;
277
278   /**
279    * Closure for @e iter.
280    */
281   void *iter_cls;
282
283   /**
284    * Zone key, NULL if part of record.
285    */
286   const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone_key;
287
288   /**
289    * Number of results still to return (counted down by
290    * number of results given to iterator).
291    */
292   uint64_t limit;
293 };
294
295
296 /**
297  * A statement has been run.  We should evaluate the result, and if possible
298  * call the @a iter in @a cls with the result.
299  *
300  * @param cls closure of type `struct ParserContext *`
301  * @param result the postgres result
302  * @param num_result the number of results in @a result
303  */
304 static void
305 parse_result_call_iterator (void *cls,
306                             PGresult *res,
307                             unsigned int num_results)
308 {
309   struct ParserContext *pc = cls;
310
311   if (NULL == pc->iter)
312     return; /* no need to do more work */
313   for (unsigned int i=0;i<num_results;i++)
314   {
315     void *data;
316     size_t data_size;
317     uint32_t record_count;
318     char *label;
319     struct GNUNET_CRYPTO_EcdsaPrivateKey zk;
320     struct GNUNET_PQ_ResultSpec rs_with_zone[] = {
321       GNUNET_PQ_result_spec_uint32 ("record_count", &record_count),
322       GNUNET_PQ_result_spec_variable_size ("record_data", &data, &data_size),
323       GNUNET_PQ_result_spec_string ("label", &label),
324       GNUNET_PQ_result_spec_auto_from_type ("zone_private_key", &zk),
325       GNUNET_PQ_result_spec_end
326     };
327     struct GNUNET_PQ_ResultSpec rs_without_zone[] = {
328       GNUNET_PQ_result_spec_uint32 ("record_count", &record_count),
329       GNUNET_PQ_result_spec_variable_size ("record_data", &data, &data_size),
330       GNUNET_PQ_result_spec_string ("label", &label),
331       GNUNET_PQ_result_spec_end
332     };
333     struct GNUNET_PQ_ResultSpec *rs;
334
335     rs = (NULL == pc->zone_key) ? rs_with_zone : rs_without_zone;
336     if (GNUNET_YES !=
337         GNUNET_PQ_extract_result (res,
338                                   rs,
339                                   i))
340     {
341       GNUNET_break (0);
342       return;
343     }
344
345     if (record_count > 64 * 1024)
346     {
347       /* sanity check, don't stack allocate far too much just
348          because database might contain a large value here */
349       GNUNET_break (0);
350       GNUNET_PQ_cleanup_result (rs);
351       return;
352     }
353
354     {
355       struct GNUNET_GNSRECORD_Data rd[GNUNET_NZL(record_count)];
356
357       if (GNUNET_OK !=
358           GNUNET_GNSRECORD_records_deserialize (data_size,
359                                                 data,
360                                                 record_count,
361                                                 rd))
362       {
363         GNUNET_break (0);
364         GNUNET_PQ_cleanup_result (rs);
365         return;
366       }
367       pc->iter (pc->iter_cls,
368                 (NULL == pc->zone_key) ? &zk : pc->zone_key,
369                 label,
370                 record_count,
371                 rd);
372     }
373     GNUNET_PQ_cleanup_result (rs);
374   }
375   pc->limit -= num_results;
376 }
377
378
379 /**
380  * Lookup records in the datastore for which we are the authority.
381  *
382  * @param cls closure (internal context for the plugin)
383  * @param zone private key of the zone
384  * @param label name of the record in the zone
385  * @param iter function to call with the result
386  * @param iter_cls closure for @a iter
387  * @return #GNUNET_OK on success, else #GNUNET_SYSERR
388  */
389 static int
390 namestore_postgres_lookup_records (void *cls,
391                                    const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
392                                    const char *label,
393                                    GNUNET_NAMESTORE_RecordIterator iter,
394                                    void *iter_cls)
395 {
396   struct Plugin *plugin = cls;
397   struct GNUNET_PQ_QueryParam params[] = {
398     GNUNET_PQ_query_param_auto_from_type (zone),
399     GNUNET_PQ_query_param_string (label),
400     GNUNET_PQ_query_param_end
401   };
402   struct ParserContext pc;
403   enum GNUNET_DB_QueryStatus res;
404
405   pc.iter = iter;
406   pc.iter_cls = iter_cls;
407   pc.zone_key = zone;
408   res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
409                                               "lookup_label",
410                                               params,
411                                               &parse_result_call_iterator,
412                                               &pc);
413   if (res <= 0)
414     return GNUNET_SYSERR;
415   return GNUNET_OK;
416 }
417
418
419 /**
420  * Iterate over the results for a particular key and zone in the
421  * datastore.  Will return at most one result to the iterator.
422  *
423  * @param cls closure (internal context for the plugin)
424  * @param zone hash of public key of the zone, NULL to iterate over all zones
425  * @param offset offset in the list of all matching records
426  * @param limit maximum number of results to fetch
427  * @param iter function to call with the result
428  * @param iter_cls closure for @a iter
429  * @return #GNUNET_OK on success, #GNUNET_NO if there were no more results, #GNUNET_SYSERR on error
430  */
431 static int
432 namestore_postgres_iterate_records (void *cls,
433                                     const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
434                                     uint64_t offset,
435                                     uint64_t limit,
436                                     GNUNET_NAMESTORE_RecordIterator iter,
437                                     void *iter_cls)
438 {
439   struct Plugin *plugin = cls;
440   enum GNUNET_DB_QueryStatus res;
441   struct ParserContext pc;
442
443   pc.iter = iter;
444   pc.iter_cls = iter_cls;
445   pc.zone_key = zone;
446   pc.limit = limit;
447   if (NULL == zone)
448   {
449     struct GNUNET_PQ_QueryParam params_without_zone[] = {
450       GNUNET_PQ_query_param_uint64 (&offset),
451       GNUNET_PQ_query_param_uint64 (&limit),
452       GNUNET_PQ_query_param_end
453     };
454
455     res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
456                                                 "iterate_all_zones",
457                                                 params_without_zone,
458                                                 &parse_result_call_iterator,
459                                                 &pc);
460   }
461   else
462   {
463     struct GNUNET_PQ_QueryParam params_with_zone[] = {
464       GNUNET_PQ_query_param_auto_from_type (zone),
465       GNUNET_PQ_query_param_uint64 (&offset),
466       GNUNET_PQ_query_param_uint64 (&limit),
467       GNUNET_PQ_query_param_end
468     };
469
470     res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
471                                                 "iterate_zone",
472                                                 params_with_zone,
473                                                 &parse_result_call_iterator,
474                                                 &pc);
475   }
476   if (res < 0)
477     return GNUNET_SYSERR;
478
479   if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == res) ||
480        (pc.limit > 0) )
481     return GNUNET_NO;
482   return GNUNET_OK;
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   struct GNUNET_PQ_QueryParam params[] = {
505     GNUNET_PQ_query_param_auto_from_type (zone),
506     GNUNET_PQ_query_param_auto_from_type (value_zone),
507     GNUNET_PQ_query_param_end
508   };
509   enum GNUNET_DB_QueryStatus res;
510   struct ParserContext pc;
511
512   pc.iter = iter;
513   pc.iter_cls = iter_cls;
514   pc.zone_key = zone;
515   res = GNUNET_PQ_eval_prepared_multi_select (plugin->dbh,
516                                               "zone_to_name",
517                                               params,
518                                               &parse_result_call_iterator,
519                                               &pc);
520   if (res < 0)
521     return GNUNET_SYSERR;
522   return GNUNET_OK;
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 namestore plugin 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 namestore plugin is finished\n");
591   return NULL;
592 }
593
594 /* end of plugin_namestore_postgres.c */