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/>.
18 SPDX-License-Identifier: AGPL3.0-or-later
22 * @file peerstore/plugin_peerstore_flat.c
23 * @brief flat file-based peerstore backend
24 * @author Martin Schanzenbach
28 #include "gnunet_peerstore_plugin.h"
29 #include "gnunet_peerstore_service.h"
30 #include "peerstore.h"
33 * 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;
99 delete_entries(void *cls,
100 const struct GNUNET_HashCode *key,
103 struct Plugin *plugin = cls;
104 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;
183 plugin->exp_changes = 0;
184 plugin->iter_now = now;
186 GNUNET_CONTAINER_multihashmap_iterate(plugin->hm,
191 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;
248 plugin->iter_cls = iter_cls;
249 plugin->iter_peer = peer;
250 plugin->iter_sub_system = sub_system;
251 plugin->iter_key = key;
253 GNUNET_CONTAINER_multihashmap_iterate(plugin->hm,
257 iter(iter_cls, NULL, NULL);
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.
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
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,
288 struct Plugin *plugin = cls;
289 struct GNUNET_HashCode hkey;
290 struct GNUNET_PEERSTORE_Record *entry;
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;
301 entry->expiry = expiry;
303 peer_id = GNUNET_i2s(peer);
304 GNUNET_CRYPTO_hash(peer_id,
308 if (GNUNET_PEERSTORE_STOREOPTION_REPLACE == options)
310 peerstore_flat_delete_records(cls, sub_system, peer, key);
313 GNUNET_CONTAINER_multihashmap_put(plugin->hm,
316 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
319 cont(cont_cls, GNUNET_OK);
326 * Initialize the database connections and associated
327 * data structures (create tables and indices
328 * as needed as well).
330 * @param plugin the plugin context (state for this module)
331 * @return GNUNET_OK on success
334 database_setup(struct Plugin *plugin)
343 struct GNUNET_DISK_FileHandle *fh;
344 struct GNUNET_PEERSTORE_Record *entry;
345 struct GNUNET_HashCode hkey;
351 GNUNET_CONFIGURATION_get_value_filename(plugin->cfg, "peerstore-flat",
352 "FILENAME", &afsdir))
354 GNUNET_log_config_missing(GNUNET_ERROR_TYPE_ERROR, "peerstore-flat",
356 return GNUNET_SYSERR;
358 if (GNUNET_OK != GNUNET_DISK_file_test(afsdir))
360 if (GNUNET_OK != GNUNET_DISK_directory_create_for_file(afsdir))
364 return GNUNET_SYSERR;
367 /* afsdir should be UTF-8-encoded. If it isn't, it's a bug */
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);
377 GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
378 _("Unable to initialize file: %s.\n"),
380 return GNUNET_SYSERR;
383 /* Load data from file into hashmap */
384 plugin->hm = GNUNET_CONTAINER_multihashmap_create(10,
387 if (GNUNET_SYSERR == GNUNET_DISK_file_size(afsdir,
392 GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
393 _("Unable to get filesize: %s.\n"),
395 return GNUNET_SYSERR;
398 buffer = GNUNET_malloc(size + 1);
400 if (GNUNET_SYSERR == GNUNET_DISK_file_read(fh,
404 GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
405 _("Unable to read file: %s.\n"),
407 GNUNET_DISK_file_close(fh);
409 return GNUNET_SYSERR;
413 GNUNET_DISK_file_close(fh);
416 line = strtok(buffer, "\n");
419 sub_system = strtok(line, ",");
420 if (NULL == sub_system)
422 peer = strtok(NULL, ",");
425 key = strtok(NULL, ",");
428 value = strtok(NULL, ",");
431 expiry = strtok(NULL, ",");
434 entry = GNUNET_new(struct GNUNET_PEERSTORE_Record);
435 entry->sub_system = GNUNET_strdup(sub_system);
436 entry->key = GNUNET_strdup(key);
442 s = GNUNET_STRINGS_base64_decode(peer,
445 if (sizeof(struct GNUNET_PeerIdentity) == s)
446 GNUNET_memcpy(&entry->peer,
451 GNUNET_free_non_null(o);
453 entry->value_size = GNUNET_STRINGS_base64_decode(value,
455 (void**)&entry->value);
457 GNUNET_STRINGS_fancy_time_to_absolute(expiry,
460 GNUNET_free(entry->sub_system);
461 GNUNET_free(entry->key);
465 peer_id = GNUNET_i2s(&entry->peer);
466 GNUNET_CRYPTO_hash(peer_id,
470 GNUNET_assert(GNUNET_OK == GNUNET_CONTAINER_multihashmap_put(plugin->hm,
473 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE));
481 store_and_free_entries(void *cls,
482 const struct GNUNET_HashCode *key,
485 struct GNUNET_DISK_FileHandle *fh = cls;
486 struct GNUNET_PEERSTORE_Record *entry = value;
492 GNUNET_STRINGS_base64_encode(entry->value,
495 expiry = GNUNET_STRINGS_absolute_time_to_string(entry->expiry);
496 GNUNET_STRINGS_base64_encode((char*)&entry->peer,
497 sizeof(struct GNUNET_PeerIdentity),
499 GNUNET_asprintf(&line,
508 GNUNET_DISK_file_write(fh,
511 GNUNET_free(entry->sub_system);
512 GNUNET_free(entry->key);
513 GNUNET_free(entry->value);
520 * Shutdown database connection and associate data
522 * @param plugin the plugin context (state for this module)
525 database_shutdown(struct Plugin *plugin)
527 struct GNUNET_DISK_FileHandle *fh;
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);
537 GNUNET_log(GNUNET_ERROR_TYPE_ERROR,
538 _("Unable to initialize file: %s.\n"),
542 GNUNET_CONTAINER_multihashmap_iterate(plugin->hm,
543 &store_and_free_entries,
545 GNUNET_CONTAINER_multihashmap_destroy(plugin->hm);
546 GNUNET_DISK_file_close(fh);
551 * Entry point for the plugin.
553 * @param cls The struct GNUNET_CONFIGURATION_Handle.
554 * @return NULL on error, otherwise the plugin context
557 libgnunet_plugin_peerstore_flat_init(void *cls)
559 static struct Plugin plugin;
560 const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
561 struct GNUNET_PEERSTORE_PluginFunctions *api;
563 if (NULL != plugin.cfg)
564 return NULL; /* can only initialize once! */
565 memset(&plugin, 0, sizeof(struct Plugin));
567 if (GNUNET_OK != database_setup(&plugin))
569 database_shutdown(&plugin);
572 api = GNUNET_new(struct GNUNET_PEERSTORE_PluginFunctions);
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");
583 * Exit point from the plugin.
585 * @param cls The plugin context (as returned by "init")
586 * @return Always NULL
589 libgnunet_plugin_peerstore_flat_done(void *cls)
591 struct GNUNET_PEERSTORE_PluginFunctions *api = cls;
592 struct Plugin *plugin = api->cls;
594 database_shutdown(plugin);
597 GNUNET_log(GNUNET_ERROR_TYPE_DEBUG, "Flat plugin is finished\n");
601 /* end of plugin_peerstore_sqlite.c */