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