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