fix build issues
[oweals/gnunet.git] / src / peerstore / plugin_peerstore_flat.c
1 /*
2  * This file is part of GNUnet
3  * Copyright (C) 2015 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., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20
21 /**
22  * @file peerstore/plugin_peerstore_flat.c
23  * @brief flat file-based peerstore backend
24  * @author Martin Schanzenbach
25  */
26
27 #include "platform.h"
28 #include "gnunet_peerstore_plugin.h"
29 #include "gnunet_peerstore_service.h"
30 #include "peerstore.h"
31
32 /**
33  * Context for all functions in this plugin.
34  */
35 struct Plugin
36 {
37
38   /**
39    * Configuration handle
40    */
41   const struct GNUNET_CONFIGURATION_Handle *cfg;
42
43   /**
44    * HashMap
45    */
46   struct GNUNET_CONTAINER_MultiHashMap *hm;
47
48   /**
49    * Iterator
50    */
51   GNUNET_PEERSTORE_Processor iter;
52
53   /**
54    * Iterator cls
55    */
56   void *iter_cls;
57
58   /**
59    * iterator key
60    */
61   const char *iter_key;
62
63   /**
64    * Iterator peer
65    */
66   const struct GNUNET_PeerIdentity *iter_peer;
67
68   /**
69    * Iterator subsystem
70    */
71   const char *iter_sub_system;
72
73   /**
74    * Iterator time
75    */
76   struct GNUNET_TIME_Absolute iter_now;
77
78   /**
79    * Deleted entries
80    */
81   uint64_t deleted_entries;
82
83   /**
84    * Expired entries
85    */
86   uint64_t exp_changes;
87
88   /**
89    * Database filename.
90    */
91   char *fn;
92
93   /**
94    * Result found bool
95    */
96   int iter_result_found;
97
98 };
99
100
101 static int
102 delete_entries (void *cls,
103                 const struct GNUNET_HashCode *key,
104                 void *value)
105 {
106   struct Plugin *plugin = cls;
107   struct GNUNET_PEERSTORE_Record *entry = value;
108   if (0 != strcmp (plugin->iter_key, entry->key))
109     return GNUNET_YES;
110   if (0 != memcmp (plugin->iter_peer,
111                    &entry->peer,
112                    sizeof (struct GNUNET_PeerIdentity)))
113     return GNUNET_YES;
114   if (0 != strcmp (plugin->iter_sub_system, entry->sub_system))
115     return GNUNET_YES;
116
117   GNUNET_CONTAINER_multihashmap_remove (plugin->hm, key, value);
118   plugin->deleted_entries++;
119   return GNUNET_YES;
120 }
121
122
123 /**
124  * Delete records with the given key
125  *
126  * @param cls closure (internal context for the plugin)
127  * @param sub_system name of sub system
128  * @param peer Peer identity (can be NULL)
129  * @param key entry key string (can be NULL)
130  * @return number of deleted records
131  */
132 static int
133 peerstore_flat_delete_records (void *cls, const char *sub_system,
134                                const struct GNUNET_PeerIdentity *peer,
135                                const char *key)
136 {
137   struct Plugin *plugin = cls;
138
139   plugin->iter_sub_system = sub_system;
140   plugin->iter_peer = peer;
141   plugin->iter_key = key;
142   plugin->deleted_entries = 0;
143
144   GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
145                                          &delete_entries,
146                                          plugin);
147   return plugin->deleted_entries;
148 }
149
150 static int
151 expire_entries (void *cls,
152                 const struct GNUNET_HashCode *key,
153                 void *value)
154 {
155   struct Plugin *plugin = cls;
156   struct GNUNET_PEERSTORE_Record *entry = value;
157
158   if (entry->expiry.abs_value_us < plugin->iter_now.abs_value_us)
159   {
160     GNUNET_CONTAINER_multihashmap_remove (plugin->hm, key, value);
161     plugin->exp_changes++;
162   }
163   return GNUNET_YES;
164 }
165
166
167
168 /**
169  * Delete expired records (expiry < now)
170  *
171  * @param cls closure (internal context for the plugin)
172  * @param now time to use as reference
173  * @param cont continuation called with the number of records expired
174  * @param cont_cls continuation closure
175  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error and cont is not
176  * called
177  */
178 static int
179 peerstore_flat_expire_records (void *cls, struct GNUNET_TIME_Absolute now,
180                                GNUNET_PEERSTORE_Continuation cont,
181                                void *cont_cls)
182 {
183   struct Plugin *plugin = cls;
184   plugin->exp_changes = 0;
185   plugin->iter_now = now;
186
187   GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
188                                          &expire_entries,
189                                          plugin);
190   if (NULL != cont)
191   {
192     cont (cont_cls, plugin->exp_changes);
193   }
194   return GNUNET_OK;
195
196 }
197
198
199 static int
200 iterate_entries (void *cls,
201                  const struct GNUNET_HashCode *key,
202                  void *value)
203 {
204   struct Plugin *plugin = cls;
205   struct GNUNET_PEERSTORE_Record *entry = value;
206
207   if ((NULL != plugin->iter_peer) &&
208       (0 != memcmp (plugin->iter_peer,
209                     &entry->peer,
210                     sizeof (struct GNUNET_PeerIdentity))))
211   {
212     return GNUNET_YES;
213   }
214   if ((NULL != plugin->iter_key) &&
215       (0 != strcmp (plugin->iter_key,
216                     entry->key)))
217   {
218     return GNUNET_YES;
219   }
220   if (NULL != plugin->iter)
221     plugin->iter (plugin->iter_cls, entry, NULL);
222   plugin->iter_result_found = GNUNET_YES;
223   return GNUNET_YES;
224 }
225
226 /**
227  * Iterate over the records given an optional peer id
228  * and/or key.
229  *
230  * @param cls closure (internal context for the plugin)
231  * @param sub_system name of sub system
232  * @param peer Peer identity (can be NULL)
233  * @param key entry key string (can be NULL)
234  * @param iter function to call asynchronously with the results, terminated
235  * by a NULL result
236  * @param iter_cls closure for @a iter
237  * @return #GNUNET_OK on success, #GNUNET_SYSERR on error and iter is not
238  * called
239  */
240 static int
241 peerstore_flat_iterate_records (void *cls, const char *sub_system,
242                                 const struct GNUNET_PeerIdentity *peer,
243                                 const char *key,
244                                 GNUNET_PEERSTORE_Processor iter,
245                                 void *iter_cls)
246 {
247   struct Plugin *plugin = cls;
248   plugin->iter = iter;
249   plugin->iter_cls = iter_cls;
250   plugin->iter_peer = peer;
251   plugin->iter_sub_system = sub_system;
252   plugin->iter_key = key;
253
254   GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
255                                          &iterate_entries,
256                                          plugin);
257   if (NULL != iter)
258     iter (iter_cls, NULL, NULL);
259   return GNUNET_OK;
260 }
261
262
263 /**
264  * Store a record in the peerstore.
265  * Key is the combination of sub system and peer identity.
266  * One key can store multiple values.
267  *
268  * @param cls closure (internal context for the plugin)
269  * @param sub_system name of the GNUnet sub system responsible
270  * @param peer peer identity
271  * @param key record key string
272  * @param value value to be stored
273  * @param size size of value to be stored
274  * @param expiry absolute time after which the record is (possibly) deleted
275  * @param options options related to the store operation
276  * @param cont continuation called when record is stored
277  * @param cont_cls continuation closure
278  * @return #GNUNET_OK on success, else #GNUNET_SYSERR and cont is not called
279  */
280 static int
281 peerstore_flat_store_record (void *cls, const char *sub_system,
282                              const struct GNUNET_PeerIdentity *peer,
283                              const char *key, const void *value, size_t size,
284                              struct GNUNET_TIME_Absolute expiry,
285                              enum GNUNET_PEERSTORE_StoreOption options,
286                              GNUNET_PEERSTORE_Continuation cont,
287                              void *cont_cls)
288 {
289   struct Plugin *plugin = cls;
290   struct GNUNET_HashCode hkey;
291   struct GNUNET_PEERSTORE_Record *entry;
292   const char *peer_id;
293
294
295   entry = GNUNET_new (struct GNUNET_PEERSTORE_Record);
296   entry->sub_system = GNUNET_strdup (sub_system);
297   entry->key = GNUNET_strdup (key);
298   entry->value = GNUNET_malloc (size);
299   GNUNET_memcpy (entry->value, value, size);
300   entry->value_size = size;
301   entry->peer = *peer;
302   entry->expiry = expiry;
303
304   peer_id = GNUNET_i2s (peer);
305   GNUNET_CRYPTO_hash (peer_id,
306                       strlen (peer_id),
307                       &hkey);
308
309   if (GNUNET_PEERSTORE_STOREOPTION_REPLACE == options)
310   {
311     peerstore_flat_delete_records (cls, sub_system, peer, key);
312   }
313
314   GNUNET_CONTAINER_multihashmap_put (plugin->hm,
315                                      &hkey,
316                                      entry,
317                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
318   if (NULL != cont)
319   {
320     cont (cont_cls, GNUNET_OK);
321   }
322   return GNUNET_OK;
323 }
324
325
326 /**
327  * Initialize the database connections and associated
328  * data structures (create tables and indices
329  * as needed as well).
330  *
331  * @param plugin the plugin context (state for this module)
332  * @return GNUNET_OK on success
333  */
334 static int
335 database_setup (struct Plugin *plugin)
336 {
337   char *afsdir;
338   char *key;
339   char *sub_system;
340   const char *peer_id;
341   char *peer;
342   char *value;
343   char *expiry;
344   struct GNUNET_DISK_FileHandle *fh;
345   struct GNUNET_PEERSTORE_Record *entry;
346   struct GNUNET_HashCode hkey;
347   size_t size;
348   char *buffer;
349   char *line;
350
351   if (GNUNET_OK !=
352       GNUNET_CONFIGURATION_get_value_filename (plugin->cfg, "peerstore-flat",
353                                                "FILENAME", &afsdir))
354   {
355     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "peerstore-flat",
356                                "FILENAME");
357     return GNUNET_SYSERR;
358   }
359   if (GNUNET_OK != GNUNET_DISK_file_test (afsdir))
360   {
361     if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (afsdir))
362     {
363       GNUNET_break (0);
364       GNUNET_free (afsdir);
365       return GNUNET_SYSERR;
366     }
367   }
368   /* afsdir should be UTF-8-encoded. If it isn't, it's a bug */
369   plugin->fn = afsdir;
370
371   fh = GNUNET_DISK_file_open (afsdir,
372                               GNUNET_DISK_OPEN_CREATE |
373                               GNUNET_DISK_OPEN_READWRITE,
374                               GNUNET_DISK_PERM_USER_WRITE |
375                               GNUNET_DISK_PERM_USER_READ);
376   if (NULL == fh)
377   {
378     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
379                 _("Unable to initialize file: %s.\n"),
380                 afsdir);
381     return GNUNET_SYSERR;
382   }
383
384   /* Load data from file into hashmap */
385   plugin->hm = GNUNET_CONTAINER_multihashmap_create (10,
386                                                      GNUNET_NO);
387
388   if (GNUNET_SYSERR == GNUNET_DISK_file_size (afsdir,
389                                               &size,
390                                               GNUNET_YES,
391                                               GNUNET_YES))
392   {
393     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
394                 _("Unable to get filesize: %s.\n"),
395                 afsdir);
396     return GNUNET_SYSERR;
397   }
398
399   buffer = GNUNET_malloc (size + 1);
400
401   if (GNUNET_SYSERR == GNUNET_DISK_file_read (fh,
402                                               buffer,
403                                               size))
404   {
405     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
406                 _("Unable to read file: %s.\n"),
407                 afsdir);
408     GNUNET_DISK_file_close (fh);
409     GNUNET_free (buffer);
410     return GNUNET_SYSERR;
411   }
412
413   buffer[size] = '\0';
414   GNUNET_DISK_file_close (fh);
415   if (0 < size) {
416     line = strtok (buffer, "\n");
417     while (line != NULL) {
418       sub_system = strtok (line, ",");
419       if (NULL == sub_system)
420         break;
421       peer = strtok (NULL, ",");
422       if (NULL == peer)
423         break;
424       key = strtok (NULL, ",");
425       if (NULL == key)
426         break;
427       value = strtok (NULL, ",");
428       if (NULL == value)
429         break;
430       expiry = strtok (NULL, ",");
431       if (NULL == expiry)
432         break;
433       entry = GNUNET_new (struct GNUNET_PEERSTORE_Record);
434       entry->sub_system = GNUNET_strdup (sub_system);
435       entry->key = GNUNET_strdup (key);
436       {
437         size_t s;
438         char *o;
439
440         o = NULL;
441         s = GNUNET_STRINGS_base64_decode (peer,
442                                           strlen (peer),
443                                           &o);
444         if (sizeof (struct GNUNET_PeerIdentity) == s)
445           GNUNET_memcpy (&entry->peer,
446                          o,
447                          s);
448         else
449           GNUNET_break (0);
450         GNUNET_free_non_null (o);
451       }
452       entry->value_size = GNUNET_STRINGS_base64_decode (value,
453                                                         strlen (value),
454                                                         (char**)&entry->value);
455       if (GNUNET_SYSERR ==
456           GNUNET_STRINGS_fancy_time_to_absolute (expiry,
457                                                  &entry->expiry))
458       {
459         GNUNET_free (entry->sub_system);
460         GNUNET_free (entry->key);
461         GNUNET_free (entry);
462         break;
463       }
464       peer_id = GNUNET_i2s (&entry->peer);
465       GNUNET_CRYPTO_hash (peer_id,
466                           strlen (peer_id),
467                           &hkey);
468
469       GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put (plugin->hm,
470                                          &hkey,
471                                          entry,
472                                          GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE));
473
474     }
475   }
476   GNUNET_free (buffer);
477   return GNUNET_OK;
478 }
479
480 static int
481 store_and_free_entries (void *cls,
482                         const struct GNUNET_HashCode *key,
483                         void *value)
484 {
485   struct GNUNET_DISK_FileHandle *fh = cls;
486   struct GNUNET_PEERSTORE_Record *entry = value;
487   char *line;
488   char *peer;
489   const char *expiry;
490   char *val;
491
492   GNUNET_STRINGS_base64_encode (entry->value,
493                                 entry->value_size,
494                                 &val);
495   expiry = GNUNET_STRINGS_absolute_time_to_string (entry->expiry);
496   GNUNET_STRINGS_base64_encode ((char*)&entry->peer,
497                                 sizeof (struct GNUNET_PeerIdentity),
498                                 &peer);
499   GNUNET_asprintf (&line,
500                    "%s,%s,%s,%s,%s",
501                    entry->sub_system,
502                    peer,
503                    entry->key,
504                    val,
505                    expiry);
506   GNUNET_free (val);
507   GNUNET_free (peer);
508   GNUNET_DISK_file_write (fh,
509                           line,
510                           strlen (line));
511   GNUNET_free (entry->sub_system);
512   GNUNET_free (entry->key);
513   GNUNET_free (entry->value);
514   GNUNET_free (entry);
515   GNUNET_free (line);
516   return GNUNET_YES;
517
518 }
519
520 /**
521  * Shutdown database connection and associate data
522  * structures.
523  * @param plugin the plugin context (state for this module)
524  */
525 static void
526 database_shutdown (struct Plugin *plugin)
527 {
528   struct GNUNET_DISK_FileHandle *fh;
529   fh = GNUNET_DISK_file_open (plugin->fn,
530                               GNUNET_DISK_OPEN_CREATE |
531                               GNUNET_DISK_OPEN_TRUNCATE |
532                               GNUNET_DISK_OPEN_READWRITE,
533                               GNUNET_DISK_PERM_USER_WRITE |
534                               GNUNET_DISK_PERM_USER_READ);
535   if (NULL == fh)
536   {
537     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
538                 _("Unable to initialize file: %s.\n"),
539                 plugin->fn);
540     return;
541   }
542   GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
543                                          &store_and_free_entries,
544                                          fh);
545   GNUNET_CONTAINER_multihashmap_destroy (plugin->hm);
546   GNUNET_DISK_file_close (fh);
547 }
548
549
550 /**
551  * Entry point for the plugin.
552  *
553  * @param cls The struct GNUNET_CONFIGURATION_Handle.
554  * @return NULL on error, otherwise the plugin context
555  */
556 void *
557 libgnunet_plugin_peerstore_flat_init (void *cls)
558 {
559   static struct Plugin plugin;
560   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
561   struct GNUNET_PEERSTORE_PluginFunctions *api;
562
563   if (NULL != plugin.cfg)
564     return NULL;                /* can only initialize once! */
565   memset (&plugin, 0, sizeof (struct Plugin));
566   plugin.cfg = cfg;
567   if (GNUNET_OK != database_setup (&plugin))
568   {
569     database_shutdown (&plugin);
570     return NULL;
571   }
572   api = GNUNET_new (struct GNUNET_PEERSTORE_PluginFunctions);
573   api->cls = &plugin;
574   api->store_record = &peerstore_flat_store_record;
575   api->iterate_records = &peerstore_flat_iterate_records;
576   api->expire_records = &peerstore_flat_expire_records;
577   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Flat plugin is running\n");
578   return api;
579 }
580
581
582 /**
583  * Exit point from the plugin.
584  *
585  * @param cls The plugin context (as returned by "init")
586  * @return Always NULL
587  */
588 void *
589 libgnunet_plugin_peerstore_flat_done (void *cls)
590 {
591   struct GNUNET_PEERSTORE_PluginFunctions *api = cls;
592   struct Plugin *plugin = api->cls;
593
594   database_shutdown (plugin);
595   plugin->cfg = NULL;
596   GNUNET_free (api);
597   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Flat plugin is finished\n");
598   return NULL;
599 }
600
601 /* end of plugin_peerstore_sqlite.c */