-add flat namecache
[oweals/gnunet.git] / src / namecache / plugin_namecache_flat.c
1  /*
2   * This file is part of GNUnet
3   * Copyright (C) 2009-2015 Christian Grothoff (and other contributing authors)
4   *
5   * GNUnet is free software; you can redistribute it and/or modify
6   * it under the terms of the GNU General Public License as published
7   * by the Free Software Foundation; either version 3, or (at your
8   * 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   * General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with GNUnet; see the file COPYING.  If not, write to the
17   * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18   * Boston, MA 02110-1301, USA.
19   */
20
21 /**
22  * @file namecache/plugin_namecache_flat.c
23  * @brief flat file-based namecache backend
24  * @author Martin Schanzenbach
25  */
26
27 #include "platform.h"
28 #include "gnunet_namecache_plugin.h"
29 #include "gnunet_namecache_service.h"
30 #include "gnunet_gnsrecord_lib.h"
31 #include "namecache.h"
32
33 /**
34  * Context for all functions in this plugin.
35  */
36 struct Plugin
37 {
38
39   const struct GNUNET_CONFIGURATION_Handle *cfg;
40
41   /**
42    * Database filename.
43    */
44   char *fn;
45   
46   /**
47    * HashMap
48    */
49   struct GNUNET_CONTAINER_MultiHashMap *hm;
50
51 };
52
53 struct FlatFileEntry
54 {
55   /**
56    * Block
57    */
58   struct GNUNET_GNSRECORD_Block *block;
59
60   /**
61    * query
62    */
63   struct GNUNET_HashCode query;
64
65 };
66
67 /**
68  * Initialize the database connections and associated
69  * data structures (create tables and indices
70  * as needed as well).
71  *
72  * @param plugin the plugin context (state for this module)
73  * @return #GNUNET_OK on success
74  */
75 static int
76 database_setup (struct Plugin *plugin)
77 {
78   char *afsdir;
79   char* block_buffer;
80   char* buffer;
81   char* line;
82   char* query;
83   char* block;
84   size_t size;
85   struct FlatFileEntry *entry;
86   struct GNUNET_DISK_FileHandle *fh;
87
88   if (GNUNET_OK !=
89       GNUNET_CONFIGURATION_get_value_filename (plugin->cfg, "namecache-flat",
90                                                "FILENAME", &afsdir))
91   {
92     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
93                                "namecache-flat", "FILENAME");
94     return GNUNET_SYSERR;
95   }
96   if (GNUNET_OK != GNUNET_DISK_file_test (afsdir))
97   {
98     if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (afsdir))
99     {
100       GNUNET_break (0);
101       GNUNET_free (afsdir);
102       return GNUNET_SYSERR;
103     }
104   }
105   /* afsdir should be UTF-8-encoded. If it isn't, it's a bug */
106   plugin->fn = afsdir;
107
108   /* Load data from file into hashmap */
109   plugin->hm = GNUNET_CONTAINER_multihashmap_create (10,
110                                                      GNUNET_NO);
111   fh = GNUNET_DISK_file_open (afsdir,
112                               GNUNET_DISK_OPEN_CREATE,
113                               GNUNET_DISK_PERM_USER_WRITE |
114                               GNUNET_DISK_PERM_USER_READ);
115   if (NULL == fh)
116   {
117     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
118                 _("Unable to initialize file: %s.\n"),
119                 afsdir);
120     return GNUNET_SYSERR;
121   }
122
123   if (GNUNET_SYSERR == GNUNET_DISK_file_size (afsdir,
124                                               &size,
125                                               GNUNET_YES,
126                                               GNUNET_YES))
127   {
128     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
129                 _("Unable to get filesize: %s.\n"),
130                 afsdir);
131     return GNUNET_SYSERR;
132   }
133
134   buffer = GNUNET_malloc (size);
135
136   if (GNUNET_SYSERR == GNUNET_DISK_file_read (fh,
137                                               buffer,
138                                               size))
139   {
140     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
141                 _("Unable to read file: %s.\n"),
142                 afsdir);
143     return GNUNET_SYSERR;
144   }
145
146   GNUNET_DISK_file_close (fh);
147
148   line = strtok ("\n", buffer);
149   while (line != NULL) {
150     query = strtok (",", line);
151     block = strtok (NULL, line);
152     line = strtok ("\n", buffer);
153     entry = GNUNET_malloc (sizeof (struct FlatFileEntry));
154     GNUNET_CRYPTO_hash_from_string (query,
155                                     &entry->query);
156     GNUNET_STRINGS_base64_decode (block,
157                                   strlen (block),
158                                   &block_buffer);
159     entry->block = (struct GNUNET_GNSRECORD_Block *) block_buffer;
160     if (GNUNET_OK != 
161         GNUNET_CONTAINER_multihashmap_put (plugin->hm,
162                                            &entry->query,
163                                            entry,
164                                            GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
165     {
166       GNUNET_free (entry);
167       GNUNET_break (0);
168     }
169   }
170   GNUNET_free (buffer);
171   return GNUNET_OK;
172 }
173
174 /**
175  * Store values in hashmap in file and free data
176  *
177  * @param plugin the plugin context
178  */
179 static int
180 store_and_free_entries (void *cls,
181                         const struct GNUNET_HashCode *key,
182                         void *value)
183 {
184   struct GNUNET_DISK_FileHandle *fh = cls;
185   struct FlatFileEntry *entry = value;
186
187   char *line;
188   char *block_b64;
189   struct GNUNET_CRYPTO_HashAsciiEncoded query;
190   size_t block_size;
191
192   block_size = ntohl (entry->block->purpose.size) +
193     sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) +
194     sizeof (struct GNUNET_CRYPTO_EcdsaSignature);
195
196   GNUNET_STRINGS_base64_encode ((char*)entry->block,
197                                 block_size,
198                                 &block_b64);
199   GNUNET_CRYPTO_hash_to_enc (&entry->query,
200                              &query);
201   GNUNET_asprintf (&line,
202                    "%s,%s\n",
203                    (char*)&query,
204                    block_b64);
205
206   GNUNET_free (block_b64);
207
208   GNUNET_DISK_file_write (fh,
209                           line,
210                           strlen (line));
211
212   GNUNET_free (entry->block);
213   GNUNET_free (entry);
214   return GNUNET_YES;
215 }
216
217 /**
218  * Shutdown database connection and associate data
219  * structures.
220  * @param plugin the plugin context (state for this module)
221  */
222 static void
223 database_shutdown (struct Plugin *plugin)
224 {
225   struct GNUNET_DISK_FileHandle *fh;
226   fh = GNUNET_DISK_file_open (plugin->fn,
227                               GNUNET_DISK_OPEN_CREATE |
228                               GNUNET_DISK_OPEN_TRUNCATE,
229                               GNUNET_DISK_PERM_USER_WRITE |
230                               GNUNET_DISK_PERM_USER_READ);
231   if (NULL == fh)
232   {
233     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
234                 _("Unable to initialize file: %s.\n"),
235                 plugin->fn);
236     return;
237   }
238
239   GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
240                                          &store_and_free_entries,
241                                          fh);
242   GNUNET_CONTAINER_multihashmap_destroy (plugin->hm);
243   GNUNET_DISK_file_close (fh);
244
245 }
246
247 static int
248 expire_blocks (void *cls,
249                const struct GNUNET_HashCode *key,
250                void *value)
251 {
252   struct Plugin *plugin = cls;
253   struct FlatFileEntry *entry = value;
254   struct GNUNET_TIME_Absolute now;
255   struct GNUNET_TIME_Absolute expiration;
256
257   now = GNUNET_TIME_absolute_get ();
258   expiration = GNUNET_TIME_absolute_ntoh (entry->block->expiration_time);
259
260   if (0 == GNUNET_TIME_absolute_get_difference (now,
261                                                 expiration).rel_value_us)
262   {
263     GNUNET_CONTAINER_multihashmap_remove_all (plugin->hm, key);
264   }
265   return GNUNET_YES;
266 }
267
268
269
270 /**
271  * Removes any expired block.
272  *
273  * @param plugin the plugin
274  */
275 static void
276 namecache_expire_blocks (struct Plugin *plugin)
277 {
278   GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
279                                          &expire_blocks,
280                                          plugin);
281 }
282
283
284 /**
285  * Cache a block in the datastore.
286  *
287  * @param cls closure (internal context for the plugin)
288  * @param block block to cache
289  * @return #GNUNET_OK on success, else #GNUNET_SYSERR
290  */
291 static int
292 namecache_cache_block (void *cls,
293                               const struct GNUNET_GNSRECORD_Block *block)
294 {
295   struct Plugin *plugin = cls;
296   struct GNUNET_HashCode query;
297   struct FlatFileEntry *entry;
298   size_t block_size;
299
300   namecache_expire_blocks (plugin);
301   GNUNET_CRYPTO_hash (&block->derived_key,
302                       sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
303                       &query);
304   block_size = ntohl (block->purpose.size) +
305     sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) +
306     sizeof (struct GNUNET_CRYPTO_EcdsaSignature);
307   if (block_size > 64 * 65536)
308   {
309     GNUNET_break (0);
310     return GNUNET_SYSERR;
311   }
312   entry = GNUNET_malloc (sizeof (struct FlatFileEntry));
313   entry->block = GNUNET_malloc (block_size);
314   memcpy (entry->block, block, block_size);
315   GNUNET_CONTAINER_multihashmap_remove_all (plugin->hm, &query);
316   if (GNUNET_OK != 
317       GNUNET_CONTAINER_multihashmap_put (plugin->hm,
318                                          &query,
319                                          entry,
320                                          GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
321   {
322     GNUNET_free (entry);
323     GNUNET_break (0);
324     return GNUNET_SYSERR;
325   }
326   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
327               "Caching block under derived key `%s'\n",
328               GNUNET_h2s_full (&query));
329   return GNUNET_OK;
330 }
331
332
333 /**
334  * Get the block for a particular zone and label in the
335  * datastore.  Will return at most one result to the iterator.
336  *
337  * @param cls closure (internal context for the plugin)
338  * @param query hash of public key derived from the zone and the label
339  * @param iter function to call with the result
340  * @param iter_cls closure for @a iter
341  * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
342  */
343 static int
344 namecache_lookup_block (void *cls,
345                         const struct GNUNET_HashCode *query,
346                         GNUNET_NAMECACHE_BlockCallback iter, void *iter_cls)
347 {
348   struct Plugin *plugin = cls;
349   const struct GNUNET_GNSRECORD_Block *block;
350
351   block = GNUNET_CONTAINER_multihashmap_get (plugin->hm, query);
352   if (NULL == block)
353     return GNUNET_NO;
354   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
355               "Found block under derived key `%s'\n",
356               GNUNET_h2s_full (query));
357   iter (iter_cls, block);
358   return GNUNET_YES;
359 }
360
361
362 /**
363  * Entry point for the plugin.
364  *
365  * @param cls the "struct GNUNET_NAMECACHE_PluginEnvironment*"
366  * @return NULL on error, otherwise the plugin context
367  */
368 void *
369 libgnunet_plugin_namecache_flat_init (void *cls)
370 {
371   static struct Plugin plugin;
372   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
373   struct GNUNET_NAMECACHE_PluginFunctions *api;
374
375   if (NULL != plugin.cfg)
376     return NULL;                /* can only initialize once! */
377   memset (&plugin, 0, sizeof (struct Plugin));
378   plugin.cfg = cfg;
379   if (GNUNET_OK != database_setup (&plugin))
380   {
381     database_shutdown (&plugin);
382     return NULL;
383   }
384   api = GNUNET_new (struct GNUNET_NAMECACHE_PluginFunctions);
385   api->cls = &plugin;
386   api->cache_block = &namecache_cache_block;
387   api->lookup_block = &namecache_lookup_block;
388   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
389        _("flat plugin running\n"));
390   return api;
391 }
392
393
394 /**
395  * Exit point from the plugin.
396  *
397  * @param cls the plugin context (as returned by "init")
398  * @return always NULL
399  */
400 void *
401 libgnunet_plugin_namecache_flat_done (void *cls)
402 {
403   struct GNUNET_NAMECACHE_PluginFunctions *api = cls;
404   struct Plugin *plugin = api->cls;
405
406   database_shutdown (plugin);
407   plugin->cfg = NULL;
408   GNUNET_free (api);
409   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
410        "flat plugin is finished\n");
411   return NULL;
412 }
413
414 /* end of plugin_namecache_sqlite.c */