use NULL as flag for evaluation of query, ensure we pass non-NULL for reply_block...
[oweals/gnunet.git] / src / namecache / plugin_namecache_flat.c
1  /*
2   * This file is part of GNUnet
3   * Copyright (C) 2009-2015 GNUnet e.V.
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_OPEN_READWRITE,
114                               GNUNET_DISK_PERM_USER_WRITE |
115                               GNUNET_DISK_PERM_USER_READ);
116   if (NULL == fh)
117   {
118     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
119                 _("Unable to initialize file: %s.\n"),
120                 afsdir);
121     return GNUNET_SYSERR;
122   }
123
124   if (GNUNET_SYSERR == GNUNET_DISK_file_size (afsdir,
125                                               &size,
126                                               GNUNET_YES,
127                                               GNUNET_YES))
128   {
129     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
130                 _("Unable to get filesize: %s.\n"),
131                 afsdir);
132     GNUNET_DISK_file_close (fh);
133     return GNUNET_SYSERR;
134   }
135
136   if (0 == size)
137   {
138     GNUNET_DISK_file_close (fh);
139     return GNUNET_OK;
140   }
141
142   buffer = GNUNET_malloc (size + 1);
143
144   if (GNUNET_SYSERR == GNUNET_DISK_file_read (fh,
145                                               buffer,
146                                               size))
147   {
148     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
149                 _("Unable to read file: %s.\n"),
150                 afsdir);
151     GNUNET_free (buffer);
152     GNUNET_DISK_file_close (fh);
153     return GNUNET_SYSERR;
154   }
155   buffer[size] = '\0';
156
157   GNUNET_DISK_file_close (fh);
158   if (0 < size)
159   {
160     line = strtok (buffer, "\n");
161     while (line != NULL) {
162       query = strtok (line, ",");
163       if (NULL == query)
164         break;
165       block = strtok (NULL, ",");
166       if (NULL == block)
167         break;
168       line = strtok (NULL, "\n");
169       entry = GNUNET_malloc (sizeof (struct FlatFileEntry));
170       GNUNET_assert (GNUNET_OK == GNUNET_CRYPTO_hash_from_string (query,
171                                         &entry->query));
172       GNUNET_STRINGS_base64_decode (block,
173                                     strlen (block),
174                                     &block_buffer);
175       entry->block = (struct GNUNET_GNSRECORD_Block *) block_buffer;
176       if (GNUNET_OK !=
177           GNUNET_CONTAINER_multihashmap_put (plugin->hm,
178                                              &entry->query,
179                                              entry,
180                                              GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
181       {
182         GNUNET_free (entry);
183         GNUNET_break (0);
184       }
185     }
186   }
187   GNUNET_free (buffer);
188   return GNUNET_OK;
189 }
190
191 /**
192  * Store values in hashmap in file and free data
193  *
194  * @param plugin the plugin context
195  */
196 static int
197 store_and_free_entries (void *cls,
198                         const struct GNUNET_HashCode *key,
199                         void *value)
200 {
201   struct GNUNET_DISK_FileHandle *fh = cls;
202   struct FlatFileEntry *entry = value;
203
204   char *line;
205   char *block_b64;
206   struct GNUNET_CRYPTO_HashAsciiEncoded query;
207   size_t block_size;
208
209   block_size = ntohl (entry->block->purpose.size) +
210     sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) +
211     sizeof (struct GNUNET_CRYPTO_EcdsaSignature);
212
213   GNUNET_STRINGS_base64_encode ((char*)entry->block,
214                                 block_size,
215                                 &block_b64);
216   GNUNET_CRYPTO_hash_to_enc (&entry->query,
217                              &query);
218   GNUNET_asprintf (&line,
219                    "%s,%s\n",
220                    (char*)&query,
221                    block_b64);
222
223   GNUNET_free (block_b64);
224
225   GNUNET_DISK_file_write (fh,
226                           line,
227                           strlen (line));
228
229   GNUNET_free (entry->block);
230   GNUNET_free (entry);
231   GNUNET_free (line);
232   return GNUNET_YES;
233 }
234
235 /**
236  * Shutdown database connection and associate data
237  * structures.
238  * @param plugin the plugin context (state for this module)
239  */
240 static void
241 database_shutdown (struct Plugin *plugin)
242 {
243   struct GNUNET_DISK_FileHandle *fh;
244   fh = GNUNET_DISK_file_open (plugin->fn,
245                               GNUNET_DISK_OPEN_CREATE |
246                               GNUNET_DISK_OPEN_TRUNCATE |
247                               GNUNET_DISK_OPEN_READWRITE,
248                               GNUNET_DISK_PERM_USER_WRITE |
249                               GNUNET_DISK_PERM_USER_READ);
250   if (NULL == fh)
251   {
252     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
253                 _("Unable to initialize file: %s.\n"),
254                 plugin->fn);
255     return;
256   }
257
258   GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
259                                          &store_and_free_entries,
260                                          fh);
261   GNUNET_CONTAINER_multihashmap_destroy (plugin->hm);
262   GNUNET_DISK_file_close (fh);
263
264 }
265
266 static int
267 expire_blocks (void *cls,
268                const struct GNUNET_HashCode *key,
269                void *value)
270 {
271   struct Plugin *plugin = cls;
272   struct FlatFileEntry *entry = value;
273   struct GNUNET_TIME_Absolute now;
274   struct GNUNET_TIME_Absolute expiration;
275
276   now = GNUNET_TIME_absolute_get ();
277   expiration = GNUNET_TIME_absolute_ntoh (entry->block->expiration_time);
278
279   if (0 == GNUNET_TIME_absolute_get_difference (now,
280                                                 expiration).rel_value_us)
281   {
282     GNUNET_CONTAINER_multihashmap_remove_all (plugin->hm, key);
283   }
284   return GNUNET_YES;
285 }
286
287
288
289 /**
290  * Removes any expired block.
291  *
292  * @param plugin the plugin
293  */
294 static void
295 namecache_expire_blocks (struct Plugin *plugin)
296 {
297   GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
298                                          &expire_blocks,
299                                          plugin);
300 }
301
302
303 /**
304  * Cache a block in the datastore.
305  *
306  * @param cls closure (internal context for the plugin)
307  * @param block block to cache
308  * @return #GNUNET_OK on success, else #GNUNET_SYSERR
309  */
310 static int
311 namecache_cache_block (void *cls,
312                        const struct GNUNET_GNSRECORD_Block *block)
313 {
314   struct Plugin *plugin = cls;
315   struct GNUNET_HashCode query;
316   struct FlatFileEntry *entry;
317   size_t block_size;
318
319   namecache_expire_blocks (plugin);
320   GNUNET_CRYPTO_hash (&block->derived_key,
321                       sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey),
322                       &query);
323   block_size = ntohl (block->purpose.size) +
324     sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey) +
325     sizeof (struct GNUNET_CRYPTO_EcdsaSignature);
326   if (block_size > 64 * 65536)
327   {
328     GNUNET_break (0);
329     return GNUNET_SYSERR;
330   }
331   entry = GNUNET_malloc (sizeof (struct FlatFileEntry));
332   entry->block = GNUNET_malloc (block_size);
333   GNUNET_memcpy (entry->block, block, block_size);
334   GNUNET_CONTAINER_multihashmap_remove_all (plugin->hm, &query);
335   if (GNUNET_OK !=
336       GNUNET_CONTAINER_multihashmap_put (plugin->hm,
337                                          &query,
338                                          entry,
339                                          GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
340   {
341     GNUNET_free (entry);
342     GNUNET_break (0);
343     return GNUNET_SYSERR;
344   }
345   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
346               "Caching block under derived key `%s'\n",
347               GNUNET_h2s_full (&query));
348   return GNUNET_OK;
349 }
350
351
352 /**
353  * Get the block for a particular zone and label in the
354  * datastore.  Will return at most one result to the iterator.
355  *
356  * @param cls closure (internal context for the plugin)
357  * @param query hash of public key derived from the zone and the label
358  * @param iter function to call with the result
359  * @param iter_cls closure for @a iter
360  * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
361  */
362 static int
363 namecache_lookup_block (void *cls,
364                         const struct GNUNET_HashCode *query,
365                         GNUNET_NAMECACHE_BlockCallback iter, void *iter_cls)
366 {
367   struct Plugin *plugin = cls;
368   const struct GNUNET_GNSRECORD_Block *block;
369
370   block = GNUNET_CONTAINER_multihashmap_get (plugin->hm, query);
371   if (NULL == block)
372     return GNUNET_NO;
373   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
374               "Found block under derived key `%s'\n",
375               GNUNET_h2s_full (query));
376   iter (iter_cls, block);
377   return GNUNET_YES;
378 }
379
380
381 /**
382  * Entry point for the plugin.
383  *
384  * @param cls the "struct GNUNET_NAMECACHE_PluginEnvironment*"
385  * @return NULL on error, otherwise the plugin context
386  */
387 void *
388 libgnunet_plugin_namecache_flat_init (void *cls)
389 {
390   static struct Plugin plugin;
391   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
392   struct GNUNET_NAMECACHE_PluginFunctions *api;
393
394   if (NULL != plugin.cfg)
395     return NULL;                /* can only initialize once! */
396   memset (&plugin, 0, sizeof (struct Plugin));
397   plugin.cfg = cfg;
398   if (GNUNET_OK != database_setup (&plugin))
399   {
400     database_shutdown (&plugin);
401     return NULL;
402   }
403   api = GNUNET_new (struct GNUNET_NAMECACHE_PluginFunctions);
404   api->cls = &plugin;
405   api->cache_block = &namecache_cache_block;
406   api->lookup_block = &namecache_lookup_block;
407   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
408               _("flat plugin running\n"));
409   return api;
410 }
411
412
413 /**
414  * Exit point from the plugin.
415  *
416  * @param cls the plugin context (as returned by "init")
417  * @return always NULL
418  */
419 void *
420 libgnunet_plugin_namecache_flat_done (void *cls)
421 {
422   struct GNUNET_NAMECACHE_PluginFunctions *api = cls;
423   struct Plugin *plugin = api->cls;
424
425   database_shutdown (plugin);
426   plugin->cfg = NULL;
427   GNUNET_free (api);
428   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
429               "flat plugin is finished\n");
430   return NULL;
431 }
432
433 /* end of plugin_namecache_sqlite.c */