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