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