uncrustify as demanded.
[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    * 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 static int
99 delete_entries(void *cls,
100                const struct GNUNET_HashCode *key,
101                void *value)
102 {
103   struct Plugin *plugin = cls;
104   struct GNUNET_PEERSTORE_Record *entry = value;
105
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
183   plugin->exp_changes = 0;
184   plugin->iter_now = now;
185
186   GNUNET_CONTAINER_multihashmap_iterate(plugin->hm,
187                                         &expire_entries,
188                                         plugin);
189   if (NULL != cont)
190     {
191       cont(cont_cls, plugin->exp_changes);
192     }
193   return GNUNET_OK;
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
247   plugin->iter = iter;
248   plugin->iter_cls = iter_cls;
249   plugin->iter_peer = peer;
250   plugin->iter_sub_system = sub_system;
251   plugin->iter_key = key;
252
253   GNUNET_CONTAINER_multihashmap_iterate(plugin->hm,
254                                         &iterate_entries,
255                                         plugin);
256   if (NULL != iter)
257     iter(iter_cls, NULL, NULL);
258   return GNUNET_OK;
259 }
260
261
262 /**
263  * Store a record in the peerstore.
264  * Key is the combination of sub system and peer identity.
265  * One key can store multiple values.
266  *
267  * @param cls closure (internal context for the plugin)
268  * @param sub_system name of the GNUnet sub system responsible
269  * @param peer peer identity
270  * @param key record key string
271  * @param value value to be stored
272  * @param size size of value to be stored
273  * @param expiry absolute time after which the record is (possibly) deleted
274  * @param options options related to the store operation
275  * @param cont continuation called when record is stored
276  * @param cont_cls continuation closure
277  * @return #GNUNET_OK on success, else #GNUNET_SYSERR and cont is not called
278  */
279 static int
280 peerstore_flat_store_record(void *cls, const char *sub_system,
281                             const struct GNUNET_PeerIdentity *peer,
282                             const char *key, const void *value, size_t size,
283                             struct GNUNET_TIME_Absolute expiry,
284                             enum GNUNET_PEERSTORE_StoreOption options,
285                             GNUNET_PEERSTORE_Continuation cont,
286                             void *cont_cls)
287 {
288   struct Plugin *plugin = cls;
289   struct GNUNET_HashCode hkey;
290   struct GNUNET_PEERSTORE_Record *entry;
291   const char *peer_id;
292
293
294   entry = GNUNET_new(struct GNUNET_PEERSTORE_Record);
295   entry->sub_system = GNUNET_strdup(sub_system);
296   entry->key = GNUNET_strdup(key);
297   entry->value = GNUNET_malloc(size);
298   GNUNET_memcpy(entry->value, value, size);
299   entry->value_size = size;
300   entry->peer = *peer;
301   entry->expiry = expiry;
302
303   peer_id = GNUNET_i2s(peer);
304   GNUNET_CRYPTO_hash(peer_id,
305                      strlen(peer_id),
306                      &hkey);
307
308   if (GNUNET_PEERSTORE_STOREOPTION_REPLACE == options)
309     {
310       peerstore_flat_delete_records(cls, sub_system, peer, key);
311     }
312
313   GNUNET_CONTAINER_multihashmap_put(plugin->hm,
314                                     &hkey,
315                                     entry,
316                                     GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
317   if (NULL != cont)
318     {
319       cont(cont_cls, GNUNET_OK);
320     }
321   return GNUNET_OK;
322 }
323
324
325 /**
326  * Initialize the database connections and associated
327  * data structures (create tables and indices
328  * as needed as well).
329  *
330  * @param plugin the plugin context (state for this module)
331  * @return GNUNET_OK on success
332  */
333 static int
334 database_setup(struct Plugin *plugin)
335 {
336   char *afsdir;
337   char *key;
338   char *sub_system;
339   const char *peer_id;
340   char *peer;
341   char *value;
342   char *expiry;
343   struct GNUNET_DISK_FileHandle *fh;
344   struct GNUNET_PEERSTORE_Record *entry;
345   struct GNUNET_HashCode hkey;
346   size_t size;
347   char *buffer;
348   char *line;
349
350   if (GNUNET_OK !=
351       GNUNET_CONFIGURATION_get_value_filename(plugin->cfg, "peerstore-flat",
352                                               "FILENAME", &afsdir))
353     {
354       GNUNET_log_config_missing(GNUNET_ERROR_TYPE_ERROR, "peerstore-flat",
355                                 "FILENAME");
356       return GNUNET_SYSERR;
357     }
358   if (GNUNET_OK != GNUNET_DISK_file_test(afsdir))
359     {
360       if (GNUNET_OK != GNUNET_DISK_directory_create_for_file(afsdir))
361         {
362           GNUNET_break(0);
363           GNUNET_free(afsdir);
364           return GNUNET_SYSERR;
365         }
366     }
367   /* afsdir should be UTF-8-encoded. If it isn't, it's a bug */
368   plugin->fn = afsdir;
369
370   fh = GNUNET_DISK_file_open(afsdir,
371                              GNUNET_DISK_OPEN_CREATE |
372                              GNUNET_DISK_OPEN_READWRITE,
373                              GNUNET_DISK_PERM_USER_WRITE |
374                              GNUNET_DISK_PERM_USER_READ);
375   if (NULL == fh)
376     {
377       GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
378                  _("Unable to initialize file: %s.\n"),
379                  afsdir);
380       return GNUNET_SYSERR;
381     }
382
383   /* Load data from file into hashmap */
384   plugin->hm = GNUNET_CONTAINER_multihashmap_create(10,
385                                                     GNUNET_NO);
386
387   if (GNUNET_SYSERR == GNUNET_DISK_file_size(afsdir,
388                                              &size,
389                                              GNUNET_YES,
390                                              GNUNET_YES))
391     {
392       GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
393                  _("Unable to get filesize: %s.\n"),
394                  afsdir);
395       return GNUNET_SYSERR;
396     }
397
398   buffer = GNUNET_malloc(size + 1);
399
400   if (GNUNET_SYSERR == GNUNET_DISK_file_read(fh,
401                                              buffer,
402                                              size))
403     {
404       GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
405                  _("Unable to read file: %s.\n"),
406                  afsdir);
407       GNUNET_DISK_file_close(fh);
408       GNUNET_free(buffer);
409       return GNUNET_SYSERR;
410     }
411
412   buffer[size] = '\0';
413   GNUNET_DISK_file_close(fh);
414   if (0 < size)
415     {
416       line = strtok(buffer, "\n");
417       while (line != NULL)
418         {
419           sub_system = strtok(line, ",");
420           if (NULL == sub_system)
421             break;
422           peer = strtok(NULL, ",");
423           if (NULL == peer)
424             break;
425           key = strtok(NULL, ",");
426           if (NULL == key)
427             break;
428           value = strtok(NULL, ",");
429           if (NULL == value)
430             break;
431           expiry = strtok(NULL, ",");
432           if (NULL == expiry)
433             break;
434           entry = GNUNET_new(struct GNUNET_PEERSTORE_Record);
435           entry->sub_system = GNUNET_strdup(sub_system);
436           entry->key = GNUNET_strdup(key);
437           {
438             size_t s;
439             char *o;
440
441             o = NULL;
442             s = GNUNET_STRINGS_base64_decode(peer,
443                                              strlen(peer),
444                                              (void**)&o);
445             if (sizeof(struct GNUNET_PeerIdentity) == s)
446               GNUNET_memcpy(&entry->peer,
447                             o,
448                             s);
449             else
450               GNUNET_break(0);
451             GNUNET_free_non_null(o);
452           }
453           entry->value_size = GNUNET_STRINGS_base64_decode(value,
454                                                            strlen(value),
455                                                            (void**)&entry->value);
456           if (GNUNET_SYSERR ==
457               GNUNET_STRINGS_fancy_time_to_absolute(expiry,
458                                                     &entry->expiry))
459             {
460               GNUNET_free(entry->sub_system);
461               GNUNET_free(entry->key);
462               GNUNET_free(entry);
463               break;
464             }
465           peer_id = GNUNET_i2s(&entry->peer);
466           GNUNET_CRYPTO_hash(peer_id,
467                              strlen(peer_id),
468                              &hkey);
469
470           GNUNET_assert(GNUNET_OK == GNUNET_CONTAINER_multihashmap_put(plugin->hm,
471                                                                        &hkey,
472                                                                        entry,
473                                                                        GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE));
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  * Shutdown database connection and associate data
521  * structures.
522  * @param plugin the plugin context (state for this module)
523  */
524 static void
525 database_shutdown(struct Plugin *plugin)
526 {
527   struct GNUNET_DISK_FileHandle *fh;
528
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 */