fix #4244
[oweals/gnunet.git] / src / peerstore / plugin_peerstore_sqlite.c
1 /*
2  * This file is part of GNUnet
3  * Copyright (C) 2013, 2017 GNUnet e.V.
4  *
5  * GNUnet is free software: you can redistribute it and/or modify it
6  * under the terms of the GNU Affero General Public License as published
7  * by the Free Software Foundation, either version 3 of the License,
8  * or (at your 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  * Affero General Public License for more details.
14  *
15  * You should have received a copy of the GNU Affero General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18      SPDX-License-Identifier: AGPL3.0-or-later
19  */
20
21 /**
22  * @file peerstore/plugin_peerstore_sqlite.c
23  * @brief sqlite-based peerstore backend
24  * @author Omar Tarabai
25  * @author Christian Grothoff
26  */
27
28 #include "platform.h"
29 #include "gnunet_peerstore_plugin.h"
30 #include "gnunet_peerstore_service.h"
31 #include "gnunet_sq_lib.h"
32 #include "peerstore.h"
33 #include <sqlite3.h>
34
35 /**
36  * After how many ms "busy" should a DB operation fail for good?  A
37  * low value makes sure that we are more responsive to requests
38  * (especially PUTs).  A high value guarantees a higher success rate
39  * (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
43  * succeed with reasonable probability.
44  */
45 #define BUSY_TIMEOUT_MS 1000
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_SQLITE(db, level, cmd) do { GNUNET_log_from (level, "peerstore-sqlite", _("`%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, "peerstore-sqlite", __VA_ARGS__)
55
56 /**
57  * Context for all functions in this plugin.
58  */
59 struct Plugin
60 {
61
62   /**
63    * Configuration handle
64    */
65   const struct GNUNET_CONFIGURATION_Handle *cfg;
66
67   /**
68    * Database filename.
69    */
70   char *fn;
71
72   /**
73    * Native SQLite database handle.
74    */
75   sqlite3 *dbh;
76
77   /**
78    * Precompiled SQL for inserting into peerstoredata
79    */
80   sqlite3_stmt *insert_peerstoredata;
81
82   /**
83    * Precompiled SQL for selecting from peerstoredata
84    */
85   sqlite3_stmt *select_peerstoredata;
86
87   /**
88    * Precompiled SQL for selecting from peerstoredata
89    */
90   sqlite3_stmt *select_peerstoredata_by_pid;
91
92   /**
93    * Precompiled SQL for selecting from peerstoredata
94    */
95   sqlite3_stmt *select_peerstoredata_by_key;
96
97   /**
98    * Precompiled SQL for selecting from peerstoredata
99    */
100   sqlite3_stmt *select_peerstoredata_by_all;
101
102   /**
103    * Precompiled SQL for deleting expired
104    * records from peerstoredata
105    */
106   sqlite3_stmt *expire_peerstoredata;
107
108   /**
109    * Precompiled SQL for deleting records
110    * with given key
111    */
112   sqlite3_stmt *delete_peerstoredata;
113
114 };
115
116
117 /**
118  * Delete records with the given key
119  *
120  * @param cls closure (internal context for the plugin)
121  * @param sub_system name of sub system
122  * @param peer Peer identity (can be NULL)
123  * @param key entry key string (can be NULL)
124  * @return number of deleted records, #GNUNE_SYSERR on error
125  */
126 static int
127 peerstore_sqlite_delete_records (void *cls,
128                                  const char *sub_system,
129                                  const struct GNUNET_PeerIdentity *peer,
130                                  const char *key)
131 {
132   struct Plugin *plugin = cls;
133   sqlite3_stmt *stmt = plugin->delete_peerstoredata;
134   struct GNUNET_SQ_QueryParam params[] = {
135     GNUNET_SQ_query_param_string (sub_system),
136     GNUNET_SQ_query_param_auto_from_type (peer),
137     GNUNET_SQ_query_param_string (key),
138     GNUNET_SQ_query_param_end
139   };
140   int ret;
141
142   if (GNUNET_OK !=
143       GNUNET_SQ_bind (stmt,
144                       params))
145   {
146     LOG_SQLITE (plugin,
147                 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
148                 "sqlite3_bind");
149     GNUNET_SQ_reset (plugin->dbh,
150                      stmt);
151     return GNUNET_SYSERR;
152   }
153   if (SQLITE_DONE !=
154       sqlite3_step (stmt))
155   {
156     LOG_SQLITE (plugin,
157                 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
158                 "sqlite3_step");
159     ret = GNUNET_SYSERR;
160   }
161   else
162   {
163     ret = sqlite3_changes (plugin->dbh);
164   }
165   GNUNET_SQ_reset (plugin->dbh,
166                    stmt);
167   return ret;
168 }
169
170
171 /**
172  * Delete expired records (expiry < now)
173  *
174  * @param cls closure (internal context for the plugin)
175  * @param now time to use as reference
176  * @param cont continuation called with the number of records expired
177  * @param cont_cls continuation closure
178  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error and cont is not
179  * called
180  */
181 static int
182 peerstore_sqlite_expire_records (void *cls, struct GNUNET_TIME_Absolute now,
183                                  GNUNET_PEERSTORE_Continuation cont,
184                                  void *cont_cls)
185 {
186   struct Plugin *plugin = cls;
187   sqlite3_stmt *stmt = plugin->expire_peerstoredata;
188   struct GNUNET_SQ_QueryParam params[] = {
189     GNUNET_SQ_query_param_absolute_time (&now),
190     GNUNET_SQ_query_param_end
191   };
192
193   if (GNUNET_OK !=
194       GNUNET_SQ_bind (stmt,
195                       params))
196   {
197     LOG_SQLITE (plugin,
198                 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
199                 "sqlite3_bind");
200     GNUNET_SQ_reset (plugin->dbh,
201                      stmt);
202     return GNUNET_SYSERR;
203   }
204   if (SQLITE_DONE != sqlite3_step (stmt))
205   {
206     LOG_SQLITE (plugin,
207                 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
208                 "sqlite3_step");
209     GNUNET_SQ_reset (plugin->dbh,
210                      stmt);
211     return GNUNET_SYSERR;
212   }
213   if (NULL != cont)
214     cont (cont_cls,
215           sqlite3_changes (plugin->dbh));
216   GNUNET_SQ_reset (plugin->dbh,
217                    stmt);
218   return GNUNET_OK;
219 }
220
221
222 /**
223  * Iterate over the records given an optional peer id
224  * and/or key.
225  *
226  * @param cls closure (internal context for the plugin)
227  * @param sub_system name of sub system
228  * @param peer Peer identity (can be NULL)
229  * @param key entry key string (can be NULL)
230  * @param iter function to call asynchronously with the results, terminated
231  * by a NULL result
232  * @param iter_cls closure for @a iter
233  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error and iter is not
234  * called
235  */
236 static int
237 peerstore_sqlite_iterate_records (void *cls,
238                                   const char *sub_system,
239                                   const struct GNUNET_PeerIdentity *peer,
240                                   const char *key,
241                                   GNUNET_PEERSTORE_Processor iter,
242                                   void *iter_cls)
243 {
244   struct Plugin *plugin = cls;
245   sqlite3_stmt *stmt;
246   int err = 0;
247   int sret;
248   struct GNUNET_PEERSTORE_Record rec;
249
250   LOG (GNUNET_ERROR_TYPE_DEBUG,
251        "Executing iterate request on sqlite db.\n");
252   if (NULL == peer)
253   {
254     if (NULL == key)
255     {
256       struct GNUNET_SQ_QueryParam params[] = {
257         GNUNET_SQ_query_param_string (sub_system),
258         GNUNET_SQ_query_param_end
259       };
260
261       stmt = plugin->select_peerstoredata;
262       err = GNUNET_SQ_bind (stmt,
263                             params);
264     }
265     else
266     {
267       struct GNUNET_SQ_QueryParam params[] = {
268         GNUNET_SQ_query_param_string (sub_system),
269         GNUNET_SQ_query_param_string (key),
270         GNUNET_SQ_query_param_end
271       };
272
273       stmt = plugin->select_peerstoredata_by_key;
274       err = GNUNET_SQ_bind (stmt,
275                             params);
276     }
277   }
278   else
279   {
280     if (NULL == key)
281     {
282       struct GNUNET_SQ_QueryParam params[] = {
283         GNUNET_SQ_query_param_string (sub_system),
284         GNUNET_SQ_query_param_auto_from_type (peer),
285         GNUNET_SQ_query_param_end
286       };
287
288       stmt = plugin->select_peerstoredata_by_pid;
289       err = GNUNET_SQ_bind (stmt,
290                             params);
291     }
292     else
293     {
294       struct GNUNET_SQ_QueryParam params[] = {
295         GNUNET_SQ_query_param_string (sub_system),
296         GNUNET_SQ_query_param_auto_from_type (peer),
297         GNUNET_SQ_query_param_string (key),
298         GNUNET_SQ_query_param_end
299       };
300
301       stmt = plugin->select_peerstoredata_by_all;
302       err = GNUNET_SQ_bind (stmt,
303                             params);
304     }
305   }
306
307   if (GNUNET_OK != err)
308   {
309     LOG_SQLITE (plugin,
310                 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
311                 "sqlite3_bind_XXXX");
312     GNUNET_SQ_reset (plugin->dbh,
313                      stmt);
314     return GNUNET_SYSERR;
315   }
316
317   err = 0;
318   while (SQLITE_ROW == (sret = sqlite3_step (stmt)))
319   {
320     LOG (GNUNET_ERROR_TYPE_DEBUG,
321          "Returning a matched record.\n");
322     struct GNUNET_SQ_ResultSpec rs[] = {
323       GNUNET_SQ_result_spec_string (&rec.sub_system),
324       GNUNET_SQ_result_spec_auto_from_type (&rec.peer),
325       GNUNET_SQ_result_spec_string (&rec.key),
326       GNUNET_SQ_result_spec_variable_size (&rec.value, &rec.value_size),
327       GNUNET_SQ_result_spec_absolute_time (&rec.expiry),
328       GNUNET_SQ_result_spec_end
329     };
330
331     if (GNUNET_OK !=
332         GNUNET_SQ_extract_result (stmt,
333                                   rs))
334     {
335       GNUNET_break (0);
336       break;
337     }
338     if (NULL != iter)
339       iter (iter_cls,
340             &rec,
341             NULL);
342     GNUNET_SQ_cleanup_result (rs);
343   }
344   if (SQLITE_DONE != sret)
345   {
346     LOG_SQLITE (plugin,
347                 GNUNET_ERROR_TYPE_ERROR,
348                 "sqlite_step");
349     err = 1;
350   }
351   GNUNET_SQ_reset (plugin->dbh,
352                    stmt);
353   if (NULL != iter)
354     iter (iter_cls,
355           NULL,
356           err ? "sqlite error" : NULL);
357   return GNUNET_OK;
358 }
359
360
361 /**
362  * Store a record in the peerstore.
363  * Key is the combination of sub system and peer identity.
364  * One key can store multiple values.
365  *
366  * @param cls closure (internal context for the plugin)
367  * @param sub_system name of the GNUnet sub system responsible
368  * @param peer peer identity
369  * @param key record key string
370  * @param value value to be stored
371  * @param size size of value to be stored
372  * @param expiry absolute time after which the record is (possibly) deleted
373  * @param options options related to the store operation
374  * @param cont continuation called when record is stored
375  * @param cont_cls continuation closure
376  * @return #GNUNET_OK on success, else #GNUNET_SYSERR and cont is not called
377  */
378 static int
379 peerstore_sqlite_store_record (void *cls,
380                                const char *sub_system,
381                                const struct GNUNET_PeerIdentity *peer,
382                                const char *key,
383                                const void *value,
384                                size_t size,
385                                struct GNUNET_TIME_Absolute expiry,
386                                enum GNUNET_PEERSTORE_StoreOption options,
387                                GNUNET_PEERSTORE_Continuation cont,
388                                void *cont_cls)
389 {
390   struct Plugin *plugin = cls;
391   sqlite3_stmt *stmt = plugin->insert_peerstoredata;
392   struct GNUNET_SQ_QueryParam params[] = {
393     GNUNET_SQ_query_param_string (sub_system),
394     GNUNET_SQ_query_param_auto_from_type (peer),
395     GNUNET_SQ_query_param_string (key),
396     GNUNET_SQ_query_param_fixed_size (value, size),
397     GNUNET_SQ_query_param_absolute_time (&expiry),
398     GNUNET_SQ_query_param_end
399   };
400
401   if (GNUNET_PEERSTORE_STOREOPTION_REPLACE == options)
402   {
403     peerstore_sqlite_delete_records (cls,
404                                      sub_system,
405                                      peer,
406                                      key);
407   }
408   if (GNUNET_OK !=
409       GNUNET_SQ_bind (stmt,
410                       params))
411     LOG_SQLITE (plugin,
412                 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
413                 "sqlite3_bind");
414   else if (SQLITE_DONE != sqlite3_step (stmt))
415   {
416     LOG_SQLITE (plugin,
417                 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
418                 "sqlite3_step");
419   }
420   GNUNET_SQ_reset (plugin->dbh,
421                    stmt);
422   if (NULL != cont)
423     cont (cont_cls,
424           GNUNET_OK);
425   return GNUNET_OK;
426 }
427
428
429 /**
430  * @brief Prepare a SQL statement
431  *
432  * @param dbh handle to the database
433  * @param sql SQL statement, UTF-8 encoded
434  * @return 0 on success
435  */
436 static int
437 sql_exec (sqlite3 *dbh,
438           const char *sql)
439 {
440   int result;
441
442   result = sqlite3_exec (dbh,
443                          sql,
444                          NULL,
445                          NULL,
446                          NULL);
447   LOG (GNUNET_ERROR_TYPE_DEBUG,
448        "Executed `%s' / %d\n",
449        sql,
450        result);
451   if (SQLITE_OK != result)
452     LOG (GNUNET_ERROR_TYPE_ERROR,
453          _("Error executing SQL query: %s\n  %s\n"),
454          sqlite3_errmsg (dbh),
455          sql);
456   return result;
457 }
458
459
460 /**
461  * @brief Prepare a SQL statement
462  *
463  * @param dbh handle to the database
464  * @param sql SQL statement, UTF-8 encoded
465  * @param stmt set to the prepared statement
466  * @return 0 on success
467  */
468 static int
469 sql_prepare (sqlite3 *dbh,
470              const char *sql,
471              sqlite3_stmt ** stmt)
472 {
473   char *tail;
474   int result;
475
476   result = sqlite3_prepare_v2 (dbh,
477                                sql,
478                                strlen (sql),
479                                stmt,
480                                (const char **) &tail);
481   LOG (GNUNET_ERROR_TYPE_DEBUG,
482        "Prepared `%s' / %p: %d\n",
483        sql,
484        *stmt,
485        result);
486   if (SQLITE_OK != result)
487     LOG (GNUNET_ERROR_TYPE_ERROR,
488          _("Error preparing SQL query: %s\n  %s\n"),
489          sqlite3_errmsg (dbh),
490          sql);
491   return result;
492 }
493
494
495 /**
496  * Initialize the database connections and associated
497  * data structures (create tables and indices
498  * as needed as well).
499  *
500  * @param plugin the plugin context (state for this module)
501  * @return #GNUNET_OK on success
502  */
503 static int
504 database_setup (struct Plugin *plugin)
505 {
506   char *filename;
507
508   if (GNUNET_OK !=
509       GNUNET_CONFIGURATION_get_value_filename (plugin->cfg,
510                                                "peerstore-sqlite",
511                                                "FILENAME",
512                                                &filename))
513   {
514     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
515                                "peerstore-sqlite",
516                                "FILENAME");
517     return GNUNET_SYSERR;
518   }
519   if (GNUNET_OK != GNUNET_DISK_file_test (filename))
520   {
521     if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (filename))
522     {
523       GNUNET_break (0);
524       GNUNET_free (filename);
525       return GNUNET_SYSERR;
526     }
527   }
528   /* filename should be UTF-8-encoded. If it isn't, it's a bug */
529   plugin->fn = filename;
530   /* Open database and precompile statements */
531   if (SQLITE_OK != sqlite3_open (plugin->fn,
532                                  &plugin->dbh))
533   {
534     LOG (GNUNET_ERROR_TYPE_ERROR,
535          _("Unable to initialize SQLite: %s.\n"),
536          sqlite3_errmsg (plugin->dbh));
537     return GNUNET_SYSERR;
538   }
539   sql_exec (plugin->dbh,
540             "PRAGMA temp_store=MEMORY");
541   sql_exec (plugin->dbh,
542             "PRAGMA synchronous=OFF");
543   sql_exec (plugin->dbh,
544             "PRAGMA legacy_file_format=OFF");
545   sql_exec (plugin->dbh,
546             "PRAGMA auto_vacuum=INCREMENTAL");
547   sql_exec (plugin->dbh,
548             "PRAGMA encoding=\"UTF-8\"");
549   sql_exec (plugin->dbh,
550             "PRAGMA page_size=4096");
551   sqlite3_busy_timeout (plugin->dbh,
552                         BUSY_TIMEOUT_MS);
553   /* Create tables */
554   sql_exec (plugin->dbh,
555             "CREATE TABLE IF NOT EXISTS peerstoredata (\n"
556             "  sub_system TEXT NOT NULL,\n"
557             "  peer_id BLOB NOT NULL,\n"
558             "  key TEXT NOT NULL,\n"
559             "  value BLOB NULL,\n"
560             "  expiry INT8 NOT NULL" ");");
561   /* Create Indices */
562   if (SQLITE_OK !=
563       sqlite3_exec (plugin->dbh,
564                     "CREATE INDEX IF NOT EXISTS peerstoredata_key_index ON peerstoredata (sub_system, peer_id, key)",
565                     NULL,
566                     NULL,
567                     NULL))
568   {
569     LOG (GNUNET_ERROR_TYPE_ERROR,
570          _("Unable to create indices: %s.\n"),
571          sqlite3_errmsg (plugin->dbh));
572     return GNUNET_SYSERR;
573   }
574   /* Prepare statements */
575
576   sql_prepare (plugin->dbh,
577                "INSERT INTO peerstoredata (sub_system, peer_id, key, value, expiry)"
578                " VALUES (?,?,?,?,?);",
579                &plugin->insert_peerstoredata);
580   sql_prepare (plugin->dbh,
581                "SELECT sub_system,peer_id,key,value,expiry FROM peerstoredata"
582                " WHERE sub_system = ?",
583                &plugin->select_peerstoredata);
584   sql_prepare (plugin->dbh,
585                "SELECT sub_system,peer_id,key,value,expiry FROM peerstoredata"
586                " WHERE sub_system = ?"
587                " AND peer_id = ?",
588                &plugin->select_peerstoredata_by_pid);
589   sql_prepare (plugin->dbh,
590                "SELECT sub_system,peer_id,key,value,expiry FROM peerstoredata"
591                " WHERE sub_system = ?"
592                " AND key = ?",
593                &plugin->select_peerstoredata_by_key);
594   sql_prepare (plugin->dbh,
595                "SELECT sub_system,peer_id,key,value,expiry FROM peerstoredata"
596                " WHERE sub_system = ?"
597                " AND peer_id = ?" " AND key = ?",
598                &plugin->select_peerstoredata_by_all);
599   sql_prepare (plugin->dbh,
600                "DELETE FROM peerstoredata"
601                " WHERE expiry < ?",
602                &plugin->expire_peerstoredata);
603   sql_prepare (plugin->dbh,
604                "DELETE FROM peerstoredata"
605                " WHERE sub_system = ?"
606                " AND peer_id = ?" " AND key = ?",
607                &plugin->delete_peerstoredata);
608   return GNUNET_OK;
609 }
610
611
612 /**
613  * Shutdown database connection and associate data
614  * structures.
615  * @param plugin the plugin context (state for this module)
616  */
617 static void
618 database_shutdown (struct Plugin *plugin)
619 {
620   int result;
621   sqlite3_stmt *stmt;
622
623   while (NULL != (stmt = sqlite3_next_stmt (plugin->dbh,
624                                             NULL)))
625   {
626     result = sqlite3_finalize (stmt);
627     if (SQLITE_OK != result)
628       LOG (GNUNET_ERROR_TYPE_WARNING,
629            "Failed to close statement %p: %d\n",
630            stmt,
631            result);
632   }
633   if (SQLITE_OK != sqlite3_close (plugin->dbh))
634     LOG_SQLITE (plugin,
635                 GNUNET_ERROR_TYPE_ERROR,
636                 "sqlite3_close");
637   GNUNET_free_non_null (plugin->fn);
638 }
639
640
641 /**
642  * Entry point for the plugin.
643  *
644  * @param cls The struct GNUNET_CONFIGURATION_Handle.
645  * @return NULL on error, otherwise the plugin context
646  */
647 void *
648 libgnunet_plugin_peerstore_sqlite_init (void *cls)
649 {
650   static struct Plugin plugin;
651   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
652   struct GNUNET_PEERSTORE_PluginFunctions *api;
653
654   if (NULL != plugin.cfg)
655     return NULL;                /* can only initialize once! */
656   memset (&plugin,
657           0,
658           sizeof (struct Plugin));
659   plugin.cfg = cfg;
660   if (GNUNET_OK != database_setup (&plugin))
661   {
662     database_shutdown (&plugin);
663     return NULL;
664   }
665   api = GNUNET_new (struct GNUNET_PEERSTORE_PluginFunctions);
666   api->cls = &plugin;
667   api->store_record = &peerstore_sqlite_store_record;
668   api->iterate_records = &peerstore_sqlite_iterate_records;
669   api->expire_records = &peerstore_sqlite_expire_records;
670   LOG (GNUNET_ERROR_TYPE_DEBUG,
671        "Sqlite plugin is running\n");
672   return api;
673 }
674
675
676 /**
677  * Exit point from the plugin.
678  *
679  * @param cls The plugin context (as returned by "init")
680  * @return Always NULL
681  */
682 void *
683 libgnunet_plugin_peerstore_sqlite_done (void *cls)
684 {
685   struct GNUNET_PEERSTORE_PluginFunctions *api = cls;
686   struct Plugin *plugin = api->cls;
687
688   database_shutdown (plugin);
689   plugin->cfg = NULL;
690   GNUNET_free (api);
691   LOG (GNUNET_ERROR_TYPE_DEBUG,
692        "Sqlite plugin is finished\n");
693   return NULL;
694 }
695
696 /* end of plugin_peerstore_sqlite.c */