2 * This file is part of GNUnet
3 * Copyright (C) 2015 Christian Grothoff (and other contributing authors)
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.
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.
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/>.
20 * @file peerstore/plugin_peerstore_flat.c
21 * @brief flat file-based peerstore backend
22 * @author Martin Schanzenbach
26 #include "gnunet_peerstore_plugin.h"
27 #include "gnunet_peerstore_service.h"
28 #include "peerstore.h"
31 * Context for all functions in this plugin.
37 * Configuration handle
39 const struct GNUNET_CONFIGURATION_Handle *cfg;
44 struct GNUNET_CONTAINER_MultiHashMap *hm;
49 GNUNET_PEERSTORE_Processor iter;
64 const struct GNUNET_PeerIdentity *iter_peer;
69 const char *iter_sub_system;
74 struct GNUNET_TIME_Absolute iter_now;
79 uint64_t deleted_entries;
94 int iter_result_found;
100 delete_entries (void *cls,
101 const struct GNUNET_HashCode *key,
104 struct Plugin *plugin = cls;
105 struct GNUNET_PEERSTORE_Record *entry = value;
106 if (0 != strcmp (plugin->iter_key, entry->key))
108 if (0 != memcmp (plugin->iter_peer,
110 sizeof (struct GNUNET_PeerIdentity)))
112 if (0 != strcmp (plugin->iter_sub_system, entry->sub_system))
115 GNUNET_CONTAINER_multihashmap_remove (plugin->hm, key, value);
116 plugin->deleted_entries++;
122 * Delete records with the given key
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
131 peerstore_flat_delete_records (void *cls, const char *sub_system,
132 const struct GNUNET_PeerIdentity *peer,
135 struct Plugin *plugin = cls;
137 plugin->iter_sub_system = sub_system;
138 plugin->iter_peer = peer;
139 plugin->iter_key = key;
140 plugin->deleted_entries = 0;
142 GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
145 return plugin->deleted_entries;
149 expire_entries (void *cls,
150 const struct GNUNET_HashCode *key,
153 struct Plugin *plugin = cls;
154 struct GNUNET_PEERSTORE_Record *entry = value;
156 if (entry->expiry.abs_value_us < plugin->iter_now.abs_value_us)
158 GNUNET_CONTAINER_multihashmap_remove (plugin->hm, key, value);
159 plugin->exp_changes++;
167 * Delete expired records (expiry < now)
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
177 peerstore_flat_expire_records (void *cls, struct GNUNET_TIME_Absolute now,
178 GNUNET_PEERSTORE_Continuation cont,
181 struct Plugin *plugin = cls;
182 plugin->exp_changes = 0;
183 plugin->iter_now = now;
185 GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
190 cont (cont_cls, plugin->exp_changes);
198 iterate_entries (void *cls,
199 const struct GNUNET_HashCode *key,
202 struct Plugin *plugin = cls;
203 struct GNUNET_PEERSTORE_Record *entry = value;
205 if ((NULL != plugin->iter_peer) &&
206 (0 != memcmp (plugin->iter_peer,
208 sizeof (struct GNUNET_PeerIdentity))))
212 if ((NULL != plugin->iter_key) &&
213 (0 != strcmp (plugin->iter_key,
218 if (NULL != plugin->iter)
219 plugin->iter (plugin->iter_cls, entry, NULL);
220 plugin->iter_result_found = GNUNET_YES;
225 * Iterate over the records given an optional peer id
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
234 * @param iter_cls closure for @a iter
235 * @return #GNUNET_OK on success, #GNUNET_SYSERR on error and iter is not
239 peerstore_flat_iterate_records (void *cls, const char *sub_system,
240 const struct GNUNET_PeerIdentity *peer,
242 GNUNET_PEERSTORE_Processor iter,
245 struct Plugin *plugin = cls;
247 plugin->iter_cls = iter_cls;
248 plugin->iter_peer = peer;
249 plugin->iter_sub_system = sub_system;
250 plugin->iter_key = key;
252 GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
256 iter (iter_cls, NULL, NULL);
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.
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
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,
287 struct Plugin *plugin = cls;
288 struct GNUNET_HashCode hkey;
289 struct GNUNET_PEERSTORE_Record *entry;
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;
300 entry->expiry = expiry;
302 peer_id = GNUNET_i2s (peer);
303 GNUNET_CRYPTO_hash (peer_id,
307 if (GNUNET_PEERSTORE_STOREOPTION_REPLACE == options)
309 peerstore_flat_delete_records (cls, sub_system, peer, key);
312 GNUNET_CONTAINER_multihashmap_put (plugin->hm,
315 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
318 cont (cont_cls, GNUNET_OK);
325 * Initialize the database connections and associated
326 * data structures (create tables and indices
327 * as needed as well).
329 * @param plugin the plugin context (state for this module)
330 * @return GNUNET_OK on success
333 database_setup (struct Plugin *plugin)
342 struct GNUNET_DISK_FileHandle *fh;
343 struct GNUNET_PEERSTORE_Record *entry;
344 struct GNUNET_HashCode hkey;
350 GNUNET_CONFIGURATION_get_value_filename (plugin->cfg, "peerstore-flat",
351 "FILENAME", &afsdir))
353 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "peerstore-flat",
355 return GNUNET_SYSERR;
357 if (GNUNET_OK != GNUNET_DISK_file_test (afsdir))
359 if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (afsdir))
362 GNUNET_free (afsdir);
363 return GNUNET_SYSERR;
366 /* afsdir should be UTF-8-encoded. If it isn't, it's a bug */
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);
376 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
377 _("Unable to initialize file: %s.\n"),
379 return GNUNET_SYSERR;
382 /* Load data from file into hashmap */
383 plugin->hm = GNUNET_CONTAINER_multihashmap_create (10,
386 if (GNUNET_SYSERR == GNUNET_DISK_file_size (afsdir,
391 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
392 _("Unable to get filesize: %s.\n"),
394 return GNUNET_SYSERR;
397 buffer = GNUNET_malloc (size + 1);
399 if (GNUNET_SYSERR == GNUNET_DISK_file_read (fh,
403 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
404 _("Unable to read file: %s.\n"),
406 GNUNET_DISK_file_close (fh);
407 GNUNET_free (buffer);
408 return GNUNET_SYSERR;
412 GNUNET_DISK_file_close (fh);
414 line = strtok (buffer, "\n");
415 while (line != NULL) {
416 sub_system = strtok (line, ",");
417 if (NULL == sub_system)
419 peer = strtok (NULL, ",");
422 key = strtok (NULL, ",");
425 value = strtok (NULL, ",");
428 expiry = strtok (NULL, ",");
431 entry = GNUNET_new (struct GNUNET_PEERSTORE_Record);
432 entry->sub_system = GNUNET_strdup (sub_system);
433 entry->key = GNUNET_strdup (key);
439 s = GNUNET_STRINGS_base64_decode (peer,
442 if (sizeof (struct GNUNET_PeerIdentity) == s)
443 GNUNET_memcpy (&entry->peer,
448 GNUNET_free_non_null (o);
450 entry->value_size = GNUNET_STRINGS_base64_decode (value,
452 (char**)&entry->value);
454 GNUNET_STRINGS_fancy_time_to_absolute (expiry,
457 GNUNET_free (entry->sub_system);
458 GNUNET_free (entry->key);
462 peer_id = GNUNET_i2s (&entry->peer);
463 GNUNET_CRYPTO_hash (peer_id,
467 GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put (plugin->hm,
470 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE));
474 GNUNET_free (buffer);
479 store_and_free_entries (void *cls,
480 const struct GNUNET_HashCode *key,
483 struct GNUNET_DISK_FileHandle *fh = cls;
484 struct GNUNET_PEERSTORE_Record *entry = value;
490 GNUNET_STRINGS_base64_encode (entry->value,
493 expiry = GNUNET_STRINGS_absolute_time_to_string (entry->expiry);
494 GNUNET_STRINGS_base64_encode ((char*)&entry->peer,
495 sizeof (struct GNUNET_PeerIdentity),
497 GNUNET_asprintf (&line,
506 GNUNET_DISK_file_write (fh,
509 GNUNET_free (entry->sub_system);
510 GNUNET_free (entry->key);
511 GNUNET_free (entry->value);
519 * Shutdown database connection and associate data
521 * @param plugin the plugin context (state for this module)
524 database_shutdown (struct Plugin *plugin)
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);
535 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
536 _("Unable to initialize file: %s.\n"),
540 GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
541 &store_and_free_entries,
543 GNUNET_CONTAINER_multihashmap_destroy (plugin->hm);
544 GNUNET_DISK_file_close (fh);
549 * Entry point for the plugin.
551 * @param cls The struct GNUNET_CONFIGURATION_Handle.
552 * @return NULL on error, otherwise the plugin context
555 libgnunet_plugin_peerstore_flat_init (void *cls)
557 static struct Plugin plugin;
558 const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
559 struct GNUNET_PEERSTORE_PluginFunctions *api;
561 if (NULL != plugin.cfg)
562 return NULL; /* can only initialize once! */
563 memset (&plugin, 0, sizeof (struct Plugin));
565 if (GNUNET_OK != database_setup (&plugin))
567 database_shutdown (&plugin);
570 api = GNUNET_new (struct GNUNET_PEERSTORE_PluginFunctions);
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");
581 * Exit point from the plugin.
583 * @param cls The plugin context (as returned by "init")
584 * @return Always NULL
587 libgnunet_plugin_peerstore_flat_done (void *cls)
589 struct GNUNET_PEERSTORE_PluginFunctions *api = cls;
590 struct Plugin *plugin = api->cls;
592 database_shutdown (plugin);
595 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Flat plugin is finished\n");
599 /* end of plugin_peerstore_sqlite.c */