9d044aa0773fe2d55f5d8da28116461f85ee109c
[oweals/gnunet.git] / src / peerstore / plugin_peerstore_sqlite.c
1 /*
2  * This file is part of GNUnet
3  * (C) 2013 Christian Grothoff (and other contributing authors)
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., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 /**
22  * @file peerstore/plugin_peerstore_sqlite.c
23  * @brief sqlite-based peerstore backend
24  * @author Omar Tarabai
25  */
26
27 #include "platform.h"
28 #include "gnunet_peerstore_plugin.h"
29 #include "gnunet_peerstore_service.h"
30 #include "peerstore.h"
31 #include <sqlite3.h>
32
33 /**
34  * After how many ms "busy" should a DB operation fail for good?  A
35  * low value makes sure that we are more responsive to requests
36  * (especially PUTs).  A high value guarantees a higher success rate
37  * (SELECTs in iterate can take several seconds despite LIMIT=1).
38  *
39  * The default value of 1s should ensure that users do not experience
40  * huge latencies while at the same time allowing operations to
41  * succeed with reasonable probability.
42  */
43 #define BUSY_TIMEOUT_MS 1000
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, "peerstore-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, "peerstore-sqlite", __VA_ARGS__)
53
54 /**
55  * Context for all functions in this plugin.
56  */
57 struct Plugin
58 {
59
60   /**
61    * Configuration handle
62    */
63   const struct GNUNET_CONFIGURATION_Handle *cfg;
64
65   /**
66    * Database filename.
67    */
68   char *fn;
69
70   /**
71    * Native SQLite database handle.
72    */
73   sqlite3 *dbh;
74
75   /**
76    * Precompiled SQL for inserting into peerstoredata
77    */
78   sqlite3_stmt *insert_peerstoredata;
79
80   /**
81    * Precompiled SQL for selecting from peerstoredata
82    */
83   sqlite3_stmt *select_peerstoredata;
84
85   /**
86    * Precompiled SQL for selecting from peerstoredata
87    */
88   sqlite3_stmt *select_peerstoredata_by_pid;
89
90   /**
91    * Precompiled SQL for selecting from peerstoredata
92    */
93   sqlite3_stmt *select_peerstoredata_by_key;
94
95   /**
96    * Precompiled SQL for selecting from peerstoredata
97    */
98   sqlite3_stmt *select_peerstoredata_by_all;
99
100 };
101
102 /**
103  * Iterate over the records given an optional peer id
104  * and/or key.
105  *
106  * @param cls closure (internal context for the plugin)
107  * @param sub_system name of sub system
108  * @param peer Peer identity (can be NULL)
109  * @param key entry key string (can be NULL)
110  * @param iter function to call with the result
111  * @param iter_cls closure for @a iter
112  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
113  */
114 static int
115 peerstore_sqlite_iterate_records (void *cls,
116     const char *sub_system,
117     const struct GNUNET_PeerIdentity *peer,
118     const char *key,
119     GNUNET_PEERSTORE_Processor iter, void *iter_cls)
120 {
121   struct Plugin *plugin = cls;
122   sqlite3_stmt *stmt;
123   int err = 0;
124   int sret;
125   struct GNUNET_PEERSTORE_Record *ret;
126
127   LOG(GNUNET_ERROR_TYPE_DEBUG, "Executing iterate request on sqlite db.\n");
128   if(NULL == peer && NULL == key)
129   {
130     stmt = plugin->select_peerstoredata;
131     err = (SQLITE_OK != sqlite3_bind_text(stmt, 1, sub_system, strlen(sub_system) + 1, SQLITE_STATIC));
132   }
133   else if(NULL == key)
134   {
135     stmt = plugin->select_peerstoredata_by_pid;
136     err = (SQLITE_OK != sqlite3_bind_text(stmt, 1, sub_system, strlen(sub_system) + 1, SQLITE_STATIC))
137         || (SQLITE_OK != sqlite3_bind_blob(stmt, 2, peer, sizeof(struct GNUNET_PeerIdentity), SQLITE_STATIC));
138   }
139   else if(NULL == peer)
140   {
141     stmt = plugin->select_peerstoredata_by_key;
142     err = (SQLITE_OK != sqlite3_bind_text(stmt, 1, sub_system, strlen(sub_system) + 1, SQLITE_STATIC))
143         || (SQLITE_OK != sqlite3_bind_text(stmt, 3, key, strlen(key) + 1, SQLITE_STATIC));
144   }
145   else
146   {
147     stmt = plugin->select_peerstoredata_by_all;
148     err = (SQLITE_OK != sqlite3_bind_text(stmt, 1, sub_system, strlen(sub_system) + 1, SQLITE_STATIC))
149             || (SQLITE_OK != sqlite3_bind_blob(stmt, 2, peer, sizeof(struct GNUNET_PeerIdentity), SQLITE_STATIC))
150             || (SQLITE_OK != sqlite3_bind_text(stmt, 3, key, strlen(key) + 1, SQLITE_STATIC));
151   }
152
153   if (err)
154   {
155     LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
156     "sqlite3_bind_XXXX");
157     if (SQLITE_OK != sqlite3_reset (stmt))
158       LOG_SQLITE (plugin,
159       GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
160       "sqlite3_reset");
161     return GNUNET_SYSERR;
162   }
163   while (SQLITE_ROW == (sret = sqlite3_step (stmt)))
164   {
165     LOG(GNUNET_ERROR_TYPE_DEBUG, "Returning a matched record.\n");
166     ret = GNUNET_new(struct GNUNET_PEERSTORE_Record);
167     ret->sub_system = (char *)sqlite3_column_text(stmt, 0);
168     ret->peer = (struct GNUNET_PeerIdentity *)sqlite3_column_blob(stmt, 1);
169     ret->key = (char *)sqlite3_column_text(stmt, 2);
170     ret->value = (void *)sqlite3_column_blob(stmt, 3);
171     ret->value_size = sqlite3_column_bytes(stmt, 3);
172     ret->expiry = GNUNET_new(struct GNUNET_TIME_Absolute);
173     ret->expiry->abs_value_us = (uint64_t)sqlite3_column_int64(stmt, 4);
174     if (NULL != iter)
175       iter (iter_cls,
176           ret,
177           NULL);
178   }
179   if (SQLITE_DONE != sret)
180   {
181     LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR, "sqlite_step");
182     err = 1;
183   }
184   if (SQLITE_OK != sqlite3_reset (stmt))
185   {
186     LOG_SQLITE (plugin,
187     GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
188     "sqlite3_reset");
189     err = 1;
190   }
191   if(err)
192     return GNUNET_SYSERR;
193   return GNUNET_OK;
194 }
195
196 /**
197  * Store a record in the peerstore.
198  * Key is the combination of sub system and peer identity.
199  * One key can store multiple values.
200  *
201  * @param cls closure (internal context for the plugin)
202  * @param peer peer identity
203  * @param sub_system name of the GNUnet sub system responsible
204  * @param value value to be stored
205  * @param size size of value to be stored
206  * @return #GNUNET_OK on success, else #GNUNET_SYSERR
207  */
208 static int
209 peerstore_sqlite_store_record(void *cls,
210     const char *sub_system,
211     const struct GNUNET_PeerIdentity *peer,
212     const char *key,
213     const void *value,
214     size_t size,
215     struct GNUNET_TIME_Absolute expiry)
216 {
217   struct Plugin *plugin = cls;
218   sqlite3_stmt *stmt = plugin->insert_peerstoredata;
219
220   //FIXME: check if value exists with the same key first
221
222   if(SQLITE_OK != sqlite3_bind_text(stmt, 1, sub_system, strlen(sub_system) + 1, SQLITE_STATIC)
223       || SQLITE_OK != sqlite3_bind_blob(stmt, 2, peer, sizeof(struct GNUNET_PeerIdentity), SQLITE_STATIC)
224       || SQLITE_OK != sqlite3_bind_text(stmt, 3, key, strlen(key) + 1, SQLITE_STATIC)
225       || SQLITE_OK != sqlite3_bind_blob(stmt, 4, value, size, SQLITE_STATIC)
226       || SQLITE_OK != sqlite3_bind_int64(stmt, 5, (sqlite3_int64)expiry.abs_value_us))
227     LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
228                     "sqlite3_bind");
229   else if (SQLITE_DONE != sqlite3_step (stmt))
230   {
231     LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
232                 "sqlite3_step");
233   }
234   if (SQLITE_OK != sqlite3_reset (stmt))
235   {
236     LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
237                 "sqlite3_reset");
238     return GNUNET_SYSERR;
239   }
240
241   return GNUNET_OK;
242 }
243
244
245 /**
246  * @brief Prepare a SQL statement
247  *
248  * @param dbh handle to the database
249  * @param sql SQL statement, UTF-8 encoded
250  * @return 0 on success
251  */
252 static int
253 sql_exec (sqlite3 *dbh, const char *sql)
254 {
255   int result;
256
257   result = sqlite3_exec (dbh, sql, NULL, NULL, NULL);
258   LOG (GNUNET_ERROR_TYPE_DEBUG,
259        "Executed `%s' / %d\n", sql, result);
260   if (result != SQLITE_OK)
261     LOG (GNUNET_ERROR_TYPE_ERROR,
262    _("Error executing SQL query: %s\n  %s\n"),
263    sqlite3_errmsg (dbh), sql);
264   return result;
265 }
266
267 /**
268  * @brief Prepare a SQL statement
269  *
270  * @param dbh handle to the database
271  * @param sql SQL statement, UTF-8 encoded
272  * @param stmt set to the prepared statement
273  * @return 0 on success
274  */
275 static int
276 sql_prepare (sqlite3 *dbh, const char *sql, sqlite3_stmt **stmt)
277 {
278   char *tail;
279   int result;
280
281   result = sqlite3_prepare_v2 (dbh, sql, strlen (sql), stmt,
282                                (const char **) &tail);
283   LOG (GNUNET_ERROR_TYPE_DEBUG,
284        "Prepared `%s' / %p: %d\n", sql, *stmt, result);
285   if (result != SQLITE_OK)
286     LOG (GNUNET_ERROR_TYPE_ERROR,
287    _("Error preparing SQL query: %s\n  %s\n"),
288    sqlite3_errmsg (dbh), sql);
289   return result;
290 }
291
292 /**
293  * Initialize the database connections and associated
294  * data structures (create tables and indices
295  * as needed as well).
296  *
297  * @param plugin the plugin context (state for this module)
298  * @return GNUNET_OK on success
299  */
300 static int
301 database_setup (struct Plugin *plugin)
302 {
303   char *filename;
304
305   if (GNUNET_OK !=
306       GNUNET_CONFIGURATION_get_value_filename (plugin->cfg, "peerstore-sqlite",
307                                                "FILENAME", &filename))
308   {
309     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
310              "peerstore-sqlite", "FILENAME");
311     return GNUNET_SYSERR;
312   }
313   if (GNUNET_OK != GNUNET_DISK_file_test (filename))
314   {
315     if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (filename))
316     {
317       GNUNET_break (0);
318       GNUNET_free (filename);
319       return GNUNET_SYSERR;
320     }
321   }
322   /* filename should be UTF-8-encoded. If it isn't, it's a bug */
323   plugin->fn = filename;
324
325   /* Open database and precompile statements */
326   if (SQLITE_OK != sqlite3_open (plugin->fn, &plugin->dbh))
327   {
328     LOG (GNUNET_ERROR_TYPE_ERROR,
329    _("Unable to initialize SQLite: %s.\n"),
330    sqlite3_errmsg (plugin->dbh));
331     return GNUNET_SYSERR;
332   }
333
334   sql_exec (plugin->dbh, "PRAGMA temp_store=MEMORY");
335   sql_exec (plugin->dbh, "PRAGMA synchronous=NORMAL");
336   sql_exec (plugin->dbh, "PRAGMA legacy_file_format=OFF");
337   sql_exec (plugin->dbh, "PRAGMA auto_vacuum=INCREMENTAL");
338   sql_exec (plugin->dbh, "PRAGMA encoding=\"UTF-8\"");
339   sql_exec (plugin->dbh, "PRAGMA count_changes=OFF");
340   sql_exec (plugin->dbh, "PRAGMA page_size=4096");
341
342   sqlite3_busy_timeout (plugin->dbh, BUSY_TIMEOUT_MS);
343
344   /* Create tables */
345
346   sql_exec (plugin->dbh,
347       "CREATE TABLE IF NOT EXISTS peerstoredata (\n"
348       "  sub_system TEXT NOT NULL,\n"
349       "  peer_id BLOB NOT NULL,\n"
350       "  key TEXT NOT NULL,\n"
351       "  value BLOB NULL,\n"
352       "  expiry INTEGER NOT NULL"
353       ");");
354
355   /* Prepare statements */
356
357   sql_prepare (plugin->dbh,
358       "INSERT INTO peerstoredata (sub_system, peer_id, key, value, expiry) VALUES (?,?,?,?,?);",
359       &plugin->insert_peerstoredata);
360   sql_prepare(plugin->dbh,
361       "SELECT * FROM peerstoredata"
362       " WHERE sub_system = ?",
363       &plugin->select_peerstoredata);
364   sql_prepare(plugin->dbh,
365       "SELECT * FROM peerstoredata"
366       " WHERE sub_system = ?"
367       " AND peer_id = ?",
368       &plugin->select_peerstoredata_by_pid);
369   sql_prepare(plugin->dbh,
370       "SELECT * FROM peerstoredata"
371       " WHERE sub_system = ?"
372       " AND key = ?",
373       &plugin->select_peerstoredata_by_key);
374   sql_prepare(plugin->dbh,
375       "SELECT * FROM peerstoredata"
376       " WHERE sub_system = ?"
377       " AND peer_id = ?"
378       " AND key = ?",
379       &plugin->select_peerstoredata_by_all);
380
381   return GNUNET_OK;
382 }
383
384 /**
385  * Shutdown database connection and associate data
386  * structures.
387  * @param plugin the plugin context (state for this module)
388  */
389 static void
390 database_shutdown (struct Plugin *plugin)
391 {
392   int result;
393   sqlite3_stmt *stmt;
394   while (NULL != (stmt = sqlite3_next_stmt (plugin->dbh, NULL)))
395   {
396     result = sqlite3_finalize (stmt);
397     if (SQLITE_OK != result)
398       LOG (GNUNET_ERROR_TYPE_WARNING,
399            "Failed to close statement %p: %d\n", stmt, result);
400   }
401   if (SQLITE_OK != sqlite3_close (plugin->dbh))
402     LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR, "sqlite3_close");
403
404   GNUNET_free_non_null (plugin->fn);
405 }
406
407 /**
408  * Entry point for the plugin.
409  *
410  * @param cls The struct GNUNET_CONFIGURATION_Handle.
411  * @return NULL on error, otherwise the plugin context
412  */
413 void *
414 libgnunet_plugin_peerstore_sqlite_init (void *cls)
415 {
416   static struct Plugin plugin;
417   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
418   struct GNUNET_PEERSTORE_PluginFunctions *api;
419
420   if (NULL != plugin.cfg)
421     return NULL;                /* can only initialize once! */
422   memset (&plugin, 0, sizeof (struct Plugin));
423   plugin.cfg = cfg;
424   if (GNUNET_OK != database_setup (&plugin))
425   {
426     database_shutdown (&plugin);
427     return NULL;
428   }
429   api = GNUNET_new (struct GNUNET_PEERSTORE_PluginFunctions);
430   api->cls = &plugin;
431   api->store_record = &peerstore_sqlite_store_record;
432   api->iterate_records = &peerstore_sqlite_iterate_records;
433   LOG(GNUNET_ERROR_TYPE_DEBUG, "Sqlite plugin is running\n");
434   return api;
435 }
436
437 /**
438  * Exit point from the plugin.
439  *
440  * @param cls The plugin context (as returned by "init")
441  * @return Always NULL
442  */
443 void *
444 libgnunet_plugin_peerstore_sqlite_done (void *cls)
445 {
446   struct GNUNET_PEERSTORE_PluginFunctions *api = cls;
447   struct Plugin *plugin = api->cls;
448
449   database_shutdown (plugin);
450   plugin->cfg = NULL;
451   GNUNET_free (api);
452   LOG (GNUNET_ERROR_TYPE_DEBUG, "Sqlite plugin is finished\n");
453   return NULL;
454
455 }
456
457 /* end of plugin_peerstore_sqlite.c */