/* * This file is part of GNUnet * Copyright (C) 2015 Christian Grothoff (and other contributing authors) * * GNUnet is free software: you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * GNUnet is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . SPDX-License-Identifier: AGPL3.0-or-later */ /** * @file peerstore/plugin_peerstore_flat.c * @brief flat file-based peerstore backend * @author Martin Schanzenbach */ #include "platform.h" #include "gnunet_peerstore_plugin.h" #include "gnunet_peerstore_service.h" #include "peerstore.h" /** * Context for all functions in this plugin. */ struct Plugin { /** * Configuration handle */ const struct GNUNET_CONFIGURATION_Handle *cfg; /** * HashMap */ struct GNUNET_CONTAINER_MultiHashMap *hm; /** * Iterator */ GNUNET_PEERSTORE_Processor iter; /** * Iterator cls */ void *iter_cls; /** * iterator key */ const char *iter_key; /** * Iterator peer */ const struct GNUNET_PeerIdentity *iter_peer; /** * Iterator subsystem */ const char *iter_sub_system; /** * Iterator time */ struct GNUNET_TIME_Absolute iter_now; /** * Deleted entries */ uint64_t deleted_entries; /** * Expired entries */ uint64_t exp_changes; /** * Database filename. */ char *fn; /** * Result found bool */ int iter_result_found; }; static int delete_entries (void *cls, const struct GNUNET_HashCode *key, void *value) { struct Plugin *plugin = cls; struct GNUNET_PEERSTORE_Record *entry = value; if (0 != strcmp (plugin->iter_key, entry->key)) return GNUNET_YES; if (0 != memcmp (plugin->iter_peer, &entry->peer, sizeof(struct GNUNET_PeerIdentity))) return GNUNET_YES; if (0 != strcmp (plugin->iter_sub_system, entry->sub_system)) return GNUNET_YES; GNUNET_CONTAINER_multihashmap_remove (plugin->hm, key, value); plugin->deleted_entries++; return GNUNET_YES; } /** * Delete records with the given key * * @param cls closure (internal context for the plugin) * @param sub_system name of sub system * @param peer Peer identity (can be NULL) * @param key entry key string (can be NULL) * @return number of deleted records */ static int peerstore_flat_delete_records (void *cls, const char *sub_system, const struct GNUNET_PeerIdentity *peer, const char *key) { struct Plugin *plugin = cls; plugin->iter_sub_system = sub_system; plugin->iter_peer = peer; plugin->iter_key = key; plugin->deleted_entries = 0; GNUNET_CONTAINER_multihashmap_iterate (plugin->hm, &delete_entries, plugin); return plugin->deleted_entries; } static int expire_entries (void *cls, const struct GNUNET_HashCode *key, void *value) { struct Plugin *plugin = cls; struct GNUNET_PEERSTORE_Record *entry = value; if (entry->expiry.abs_value_us < plugin->iter_now.abs_value_us) { GNUNET_CONTAINER_multihashmap_remove (plugin->hm, key, value); plugin->exp_changes++; } return GNUNET_YES; } /** * Delete expired records (expiry < now) * * @param cls closure (internal context for the plugin) * @param now time to use as reference * @param cont continuation called with the number of records expired * @param cont_cls continuation closure * @return #GNUNET_OK on success, #GNUNET_SYSERR on error and cont is not * called */ static int peerstore_flat_expire_records (void *cls, struct GNUNET_TIME_Absolute now, GNUNET_PEERSTORE_Continuation cont, void *cont_cls) { struct Plugin *plugin = cls; plugin->exp_changes = 0; plugin->iter_now = now; GNUNET_CONTAINER_multihashmap_iterate (plugin->hm, &expire_entries, plugin); if (NULL != cont) { cont (cont_cls, plugin->exp_changes); } return GNUNET_OK; } static int iterate_entries (void *cls, const struct GNUNET_HashCode *key, void *value) { struct Plugin *plugin = cls; struct GNUNET_PEERSTORE_Record *entry = value; if ((NULL != plugin->iter_peer) && (0 != memcmp (plugin->iter_peer, &entry->peer, sizeof(struct GNUNET_PeerIdentity)))) { return GNUNET_YES; } if ((NULL != plugin->iter_key) && (0 != strcmp (plugin->iter_key, entry->key))) { return GNUNET_YES; } if (NULL != plugin->iter) plugin->iter (plugin->iter_cls, entry, NULL); plugin->iter_result_found = GNUNET_YES; return GNUNET_YES; } /** * Iterate over the records given an optional peer id * and/or key. * * @param cls closure (internal context for the plugin) * @param sub_system name of sub system * @param peer Peer identity (can be NULL) * @param key entry key string (can be NULL) * @param iter function to call asynchronously with the results, terminated * by a NULL result * @param iter_cls closure for @a iter * @return #GNUNET_OK on success, #GNUNET_SYSERR on error and iter is not * called */ static int peerstore_flat_iterate_records (void *cls, const char *sub_system, const struct GNUNET_PeerIdentity *peer, const char *key, GNUNET_PEERSTORE_Processor iter, void *iter_cls) { struct Plugin *plugin = cls; plugin->iter = iter; plugin->iter_cls = iter_cls; plugin->iter_peer = peer; plugin->iter_sub_system = sub_system; plugin->iter_key = key; GNUNET_CONTAINER_multihashmap_iterate (plugin->hm, &iterate_entries, plugin); if (NULL != iter) iter (iter_cls, NULL, NULL); return GNUNET_OK; } /** * Store a record in the peerstore. * Key is the combination of sub system and peer identity. * One key can store multiple values. * * @param cls closure (internal context for the plugin) * @param sub_system name of the GNUnet sub system responsible * @param peer peer identity * @param key record key string * @param value value to be stored * @param size size of value to be stored * @param expiry absolute time after which the record is (possibly) deleted * @param options options related to the store operation * @param cont continuation called when record is stored * @param cont_cls continuation closure * @return #GNUNET_OK on success, else #GNUNET_SYSERR and cont is not called */ static int peerstore_flat_store_record (void *cls, const char *sub_system, const struct GNUNET_PeerIdentity *peer, const char *key, const void *value, size_t size, struct GNUNET_TIME_Absolute expiry, enum GNUNET_PEERSTORE_StoreOption options, GNUNET_PEERSTORE_Continuation cont, void *cont_cls) { struct Plugin *plugin = cls; struct GNUNET_HashCode hkey; struct GNUNET_PEERSTORE_Record *entry; const char *peer_id; entry = GNUNET_new (struct GNUNET_PEERSTORE_Record); entry->sub_system = GNUNET_strdup (sub_system); entry->key = GNUNET_strdup (key); entry->value = GNUNET_malloc (size); GNUNET_memcpy (entry->value, value, size); entry->value_size = size; entry->peer = *peer; entry->expiry = expiry; peer_id = GNUNET_i2s (peer); GNUNET_CRYPTO_hash (peer_id, strlen (peer_id), &hkey); if (GNUNET_PEERSTORE_STOREOPTION_REPLACE == options) { peerstore_flat_delete_records (cls, sub_system, peer, key); } GNUNET_CONTAINER_multihashmap_put (plugin->hm, &hkey, entry, GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE); if (NULL != cont) { cont (cont_cls, GNUNET_OK); } return GNUNET_OK; } /** * Initialize the database connections and associated * data structures (create tables and indices * as needed as well). * * @param plugin the plugin context (state for this module) * @return GNUNET_OK on success */ static int database_setup (struct Plugin *plugin) { char *afsdir; char *key; char *sub_system; const char *peer_id; char *peer; char *value; char *expiry; struct GNUNET_DISK_FileHandle *fh; struct GNUNET_PEERSTORE_Record *entry; struct GNUNET_HashCode hkey; uint64_t size; char *buffer; char *line; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (plugin->cfg, "peerstore-flat", "FILENAME", &afsdir)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "peerstore-flat", "FILENAME"); return GNUNET_SYSERR; } if (GNUNET_OK != GNUNET_DISK_file_test (afsdir)) { if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (afsdir)) { GNUNET_break (0); GNUNET_free (afsdir); return GNUNET_SYSERR; } } /* afsdir should be UTF-8-encoded. If it isn't, it's a bug */ plugin->fn = afsdir; fh = GNUNET_DISK_file_open (afsdir, GNUNET_DISK_OPEN_CREATE | GNUNET_DISK_OPEN_READWRITE, GNUNET_DISK_PERM_USER_WRITE | GNUNET_DISK_PERM_USER_READ); if (NULL == fh) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Unable to initialize file: %s.\n"), afsdir); return GNUNET_SYSERR; } /* Load data from file into hashmap */ plugin->hm = GNUNET_CONTAINER_multihashmap_create (10, GNUNET_NO); if (GNUNET_SYSERR == GNUNET_DISK_file_size (afsdir, &size, GNUNET_YES, GNUNET_YES)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Unable to get filesize: %s.\n"), afsdir); return GNUNET_SYSERR; } buffer = GNUNET_malloc (size + 1); if (GNUNET_SYSERR == GNUNET_DISK_file_read (fh, buffer, size)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Unable to read file: %s.\n"), afsdir); GNUNET_DISK_file_close (fh); GNUNET_free (buffer); return GNUNET_SYSERR; } buffer[size] = '\0'; GNUNET_DISK_file_close (fh); if (0 < size) { line = strtok (buffer, "\n"); while (line != NULL) { sub_system = strtok (line, ","); if (NULL == sub_system) break; peer = strtok (NULL, ","); if (NULL == peer) break; key = strtok (NULL, ","); if (NULL == key) break; value = strtok (NULL, ","); if (NULL == value) break; expiry = strtok (NULL, ","); if (NULL == expiry) break; entry = GNUNET_new (struct GNUNET_PEERSTORE_Record); entry->sub_system = GNUNET_strdup (sub_system); entry->key = GNUNET_strdup (key); { size_t s; char *o; o = NULL; s = GNUNET_STRINGS_base64_decode (peer, strlen (peer), (void **) &o); if (sizeof(struct GNUNET_PeerIdentity) == s) GNUNET_memcpy (&entry->peer, o, s); else GNUNET_break (0); GNUNET_free_non_null (o); } entry->value_size = GNUNET_STRINGS_base64_decode (value, strlen (value), (void **) &entry->value); if (GNUNET_SYSERR == GNUNET_STRINGS_fancy_time_to_absolute (expiry, &entry->expiry)) { GNUNET_free (entry->sub_system); GNUNET_free (entry->key); GNUNET_free (entry); break; } peer_id = GNUNET_i2s (&entry->peer); GNUNET_CRYPTO_hash (peer_id, strlen (peer_id), &hkey); GNUNET_assert (GNUNET_OK == GNUNET_CONTAINER_multihashmap_put (plugin->hm, &hkey, entry, GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE)); } } GNUNET_free (buffer); return GNUNET_OK; } static int store_and_free_entries (void *cls, const struct GNUNET_HashCode *key, void *value) { struct GNUNET_DISK_FileHandle *fh = cls; struct GNUNET_PEERSTORE_Record *entry = value; char *line; char *peer; const char *expiry; char *val; GNUNET_STRINGS_base64_encode (entry->value, entry->value_size, &val); expiry = GNUNET_STRINGS_absolute_time_to_string (entry->expiry); GNUNET_STRINGS_base64_encode ((char *) &entry->peer, sizeof(struct GNUNET_PeerIdentity), &peer); GNUNET_asprintf (&line, "%s,%s,%s,%s,%s", entry->sub_system, peer, entry->key, val, expiry); GNUNET_free (val); GNUNET_free (peer); GNUNET_DISK_file_write (fh, line, strlen (line)); GNUNET_free (entry->sub_system); GNUNET_free (entry->key); GNUNET_free (entry->value); GNUNET_free (entry); GNUNET_free (line); return GNUNET_YES; } /** * Shutdown database connection and associate data * structures. * @param plugin the plugin context (state for this module) */ static void database_shutdown (struct Plugin *plugin) { struct GNUNET_DISK_FileHandle *fh; fh = GNUNET_DISK_file_open (plugin->fn, GNUNET_DISK_OPEN_CREATE | GNUNET_DISK_OPEN_TRUNCATE | GNUNET_DISK_OPEN_READWRITE, GNUNET_DISK_PERM_USER_WRITE | GNUNET_DISK_PERM_USER_READ); if (NULL == fh) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("Unable to initialize file: %s.\n"), plugin->fn); return; } GNUNET_CONTAINER_multihashmap_iterate (plugin->hm, &store_and_free_entries, fh); GNUNET_CONTAINER_multihashmap_destroy (plugin->hm); GNUNET_DISK_file_close (fh); } /** * Entry point for the plugin. * * @param cls The struct GNUNET_CONFIGURATION_Handle. * @return NULL on error, otherwise the plugin context */ void * libgnunet_plugin_peerstore_flat_init (void *cls) { static struct Plugin plugin; const struct GNUNET_CONFIGURATION_Handle *cfg = cls; struct GNUNET_PEERSTORE_PluginFunctions *api; if (NULL != plugin.cfg) return NULL; /* can only initialize once! */ memset (&plugin, 0, sizeof(struct Plugin)); plugin.cfg = cfg; if (GNUNET_OK != database_setup (&plugin)) { database_shutdown (&plugin); return NULL; } api = GNUNET_new (struct GNUNET_PEERSTORE_PluginFunctions); api->cls = &plugin; api->store_record = &peerstore_flat_store_record; api->iterate_records = &peerstore_flat_iterate_records; api->expire_records = &peerstore_flat_expire_records; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Flat plugin is running\n"); return api; } /** * Exit point from the plugin. * * @param cls The plugin context (as returned by "init") * @return Always NULL */ void * libgnunet_plugin_peerstore_flat_done (void *cls) { struct GNUNET_PEERSTORE_PluginFunctions *api = cls; struct Plugin *plugin = api->cls; database_shutdown (plugin); plugin->cfg = NULL; GNUNET_free (api); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Flat plugin is finished\n"); return NULL; } /* end of plugin_peerstore_sqlite.c */