84794e1acab9b16938fe49963fc933f31526512a
[oweals/gnunet.git] / src / namecache / plugin_namecache_sqlite.c
1  /*
2   * This file is part of GNUnet
3   * Copyright (C) 2009-2013 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
19 /**
20  * @file namecache/plugin_namecache_sqlite.c
21  * @brief sqlite-based namecache backend
22  * @author Christian Grothoff
23  */
24 #include "platform.h"
25 #include "gnunet_sq_lib.h"
26 #include "gnunet_namecache_plugin.h"
27 #include "gnunet_namecache_service.h"
28 #include "gnunet_gnsrecord_lib.h"
29 #include "namecache.h"
30 #include <sqlite3.h>
31
32 /**
33  * After how many ms "busy" should a DB operation fail for good?  A
34  * low value makes sure that we are more responsive to requests
35  * (especially PUTs).  A high value guarantees a higher success rate
36  * (SELECTs in iterate can take several seconds despite LIMIT=1).
37  *
38  * The default value of 1s should ensure that users do not experience
39  * huge latencies while at the same time allowing operations to
40  * succeed with reasonable probability.
41  */
42 #define BUSY_TIMEOUT_MS 1000
43
44
45 /**
46  * Log an error message at log-level 'level' that indicates
47  * a failure of the command 'cmd' on file 'filename'
48  * with the message given by strerror(errno).
49  */
50 #define LOG_SQLITE(db, level, cmd) do { GNUNET_log_from (level, "namecache-sqlite", _("`%s' failed at %s:%d with error: %s\n"), cmd, __FILE__, __LINE__, sqlite3_errmsg(db->dbh)); } while(0)
51
52 #define LOG(kind,...) GNUNET_log_from (kind, "namecache-sqlite", __VA_ARGS__)
53
54
55 /**
56  * Context for all functions in this plugin.
57  */
58 struct Plugin
59 {
60
61   const struct GNUNET_CONFIGURATION_Handle *cfg;
62
63   /**
64    * Database filename.
65    */
66   char *fn;
67
68   /**
69    * Native SQLite database handle.
70    */
71   sqlite3 *dbh;
72
73   /**
74    * Precompiled SQL for caching a block
75    */
76   sqlite3_stmt *cache_block;
77
78   /**
79    * Precompiled SQL for deleting an older block
80    */
81   sqlite3_stmt *delete_block;
82
83   /**
84    * Precompiled SQL for looking up a block
85    */
86   sqlite3_stmt *lookup_block;
87
88   /**
89    * Precompiled SQL for removing expired blocks
90    */
91   sqlite3_stmt *expire_blocks;
92
93 };
94
95
96 /**
97  * Initialize the database connections and associated
98  * data structures (create tables and indices
99  * as needed as well).
100  *
101  * @param plugin the plugin context (state for this module)
102  * @return #GNUNET_OK on success
103  */
104 static int
105 database_setup (struct Plugin *plugin)
106 {
107   struct GNUNET_SQ_ExecuteStatement es[] = {
108     GNUNET_SQ_make_try_execute ("PRAGMA temp_store=MEMORY"),
109     GNUNET_SQ_make_try_execute ("PRAGMA synchronous=NORMAL"),
110     GNUNET_SQ_make_try_execute ("PRAGMA legacy_file_format=OFF"),
111     GNUNET_SQ_make_try_execute ("PRAGMA auto_vacuum=INCREMENTAL"),
112     GNUNET_SQ_make_try_execute ("PRAGMA encoding=\"UTF-8\""),
113     GNUNET_SQ_make_try_execute ("PRAGMA locking_mode=EXCLUSIVE"),
114     GNUNET_SQ_make_try_execute ("PRAGMA page_size=4092"),
115     GNUNET_SQ_make_try_execute ("PRAGMA journal_mode=WAL"),
116     GNUNET_SQ_make_execute ("CREATE TABLE IF NOT EXISTS ns096blocks ("
117                             " query BLOB NOT NULL,"
118                             " block BLOB NOT NULL,"
119                             " expiration_time INT8 NOT NULL"
120                             ")"),
121     GNUNET_SQ_make_execute ("CREATE INDEX IF NOT EXISTS ir_query_hash "
122                             "ON ns096blocks (query,expiration_time)"),
123     GNUNET_SQ_make_execute ("CREATE INDEX IF NOT EXISTS ir_block_expiration "
124                             "ON ns096blocks (expiration_time)"),
125     GNUNET_SQ_EXECUTE_STATEMENT_END
126   };
127   struct GNUNET_SQ_PrepareStatement ps[] = {
128     GNUNET_SQ_make_prepare ("INSERT INTO ns096blocks (query,block,expiration_time) VALUES (?, ?, ?)",
129                             &plugin->cache_block),
130     GNUNET_SQ_make_prepare ("DELETE FROM ns096blocks WHERE expiration_time<?",
131                             &plugin->expire_blocks),
132     GNUNET_SQ_make_prepare ("DELETE FROM ns096blocks WHERE query=? AND expiration_time<=?",
133                             &plugin->delete_block),
134     GNUNET_SQ_make_prepare ("SELECT block FROM ns096blocks WHERE query=? "
135                             "ORDER BY expiration_time DESC LIMIT 1",
136                             &plugin->lookup_block),
137     GNUNET_SQ_PREPARE_END
138   };
139   char *afsdir;
140
141   if (GNUNET_OK !=
142       GNUNET_CONFIGURATION_get_value_filename (plugin->cfg,
143                                                "namecache-sqlite",
144                                                "FILENAME",
145                                                &afsdir))
146   {
147     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
148                                "namecache-sqlite",
149                                "FILENAME");
150     return GNUNET_SYSERR;
151   }
152   if (GNUNET_OK !=
153       GNUNET_DISK_file_test (afsdir))
154   {
155     if (GNUNET_OK !=
156         GNUNET_DISK_directory_create_for_file (afsdir))
157     {
158       GNUNET_break (0);
159       GNUNET_free (afsdir);
160       return GNUNET_SYSERR;
161     }
162   }
163   /* afsdir should be UTF-8-encoded. If it isn't, it's a bug */
164   plugin->fn = afsdir;
165
166   /* Open database and precompile statements */
167   if (SQLITE_OK !=
168       sqlite3_open (plugin->fn, &plugin->dbh))
169   {
170     LOG (GNUNET_ERROR_TYPE_ERROR,
171          _("Unable to initialize SQLite: %s.\n"),
172          sqlite3_errmsg (plugin->dbh));
173     return GNUNET_SYSERR;
174   }
175   if (GNUNET_OK !=
176       GNUNET_SQ_exec_statements (plugin->dbh,
177                                  es))
178   {
179     GNUNET_break (0);
180     LOG (GNUNET_ERROR_TYPE_ERROR,
181          _("Failed to setup database at `%s'\n"),
182          plugin->fn);
183     return GNUNET_SYSERR;
184   }
185   GNUNET_break (SQLITE_OK ==
186                 sqlite3_busy_timeout (plugin->dbh,
187                                       BUSY_TIMEOUT_MS));
188   
189   if (GNUNET_OK !=
190       GNUNET_SQ_prepare (plugin->dbh,
191                          ps))
192   {
193     GNUNET_break (0);
194     LOG (GNUNET_ERROR_TYPE_ERROR,
195          _("Failed to setup database at `%s'\n"),
196          plugin->fn);
197     return GNUNET_SYSERR;
198   }
199
200   return GNUNET_OK;
201 }
202
203
204 /**
205  * Shutdown database connection and associate data
206  * structures.
207  * @param plugin the plugin context (state for this module)
208  */
209 static void
210 database_shutdown (struct Plugin *plugin)
211 {
212   int result;
213   sqlite3_stmt *stmt;
214
215   if (NULL != plugin->cache_block)
216     sqlite3_finalize (plugin->cache_block);
217   if (NULL != plugin->lookup_block)
218     sqlite3_finalize (plugin->lookup_block);
219   if (NULL != plugin->expire_blocks)
220     sqlite3_finalize (plugin->expire_blocks);
221   if (NULL != plugin->delete_block)
222     sqlite3_finalize (plugin->delete_block);
223   result = sqlite3_close (plugin->dbh);
224   if (result == SQLITE_BUSY)
225   {
226     LOG (GNUNET_ERROR_TYPE_WARNING,
227          _("Tried to close sqlite without finalizing all prepared statements.\n"));
228     stmt = sqlite3_next_stmt (plugin->dbh,
229                               NULL);
230     while (stmt != NULL)
231     {
232       GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
233                        "sqlite",
234                        "Closing statement %p\n",
235                        stmt);
236       result = sqlite3_finalize (stmt);
237       if (result != SQLITE_OK)
238         GNUNET_log_from (GNUNET_ERROR_TYPE_WARNING,
239                          "sqlite",
240                          "Failed to close statement %p: %d\n",
241                          stmt,
242                          result);
243       stmt = sqlite3_next_stmt (plugin->dbh,
244                                 NULL);
245     }
246     result = sqlite3_close (plugin->dbh);
247   }
248   if (SQLITE_OK != result)
249     LOG_SQLITE (plugin,
250                 GNUNET_ERROR_TYPE_ERROR,
251                 "sqlite3_close");
252
253   GNUNET_free_non_null (plugin->fn);
254 }
255
256
257 /**
258  * Removes any expired block.
259  *
260  * @param plugin the plugin
261  */
262 static void
263 namecache_sqlite_expire_blocks (struct Plugin *plugin)
264 {
265   struct GNUNET_TIME_Absolute now;
266   struct GNUNET_SQ_QueryParam params[] = {
267     GNUNET_SQ_query_param_absolute_time (&now),
268     GNUNET_SQ_query_param_end
269   };
270   int n;
271
272   now = GNUNET_TIME_absolute_get ();
273   if (GNUNET_OK !=
274       GNUNET_SQ_bind (plugin->expire_blocks,
275                       params))
276   {
277     LOG_SQLITE (plugin,
278                 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
279                 "sqlite3_bind_XXXX");
280     GNUNET_SQ_reset (plugin->dbh,
281                      plugin->expire_blocks);
282     return;
283   }
284   n = sqlite3_step (plugin->expire_blocks);
285   GNUNET_SQ_reset (plugin->dbh,
286                    plugin->expire_blocks);
287   switch (n)
288   {
289   case SQLITE_DONE:
290     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
291                      "sqlite",
292                      "Records expired\n");
293     return;
294   case SQLITE_BUSY:
295     LOG_SQLITE (plugin,
296                 GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK,
297                 "sqlite3_step");
298     return;
299   default:
300     LOG_SQLITE (plugin,
301                 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
302                 "sqlite3_step");
303     return;
304   }
305 }
306
307
308 /**
309  * Cache a block in the datastore.
310  *
311  * @param cls closure (internal context for the plugin)
312  * @param block block to cache
313  * @return #GNUNET_OK on success, else #GNUNET_SYSERR
314  */
315 static int
316 namecache_sqlite_cache_block (void *cls,
317                               const struct GNUNET_GNSRECORD_Block *block)
318 {
319   static struct GNUNET_TIME_Absolute last_expire;
320   struct Plugin *plugin = cls;
321   struct GNUNET_HashCode query;
322   struct GNUNET_TIME_Absolute expiration;
323   size_t block_size = ntohl (block->purpose.size) +
324     sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) +
325     sizeof (struct GNUNET_CRYPTO_EcdsaSignature);
326   struct GNUNET_SQ_QueryParam del_params[] = {
327     GNUNET_SQ_query_param_auto_from_type (&query),
328     GNUNET_SQ_query_param_absolute_time (&expiration),
329     GNUNET_SQ_query_param_end
330   };
331   struct GNUNET_SQ_QueryParam ins_params[] = {
332     GNUNET_SQ_query_param_auto_from_type (&query),
333     GNUNET_SQ_query_param_fixed_size (block,
334                                       block_size),
335     GNUNET_SQ_query_param_absolute_time (&expiration),
336     GNUNET_SQ_query_param_end
337   };
338   int n;
339
340   /* run expiration of old cache entries once per hour */
341   if (GNUNET_TIME_absolute_get_duration (last_expire).rel_value_us >
342       GNUNET_TIME_UNIT_HOURS.rel_value_us)
343   {
344     last_expire = GNUNET_TIME_absolute_get ();
345     namecache_sqlite_expire_blocks (plugin);
346   }
347   GNUNET_CRYPTO_hash (&block->derived_key,
348                       sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
349                       &query);
350   expiration = GNUNET_TIME_absolute_ntoh (block->expiration_time);
351   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
352               "Caching new version of block %s (expires %s)\n",
353               GNUNET_h2s (&query),
354               GNUNET_STRINGS_absolute_time_to_string (expiration));
355   if (block_size > 64 * 65536)
356   {
357     GNUNET_break (0);
358     return GNUNET_SYSERR;
359   }
360
361   /* delete old version of the block */
362   if (GNUNET_OK !=
363       GNUNET_SQ_bind (plugin->delete_block,
364                       del_params))
365   {
366     LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
367                 "sqlite3_bind_XXXX");
368     GNUNET_SQ_reset (plugin->dbh,
369                      plugin->delete_block);
370     return GNUNET_SYSERR;
371   }
372   n = sqlite3_step (plugin->delete_block);
373   switch (n)
374   {
375   case SQLITE_DONE:
376     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
377                      "sqlite",
378                      "Old block deleted\n");
379     break;
380   case SQLITE_BUSY:
381     LOG_SQLITE (plugin,
382                 GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK,
383                 "sqlite3_step");
384     break;
385   default:
386     LOG_SQLITE (plugin,
387                 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
388                 "sqlite3_step");
389     break;
390   }
391   GNUNET_SQ_reset (plugin->dbh,
392                    plugin->delete_block);
393
394   /* insert new version of the block */
395   if (GNUNET_OK !=
396       GNUNET_SQ_bind (plugin->cache_block,
397                       ins_params))
398   {
399     LOG_SQLITE (plugin,
400                 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
401                 "sqlite3_bind_XXXX");
402     GNUNET_SQ_reset (plugin->dbh,
403                      plugin->cache_block);
404     return GNUNET_SYSERR;
405
406   }
407   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
408               "Caching block under derived key `%s'\n",
409               GNUNET_h2s_full (&query));
410   n = sqlite3_step (plugin->cache_block);
411   GNUNET_SQ_reset (plugin->dbh,
412                    plugin->cache_block);
413   switch (n)
414   {
415   case SQLITE_DONE:
416     LOG (GNUNET_ERROR_TYPE_DEBUG,
417          "Record stored\n");
418     return GNUNET_OK;
419   case SQLITE_BUSY:
420     LOG_SQLITE (plugin,
421                 GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK,
422                 "sqlite3_step");
423     return GNUNET_NO;
424   default:
425     LOG_SQLITE (plugin,
426                 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
427                 "sqlite3_step");
428     return GNUNET_SYSERR;
429   }
430 }
431
432
433 /**
434  * Get the block for a particular zone and label in the
435  * datastore.  Will return at most one result to the iterator.
436  *
437  * @param cls closure (internal context for the plugin)
438  * @param query hash of public key derived from the zone and the label
439  * @param iter function to call with the result
440  * @param iter_cls closure for @a iter
441  * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
442  */
443 static int
444 namecache_sqlite_lookup_block (void *cls,
445                                const struct GNUNET_HashCode *query,
446                                GNUNET_NAMECACHE_BlockCallback iter,
447                                void *iter_cls)
448 {
449   struct Plugin *plugin = cls;
450   int ret;
451   int sret;
452   size_t block_size;
453   const struct GNUNET_GNSRECORD_Block *block;
454   struct GNUNET_SQ_QueryParam params[] = {
455     GNUNET_SQ_query_param_auto_from_type (query),
456     GNUNET_SQ_query_param_end
457   };
458   struct GNUNET_SQ_ResultSpec rs[] = {
459     GNUNET_SQ_result_spec_variable_size ((void **) &block,
460                                          &block_size),
461     GNUNET_SQ_result_spec_end
462   };
463
464   if (GNUNET_OK !=
465       GNUNET_SQ_bind (plugin->lookup_block,
466                       params))
467   {
468     LOG_SQLITE (plugin,
469                 GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
470                 "sqlite3_bind_XXXX");
471     GNUNET_SQ_reset (plugin->dbh,
472                      plugin->lookup_block);
473     return GNUNET_SYSERR;
474   }
475   ret = GNUNET_NO;
476   if (SQLITE_ROW ==
477       (sret = sqlite3_step (plugin->lookup_block)))
478   {
479     if (GNUNET_OK !=
480         GNUNET_SQ_extract_result (plugin->lookup_block,
481                                   rs))
482     {
483       GNUNET_break (0);
484       ret = GNUNET_SYSERR;
485     }
486     else if ( (block_size < sizeof (struct GNUNET_GNSRECORD_Block)) ||
487               (ntohl (block->purpose.size) +
488                sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) +
489                sizeof (struct GNUNET_CRYPTO_EcdsaSignature) != block_size) )
490     {
491       GNUNET_break (0);
492       GNUNET_SQ_cleanup_result (rs);
493       ret = GNUNET_SYSERR;
494     }
495     else
496     {
497       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
498                   "Found block under derived key `%s'\n",
499                   GNUNET_h2s_full (query));
500       iter (iter_cls,
501             block);
502       GNUNET_SQ_cleanup_result (rs);
503       ret = GNUNET_YES;
504     }
505   }
506   else
507   {
508     if (SQLITE_DONE != sret)
509     {
510       LOG_SQLITE (plugin,
511                   GNUNET_ERROR_TYPE_ERROR,
512                   "sqlite_step");
513       ret = GNUNET_SYSERR;
514     }
515     else
516     {
517       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
518                   "No block found under derived key `%s'\n",
519                   GNUNET_h2s_full (query));
520     }
521   }
522   GNUNET_SQ_reset (plugin->dbh,
523                    plugin->lookup_block);
524   return ret;
525 }
526
527
528 /**
529  * Entry point for the plugin.
530  *
531  * @param cls the "struct GNUNET_NAMECACHE_PluginEnvironment*"
532  * @return NULL on error, otherwise the plugin context
533  */
534 void *
535 libgnunet_plugin_namecache_sqlite_init (void *cls)
536 {
537   static struct Plugin plugin;
538   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
539   struct GNUNET_NAMECACHE_PluginFunctions *api;
540
541   if (NULL != plugin.cfg)
542     return NULL;                /* can only initialize once! */
543   memset (&plugin, 0, sizeof (struct Plugin));
544   plugin.cfg = cfg;
545   if (GNUNET_OK != database_setup (&plugin))
546   {
547     database_shutdown (&plugin);
548     return NULL;
549   }
550   api = GNUNET_new (struct GNUNET_NAMECACHE_PluginFunctions);
551   api->cls = &plugin;
552   api->cache_block = &namecache_sqlite_cache_block;
553   api->lookup_block = &namecache_sqlite_lookup_block;
554   LOG (GNUNET_ERROR_TYPE_INFO,
555        _("Sqlite database running\n"));
556   return api;
557 }
558
559
560 /**
561  * Exit point from the plugin.
562  *
563  * @param cls the plugin context (as returned by "init")
564  * @return always NULL
565  */
566 void *
567 libgnunet_plugin_namecache_sqlite_done (void *cls)
568 {
569   struct GNUNET_NAMECACHE_PluginFunctions *api = cls;
570   struct Plugin *plugin = api->cls;
571
572   database_shutdown (plugin);
573   plugin->cfg = NULL;
574   GNUNET_free (api);
575   LOG (GNUNET_ERROR_TYPE_DEBUG,
576        "sqlite plugin is finished\n");
577   return NULL;
578 }
579
580 /* end of plugin_namecache_sqlite.c */