Merge branch 'credentials' of git+ssh://gnunet.org/gnunet into credentials
[oweals/gnunet.git] / src / datacache / plugin_datacache_postgres.c
1 /*
2      This file is part of GNUnet
3      Copyright (C) 2006, 2009, 2010, 2012, 2015 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 datacache/plugin_datacache_postgres.c
23  * @brief postgres for an implementation of a database backend for the datacache
24  * @author Christian Grothoff
25  */
26 #include "platform.h"
27 #include "gnunet_util_lib.h"
28 #include "gnunet_postgres_lib.h"
29 #include "gnunet_datacache_plugin.h"
30
31 #define LOG(kind,...) GNUNET_log_from (kind, "datacache-postgres", __VA_ARGS__)
32
33 /**
34  * Per-entry overhead estimate
35  */
36 #define OVERHEAD (sizeof(struct GNUNET_HashCode) + 24)
37
38 /**
39  * Context for all functions in this plugin.
40  */
41 struct Plugin
42 {
43   /**
44    * Our execution environment.
45    */
46   struct GNUNET_DATACACHE_PluginEnvironment *env;
47
48   /**
49    * Native Postgres database handle.
50    */
51   PGconn *dbh;
52
53   /**
54    * Number of key-value pairs in the database.
55    */
56   unsigned int num_items;
57 };
58
59
60 /**
61  * @brief Get a database handle
62  *
63  * @param plugin global context
64  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
65  */
66 static int
67 init_connection (struct Plugin *plugin)
68 {
69   PGresult *ret;
70
71   plugin->dbh = GNUNET_POSTGRES_connect (plugin->env->cfg,
72                                          "datacache-postgres");
73   if (NULL == plugin->dbh)
74     return GNUNET_SYSERR;
75   ret =
76       PQexec (plugin->dbh,
77               "CREATE TEMPORARY TABLE IF NOT EXISTS gn090dc ("
78               "  type INTEGER NOT NULL DEFAULT 0,"
79               "  discard_time BIGINT NOT NULL DEFAULT 0,"
80               "  key BYTEA NOT NULL DEFAULT '',"
81               "  value BYTEA NOT NULL DEFAULT '',"
82               "  path BYTEA DEFAULT '')"
83               "WITH OIDS");
84   if ( (ret == NULL) ||
85        ((PQresultStatus (ret) != PGRES_COMMAND_OK) &&
86         (0 != strcmp ("42P07",    /* duplicate table */
87                       PQresultErrorField
88                       (ret,
89                        PG_DIAG_SQLSTATE)))))
90   {
91     (void) GNUNET_POSTGRES_check_result (plugin->dbh, ret,
92                                          PGRES_COMMAND_OK,
93                                          "CREATE TABLE",
94                                          "gn090dc");
95     PQfinish (plugin->dbh);
96     plugin->dbh = NULL;
97     return GNUNET_SYSERR;
98   }
99   if (PQresultStatus (ret) == PGRES_COMMAND_OK)
100   {
101     if ((GNUNET_OK !=
102          GNUNET_POSTGRES_exec (plugin->dbh,
103                                "CREATE INDEX IF NOT EXISTS idx_key ON gn090dc (key)")) ||
104         (GNUNET_OK !=
105          GNUNET_POSTGRES_exec (plugin->dbh,
106                                "CREATE INDEX IF NOT EXISTS idx_dt ON gn090dc (discard_time)")))
107     {
108       PQclear (ret);
109       PQfinish (plugin->dbh);
110       plugin->dbh = NULL;
111       return GNUNET_SYSERR;
112     }
113   }
114   PQclear (ret);
115   ret =
116       PQexec (plugin->dbh,
117               "ALTER TABLE gn090dc ALTER value SET STORAGE EXTERNAL");
118   if (GNUNET_OK !=
119       GNUNET_POSTGRES_check_result (plugin->dbh,
120                                     ret,
121                                     PGRES_COMMAND_OK,
122                                     "ALTER TABLE",
123                                     "gn090dc"))
124   {
125     PQfinish (plugin->dbh);
126     plugin->dbh = NULL;
127     return GNUNET_SYSERR;
128   }
129   PQclear (ret);
130   ret = PQexec (plugin->dbh,
131                 "ALTER TABLE gn090dc ALTER key SET STORAGE PLAIN");
132   if (GNUNET_OK !=
133       GNUNET_POSTGRES_check_result (plugin->dbh,
134                                     ret,
135                                     PGRES_COMMAND_OK,
136                                     "ALTER TABLE",
137                                     "gn090dc"))
138   {
139     PQfinish (plugin->dbh);
140     plugin->dbh = NULL;
141     return GNUNET_SYSERR;
142   }
143   PQclear (ret);
144   if ((GNUNET_OK !=
145        GNUNET_POSTGRES_prepare (plugin->dbh,
146                                 "getkt",
147                                 "SELECT discard_time,type,value,path FROM gn090dc "
148                                 "WHERE key=$1 AND type=$2 ", 2)) ||
149       (GNUNET_OK !=
150        GNUNET_POSTGRES_prepare (plugin->dbh,
151                                 "getk",
152                                 "SELECT discard_time,type,value,path FROM gn090dc "
153                                 "WHERE key=$1", 1)) ||
154       (GNUNET_OK !=
155        GNUNET_POSTGRES_prepare (plugin->dbh,
156                                 "getm",
157                                 "SELECT length(value),oid,key FROM gn090dc "
158                                 "ORDER BY discard_time ASC LIMIT 1", 0)) ||
159       (GNUNET_OK !=
160        GNUNET_POSTGRES_prepare (plugin->dbh,
161                                 "get_random",
162                                 "SELECT discard_time,type,value,path,key FROM gn090dc "
163                                 "ORDER BY key ASC LIMIT 1 OFFSET $1", 1)) ||
164       (GNUNET_OK !=
165        GNUNET_POSTGRES_prepare (plugin->dbh,
166                                 "get_closest",
167                                 "SELECT discard_time,type,value,path,key FROM gn090dc "
168                                 "WHERE key>=$1 ORDER BY key ASC LIMIT $2", 1)) ||
169       (GNUNET_OK !=
170        GNUNET_POSTGRES_prepare (plugin->dbh,
171                                 "delrow",
172                                 "DELETE FROM gn090dc WHERE oid=$1", 1)) ||
173       (GNUNET_OK !=
174        GNUNET_POSTGRES_prepare (plugin->dbh,
175                                 "put",
176                                 "INSERT INTO gn090dc (type, discard_time, key, value, path) "
177                                 "VALUES ($1, $2, $3, $4, $5)", 5)))
178   {
179     PQfinish (plugin->dbh);
180     plugin->dbh = NULL;
181     return GNUNET_SYSERR;
182   }
183   return GNUNET_OK;
184 }
185
186
187 /**
188  * Store an item in the datastore.
189  *
190  * @param cls closure (our `struct Plugin`)
191  * @param key key to store @a data under
192  * @param size number of bytes in @a data
193  * @param data data to store
194  * @param type type of the value
195  * @param discard_time when to discard the value in any case
196  * @param path_info_len number of entries in @a path_info
197  * @param path_info a path through the network
198  * @return 0 if duplicate, -1 on error, number of bytes used otherwise
199  */
200 static ssize_t
201 postgres_plugin_put (void *cls,
202                      const struct GNUNET_HashCode *key,
203                      size_t size,
204                      const char *data,
205                      enum GNUNET_BLOCK_Type type,
206                      struct GNUNET_TIME_Absolute discard_time,
207                      unsigned int path_info_len,
208                      const struct GNUNET_PeerIdentity *path_info)
209 {
210   struct Plugin *plugin = cls;
211   PGresult *ret;
212   uint32_t btype = htonl (type);
213   uint64_t bexpi = GNUNET_TIME_absolute_hton (discard_time).abs_value_us__;
214
215   const char *paramValues[] = {
216     (const char *) &btype,
217     (const char *) &bexpi,
218     (const char *) key,
219     (const char *) data,
220     (const char *) path_info
221   };
222   int paramLengths[] = {
223     sizeof (btype),
224     sizeof (bexpi),
225     sizeof (struct GNUNET_HashCode),
226     size,
227     path_info_len * sizeof (struct GNUNET_PeerIdentity)
228   };
229   const int paramFormats[] = { 1, 1, 1, 1, 1 };
230
231   ret =
232       PQexecPrepared (plugin->dbh, "put", 5, paramValues, paramLengths,
233                       paramFormats, 1);
234   if (GNUNET_OK !=
235       GNUNET_POSTGRES_check_result (plugin->dbh, ret,
236                                     PGRES_COMMAND_OK, "PQexecPrepared", "put"))
237     return -1;
238   plugin->num_items++;
239   PQclear (ret);
240   return size + OVERHEAD;
241 }
242
243
244 /**
245  * Iterate over the results for a particular key
246  * in the datastore.
247  *
248  * @param cls closure (our `struct Plugin`)
249  * @param key key to look for
250  * @param type entries of which type are relevant?
251  * @param iter maybe NULL (to just count)
252  * @param iter_cls closure for @a iter
253  * @return the number of results found
254  */
255 static unsigned int
256 postgres_plugin_get (void *cls,
257                      const struct GNUNET_HashCode *key,
258                      enum GNUNET_BLOCK_Type type,
259                      GNUNET_DATACACHE_Iterator iter,
260                      void *iter_cls)
261 {
262   struct Plugin *plugin = cls;
263   uint32_t btype = htonl (type);
264
265   const char *paramValues[] = {
266     (const char *) key,
267     (const char *) &btype
268   };
269   int paramLengths[] = {
270     sizeof (struct GNUNET_HashCode),
271     sizeof (btype)
272   };
273   const int paramFormats[] = { 1, 1 };
274   struct GNUNET_TIME_Absolute expiration_time;
275   uint32_t size;
276   unsigned int cnt;
277   unsigned int i;
278   unsigned int path_len;
279   const struct GNUNET_PeerIdentity *path;
280   PGresult *res;
281
282   res =
283       PQexecPrepared (plugin->dbh, (type == 0) ? "getk" : "getkt",
284                       (type == 0) ? 1 : 2, paramValues, paramLengths,
285                       paramFormats, 1);
286   if (GNUNET_OK !=
287       GNUNET_POSTGRES_check_result (plugin->dbh,
288                                     res,
289                                     PGRES_TUPLES_OK,
290                                     "PQexecPrepared",
291                                     (type == 0) ? "getk" : "getkt"))
292   {
293     LOG (GNUNET_ERROR_TYPE_DEBUG,
294          "Ending iteration (postgres error)\n");
295     return 0;
296   }
297
298   if (0 == (cnt = PQntuples (res)))
299   {
300     /* no result */
301     LOG (GNUNET_ERROR_TYPE_DEBUG,
302          "Ending iteration (no more results)\n");
303     PQclear (res);
304     return 0;
305   }
306   if (iter == NULL)
307   {
308     PQclear (res);
309     return cnt;
310   }
311   if ( (4 != PQnfields (res)) ||
312        (sizeof (uint64_t) != PQfsize (res, 0)) ||
313        (sizeof (uint32_t) != PQfsize (res, 1)))
314   {
315     GNUNET_break (0);
316     PQclear (res);
317     return 0;
318   }
319   for (i = 0; i < cnt; i++)
320   {
321     expiration_time.abs_value_us =
322         GNUNET_ntohll (*(uint64_t *) PQgetvalue (res, i, 0));
323     type = ntohl (*(uint32_t *) PQgetvalue (res, i, 1));
324     size = PQgetlength (res, i, 2);
325     path_len = PQgetlength (res, i, 3);
326     if (0 != (path_len % sizeof (struct GNUNET_PeerIdentity)))
327     {
328       GNUNET_break (0);
329       path_len = 0;
330     }
331     path_len %= sizeof (struct GNUNET_PeerIdentity);
332     path = (const struct GNUNET_PeerIdentity *) PQgetvalue (res, i, 3);
333     LOG (GNUNET_ERROR_TYPE_DEBUG,
334          "Found result of size %u bytes and type %u in database\n",
335          (unsigned int) size, (unsigned int) type);
336     if (GNUNET_SYSERR ==
337         iter (iter_cls, key, size, PQgetvalue (res, i, 2),
338               (enum GNUNET_BLOCK_Type) type,
339               expiration_time,
340               path_len,
341               path))
342     {
343       LOG (GNUNET_ERROR_TYPE_DEBUG,
344            "Ending iteration (client error)\n");
345       PQclear (res);
346       return cnt;
347     }
348   }
349   PQclear (res);
350   return cnt;
351 }
352
353
354 /**
355  * Delete the entry with the lowest expiration value
356  * from the datacache right now.
357  *
358  * @param cls closure (our `struct Plugin`)
359  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
360  */
361 static int
362 postgres_plugin_del (void *cls)
363 {
364   struct Plugin *plugin = cls;
365   uint32_t size;
366   uint32_t oid;
367   struct GNUNET_HashCode key;
368   PGresult *res;
369
370   res = PQexecPrepared (plugin->dbh,
371                         "getm",
372                         0, NULL, NULL, NULL, 1);
373   if (GNUNET_OK !=
374       GNUNET_POSTGRES_check_result (plugin->dbh,
375                                     res,
376                                     PGRES_TUPLES_OK,
377                                     "PQexecPrepared",
378                                     "getm"))
379   {
380     LOG (GNUNET_ERROR_TYPE_DEBUG,
381          "Ending iteration (postgres error)\n");
382     return 0;
383   }
384   if (0 == PQntuples (res))
385   {
386     /* no result */
387     LOG (GNUNET_ERROR_TYPE_DEBUG,
388          "Ending iteration (no more results)\n");
389     PQclear (res);
390     return GNUNET_SYSERR;
391   }
392   if ((3 != PQnfields (res)) || (sizeof (size) != PQfsize (res, 0)) ||
393       (sizeof (oid) != PQfsize (res, 1)) ||
394       (sizeof (struct GNUNET_HashCode) != PQgetlength (res, 0, 2)))
395   {
396     GNUNET_break (0);
397     PQclear (res);
398     return 0;
399   }
400   size = ntohl (*(uint32_t *) PQgetvalue (res, 0, 0));
401   oid = ntohl (*(uint32_t *) PQgetvalue (res, 0, 1));
402   GNUNET_memcpy (&key, PQgetvalue (res, 0, 2), sizeof (struct GNUNET_HashCode));
403   PQclear (res);
404   if (GNUNET_OK !=
405       GNUNET_POSTGRES_delete_by_rowid (plugin->dbh,
406                                        "delrow",
407                                        oid))
408     return GNUNET_SYSERR;
409   plugin->num_items--;
410   plugin->env->delete_notify (plugin->env->cls,
411                               &key,
412                               size + OVERHEAD);
413   return GNUNET_OK;
414 }
415
416
417 /**
418  * Obtain a random key-value pair from the datacache.
419  *
420  * @param cls closure (our `struct Plugin`)
421  * @param iter maybe NULL (to just count)
422  * @param iter_cls closure for @a iter
423  * @return the number of results found, zero (datacache empty) or one
424  */
425 static unsigned int
426 postgres_plugin_get_random (void *cls,
427                             GNUNET_DATACACHE_Iterator iter,
428                             void *iter_cls)
429 {
430   struct Plugin *plugin = cls;
431   unsigned int off;
432   uint32_t off_be;
433   struct GNUNET_TIME_Absolute expiration_time;
434   uint32_t size;
435   unsigned int path_len;
436   const struct GNUNET_PeerIdentity *path;
437   const struct GNUNET_HashCode *key;
438   unsigned int type;
439   PGresult *res;
440   const char *paramValues[] = {
441     (const char *) &off_be
442   };
443   int paramLengths[] = {
444     sizeof (off_be)
445   };
446   const int paramFormats[] = { 1 };
447
448   if (0 == plugin->num_items)
449     return 0;
450   if (NULL == iter)
451     return 1;
452   off = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
453                                   plugin->num_items);
454   off_be = htonl (off);
455   res =
456     PQexecPrepared (plugin->dbh, "get_random",
457                     1, paramValues, paramLengths, paramFormats,
458                     1);
459   if (GNUNET_OK !=
460       GNUNET_POSTGRES_check_result (plugin->dbh,
461                                     res,
462                                     PGRES_TUPLES_OK,
463                                     "PQexecPrepared",
464                                     "get_random"))
465   {
466     GNUNET_break (0);
467     return 0;
468   }
469   if (0 == PQntuples (res))
470   {
471     GNUNET_break (0);
472     return 0;
473   }
474   if ( (5 != PQnfields (res)) ||
475        (sizeof (uint64_t) != PQfsize (res, 0)) ||
476        (sizeof (uint32_t) != PQfsize (res, 1)) ||
477        (sizeof (struct GNUNET_HashCode) != PQfsize (res, 4)) )
478   {
479     GNUNET_break (0);
480     PQclear (res);
481     return 0;
482   }
483   expiration_time.abs_value_us =
484     GNUNET_ntohll (*(uint64_t *) PQgetvalue (res, 0, 0));
485   type = ntohl (*(uint32_t *) PQgetvalue (res, 0, 1));
486   size = PQgetlength (res, 0, 2);
487   path_len = PQgetlength (res, 0, 3);
488   if (0 != (path_len % sizeof (struct GNUNET_PeerIdentity)))
489   {
490     GNUNET_break (0);
491     path_len = 0;
492   }
493   path_len %= sizeof (struct GNUNET_PeerIdentity);
494   path = (const struct GNUNET_PeerIdentity *) PQgetvalue (res, 0, 3);
495   key = (const struct GNUNET_HashCode *) PQgetvalue (res, 0, 4);
496   LOG (GNUNET_ERROR_TYPE_DEBUG,
497        "Found random value with key %s of size %u bytes and type %u in database\n",
498        GNUNET_h2s (key),
499        (unsigned int) size,
500        (unsigned int) type);
501   (void) iter (iter_cls,
502                key,
503                size,
504                PQgetvalue (res, 0, 2),
505                (enum GNUNET_BLOCK_Type) type,
506                expiration_time,
507                path_len,
508                path);
509   PQclear (res);
510   return 1;
511 }
512
513
514 /**
515  * Iterate over the results that are "close" to a particular key in
516  * the datacache.  "close" is defined as numerically larger than @a
517  * key (when interpreted as a circular address space), with small
518  * distance.
519  *
520  * @param cls closure (internal context for the plugin)
521  * @param key area of the keyspace to look into
522  * @param num_results number of results that should be returned to @a iter
523  * @param iter maybe NULL (to just count)
524  * @param iter_cls closure for @a iter
525  * @return the number of results found
526  */
527 static unsigned int
528 postgres_plugin_get_closest (void *cls,
529                              const struct GNUNET_HashCode *key,
530                              unsigned int num_results,
531                              GNUNET_DATACACHE_Iterator iter,
532                              void *iter_cls)
533 {
534   struct Plugin *plugin = cls;
535   uint32_t nbo_limit = htonl (num_results);
536   const char *paramValues[] = {
537     (const char *) key,
538     (const char *) &nbo_limit,
539   };
540   int paramLengths[] = {
541     sizeof (struct GNUNET_HashCode),
542     sizeof (nbo_limit)
543
544   };
545   const int paramFormats[] = { 1, 1 };
546   struct GNUNET_TIME_Absolute expiration_time;
547   uint32_t size;
548   unsigned int type;
549   unsigned int cnt;
550   unsigned int i;
551   unsigned int path_len;
552   const struct GNUNET_PeerIdentity *path;
553   PGresult *res;
554
555   res =
556       PQexecPrepared (plugin->dbh,
557                       "get_closest",
558                       2,
559                       paramValues,
560                       paramLengths,
561                       paramFormats,
562                       1);
563   if (GNUNET_OK !=
564       GNUNET_POSTGRES_check_result (plugin->dbh,
565                                     res,
566                                     PGRES_TUPLES_OK,
567                                     "PQexecPrepared",
568                                     "get_closest"))
569   {
570     LOG (GNUNET_ERROR_TYPE_DEBUG,
571          "Ending iteration (postgres error)\n");
572     return 0;
573   }
574
575   if (0 == (cnt = PQntuples (res)))
576   {
577     /* no result */
578     LOG (GNUNET_ERROR_TYPE_DEBUG,
579          "Ending iteration (no more results)\n");
580     PQclear (res);
581     return 0;
582   }
583   if (NULL == iter)
584   {
585     PQclear (res);
586     return cnt;
587   }
588   if ( (5 != PQnfields (res)) ||
589        (sizeof (uint64_t) != PQfsize (res, 0)) ||
590        (sizeof (uint32_t) != PQfsize (res, 1)) ||
591        (sizeof (struct GNUNET_HashCode) != PQfsize (res, 4)) )
592   {
593     GNUNET_break (0);
594     PQclear (res);
595     return 0;
596   }
597   for (i = 0; i < cnt; i++)
598   {
599     expiration_time.abs_value_us =
600         GNUNET_ntohll (*(uint64_t *) PQgetvalue (res, i, 0));
601     type = ntohl (*(uint32_t *) PQgetvalue (res, i, 1));
602     size = PQgetlength (res, i, 2);
603     path_len = PQgetlength (res, i, 3);
604     if (0 != (path_len % sizeof (struct GNUNET_PeerIdentity)))
605     {
606       GNUNET_break (0);
607       path_len = 0;
608     }
609     path_len %= sizeof (struct GNUNET_PeerIdentity);
610     path = (const struct GNUNET_PeerIdentity *) PQgetvalue (res, i, 3);
611     key = (const struct GNUNET_HashCode *) PQgetvalue (res, i, 4);
612     LOG (GNUNET_ERROR_TYPE_DEBUG,
613          "Found result of size %u bytes and type %u in database\n",
614          (unsigned int) size,
615          (unsigned int) type);
616     if (GNUNET_SYSERR ==
617         iter (iter_cls,
618               key,
619               size,
620               PQgetvalue (res, i, 2),
621               (enum GNUNET_BLOCK_Type) type,
622               expiration_time,
623               path_len,
624               path))
625     {
626       LOG (GNUNET_ERROR_TYPE_DEBUG,
627            "Ending iteration (client error)\n");
628       PQclear (res);
629       return cnt;
630     }
631   }
632   PQclear (res);
633   return cnt;
634 }
635
636
637 /**
638  * Entry point for the plugin.
639  *
640  * @param cls closure (the `struct GNUNET_DATACACHE_PluginEnvironmnet`)
641  * @return the plugin's closure (our `struct Plugin`)
642  */
643 void *
644 libgnunet_plugin_datacache_postgres_init (void *cls)
645 {
646   struct GNUNET_DATACACHE_PluginEnvironment *env = cls;
647   struct GNUNET_DATACACHE_PluginFunctions *api;
648   struct Plugin *plugin;
649
650   plugin = GNUNET_new (struct Plugin);
651   plugin->env = env;
652
653   if (GNUNET_OK != init_connection (plugin))
654   {
655     GNUNET_free (plugin);
656     return NULL;
657   }
658
659   api = GNUNET_new (struct GNUNET_DATACACHE_PluginFunctions);
660   api->cls = plugin;
661   api->get = &postgres_plugin_get;
662   api->put = &postgres_plugin_put;
663   api->del = &postgres_plugin_del;
664   api->get_random = &postgres_plugin_get_random;
665   api->get_closest = &postgres_plugin_get_closest;
666   LOG (GNUNET_ERROR_TYPE_INFO,
667        "Postgres datacache running\n");
668   return api;
669 }
670
671
672 /**
673  * Exit point from the plugin.
674  *
675  * @param cls closure (our `struct Plugin`)
676  * @return NULL
677  */
678 void *
679 libgnunet_plugin_datacache_postgres_done (void *cls)
680 {
681   struct GNUNET_DATACACHE_PluginFunctions *api = cls;
682   struct Plugin *plugin = api->cls;
683
684   PQfinish (plugin->dbh);
685   GNUNET_free (plugin);
686   GNUNET_free (api);
687   return NULL;
688 }
689
690
691
692 /* end of plugin_datacache_postgres.c */