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