fix context for zone_to_name flat plugin
[oweals/gnunet.git] / src / namestore / plugin_namestore_flat.c
1  /*
2   * This file is part of GNUnet
3   * Copyright (C) 2009-2015, 2018 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 namestore/plugin_namestore_flat.c
23  * @brief file-based namestore backend
24  * @author Martin Schanzenbach
25  * @author Christian Grothoff
26  */
27
28 #include "platform.h"
29 #include "gnunet_namestore_plugin.h"
30 #include "gnunet_namestore_service.h"
31 #include "gnunet_gnsrecord_lib.h"
32 #include "namestore.h"
33
34 /**
35  * Context for all functions in this plugin.
36  */
37 struct Plugin
38 {
39
40   const struct GNUNET_CONFIGURATION_Handle *cfg;
41
42   /**
43    * Database filename.
44    */
45   char *fn;
46
47   /**
48    * HashMap
49    */
50   struct GNUNET_CONTAINER_MultiHashMap *hm;
51
52 };
53
54
55 struct FlatFileEntry
56 {
57   /**
58    * Entry zone
59    */
60   struct GNUNET_CRYPTO_EcdsaPrivateKey *private_key;
61
62   /**
63    * Record cound
64    */
65   uint32_t record_count;
66
67   /**
68    * Rvalue
69    */
70   uint64_t rvalue;
71
72   /**
73    * Record data
74    */
75   struct GNUNET_GNSRECORD_Data *record_data;
76
77   /**
78    * Label
79    */
80   char *label;
81
82
83 };
84
85
86 /**
87  * Initialize the database connections and associated
88  * data structures (create tables and indices
89  * as needed as well).
90  *
91  * @param plugin the plugin context (state for this module)
92  * @return #GNUNET_OK on success
93  */
94 static int
95 database_setup (struct Plugin *plugin)
96 {
97   char *afsdir;
98   char *key;
99   char *record_data;
100   char *zone_private_key;
101   char *record_data_b64;
102   char *buffer;
103   char *line;
104   char *label;
105   char *rvalue;
106   char *record_count;
107   size_t record_data_size;
108   uint64_t size;
109   size_t key_len;
110   struct GNUNET_HashCode hkey;
111   struct GNUNET_DISK_FileHandle *fh;
112   struct FlatFileEntry *entry;
113
114   if (GNUNET_OK !=
115       GNUNET_CONFIGURATION_get_value_filename (plugin->cfg,
116                                                "namestore-flat",
117                                                "FILENAME",
118                                                &afsdir))
119   {
120     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
121                                "namestore-flat",
122                                "FILENAME");
123     return GNUNET_SYSERR;
124   }
125   if (GNUNET_OK !=
126       GNUNET_DISK_file_test (afsdir))
127   {
128     if (GNUNET_OK !=
129         GNUNET_DISK_directory_create_for_file (afsdir))
130     {
131       GNUNET_break (0);
132       GNUNET_free (afsdir);
133       return GNUNET_SYSERR;
134     }
135   }
136   /* afsdir should be UTF-8-encoded. If it isn't, it's a bug */
137   plugin->fn = afsdir;
138
139   /* Load data from file into hashmap */
140   plugin->hm = GNUNET_CONTAINER_multihashmap_create (10,
141                                                      GNUNET_NO);
142   fh = GNUNET_DISK_file_open (afsdir,
143                               GNUNET_DISK_OPEN_CREATE |
144                               GNUNET_DISK_OPEN_READWRITE,
145                               GNUNET_DISK_PERM_USER_WRITE |
146                               GNUNET_DISK_PERM_USER_READ);
147   if (NULL == fh)
148   {
149     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
150          _("Unable to initialize file: %s.\n"),
151          afsdir);
152     return GNUNET_SYSERR;
153   }
154   if (GNUNET_SYSERR ==
155       GNUNET_DISK_file_size (afsdir,
156                              &size,
157                              GNUNET_YES,
158                              GNUNET_YES))
159   {
160     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
161          _("Unable to get filesize: %s.\n"),
162          afsdir);
163     GNUNET_DISK_file_close (fh);
164     return GNUNET_SYSERR;
165   }
166
167   buffer = GNUNET_malloc (size + 1);
168   if (GNUNET_SYSERR ==
169       GNUNET_DISK_file_read (fh,
170                              buffer,
171                              size))
172   {
173     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
174          _("Unable to read file: %s.\n"),
175          afsdir);
176     GNUNET_free (buffer);
177     GNUNET_DISK_file_close (fh);
178     return GNUNET_SYSERR;
179   }
180   buffer[size] = '\0';
181   GNUNET_DISK_file_close (fh);
182
183   if (0 < size)
184   {
185     line = strtok (buffer, "\n");
186     while (line != NULL)
187     {
188       zone_private_key = strtok (line, ",");
189       if (NULL == zone_private_key)
190         break;
191       rvalue = strtok (NULL, ",");
192       if (NULL == rvalue)
193         break;
194       record_count = strtok (NULL, ",");
195       if (NULL == record_count)
196         break;
197       record_data_b64 = strtok (NULL, ",");
198       if (NULL == record_data_b64)
199         break;
200       label = strtok (NULL, ",");
201       if (NULL == label)
202         break;
203       line = strtok (NULL, "\n");
204       entry = GNUNET_new (struct FlatFileEntry);
205       {
206         unsigned long long ll;
207
208         if (1 != sscanf (rvalue,
209                          "%llu",
210                          &ll))
211         {
212           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
213                       "Error parsing entry\n");
214           GNUNET_free (entry);
215           break;
216         }
217         entry->rvalue = (uint64_t) ll;
218       }
219       {
220         unsigned int ui;
221
222         if (1 != sscanf (record_count,
223                          "%u",
224                          &ui))
225         {
226           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
227                       "Error parsing entry\n");
228           GNUNET_free (entry);
229           break;
230         }
231         entry->record_count = (uint32_t) ui;
232       }
233       entry->label = GNUNET_strdup (label);
234       record_data_size
235         = GNUNET_STRINGS_base64_decode (record_data_b64,
236                                         strlen (record_data_b64),
237                                         &record_data);
238       entry->record_data =
239         GNUNET_new_array (entry->record_count,
240                           struct GNUNET_GNSRECORD_Data);
241       if (GNUNET_OK !=
242           GNUNET_GNSRECORD_records_deserialize (record_data_size,
243                                                 record_data,
244                                                 entry->record_count,
245                                                 entry->record_data))
246       {
247         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
248                     "Unable to deserialize record %s\n",
249                     label);
250         GNUNET_free (entry->label);
251         GNUNET_free (entry);
252         GNUNET_free (record_data);
253         break;
254       }
255       GNUNET_free (record_data);
256       GNUNET_STRINGS_base64_decode (zone_private_key,
257                                     strlen (zone_private_key),
258                                     (char**)&entry->private_key);
259       key_len = strlen (label) + sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey);
260       key = GNUNET_malloc (strlen (label) + sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
261       GNUNET_memcpy (key,
262                      label,
263                      strlen (label));
264       GNUNET_memcpy (key+strlen(label),
265                      entry->private_key,
266                      sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
267       GNUNET_CRYPTO_hash (key,
268                           key_len,
269                           &hkey);
270       GNUNET_free (key);
271       if (GNUNET_OK !=
272           GNUNET_CONTAINER_multihashmap_put (plugin->hm,
273                                              &hkey,
274                                              entry,
275                                              GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
276       {
277         GNUNET_free (entry);
278         GNUNET_break (0);
279       }
280     }
281   }
282   GNUNET_free (buffer);
283   return GNUNET_OK;
284 }
285
286
287 /**
288  * Store values in hashmap in file and free data
289  *
290  * @param plugin the plugin context
291  * @param key key in the map
292  * @param value a `struct FlatFileEntry`
293  */
294 static int
295 store_and_free_entries (void *cls,
296                         const struct GNUNET_HashCode *key,
297                         void *value)
298 {
299   struct GNUNET_DISK_FileHandle *fh = cls;
300   struct FlatFileEntry *entry = value;
301   char *line;
302   char *zone_private_key;
303   char *record_data_b64;
304   size_t data_size;
305
306   (void) key;
307   GNUNET_STRINGS_base64_encode ((char*)entry->private_key,
308                                 sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey),
309                                 &zone_private_key);
310   data_size = GNUNET_GNSRECORD_records_get_size (entry->record_count,
311                                                  entry->record_data);
312   {
313     char data[data_size];
314     ssize_t ret;
315
316     ret = GNUNET_GNSRECORD_records_serialize (entry->record_count,
317                                               entry->record_data,
318                                               data_size,
319                                               data);
320     if ( (ret < 0) ||
321          (data_size != (size_t) ret) )
322     {
323       GNUNET_break (0);
324       GNUNET_free (zone_private_key);
325       return GNUNET_SYSERR;
326     }
327     GNUNET_STRINGS_base64_encode (data,
328                                   data_size,
329                                   &record_data_b64);
330   }
331   GNUNET_asprintf (&line,
332                    "%s,%llu,%u,%s,%s\n",
333                    zone_private_key,
334                    (unsigned long long) entry->rvalue,
335                    (unsigned int) entry->record_count,
336                    record_data_b64,
337                    entry->label);
338   GNUNET_free (record_data_b64);
339   GNUNET_free (zone_private_key);
340
341   GNUNET_DISK_file_write (fh,
342                           line,
343                           strlen (line));
344
345   GNUNET_free (line);
346   GNUNET_free (entry->private_key);
347   GNUNET_free (entry->label);
348   GNUNET_free (entry->record_data);
349   GNUNET_free (entry);
350   return GNUNET_YES;
351 }
352
353
354 /**
355  * Shutdown database connection and associate data
356  * structures.
357  * @param plugin the plugin context (state for this module)
358  */
359 static void
360 database_shutdown (struct Plugin *plugin)
361 {
362   struct GNUNET_DISK_FileHandle *fh;
363
364   fh = GNUNET_DISK_file_open (plugin->fn,
365                               GNUNET_DISK_OPEN_CREATE |
366                               GNUNET_DISK_OPEN_TRUNCATE |
367                               GNUNET_DISK_OPEN_READWRITE,
368                               GNUNET_DISK_PERM_USER_WRITE |
369                               GNUNET_DISK_PERM_USER_READ);
370   if (NULL == fh)
371   {
372     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
373                 _("Unable to initialize file: %s.\n"),
374                 plugin->fn);
375     return;
376   }
377
378   GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
379                                          &store_and_free_entries,
380                                          fh);
381   GNUNET_CONTAINER_multihashmap_destroy (plugin->hm);
382   GNUNET_DISK_file_close (fh);
383 }
384
385
386 /**
387  * Store a record in the datastore.  Removes any existing record in the
388  * same zone with the same name.
389  *
390  * @param cls closure (internal context for the plugin)
391  * @param zone_key private key of the zone
392  * @param label name that is being mapped (at most 255 characters long)
393  * @param rd_count number of entries in @a rd array
394  * @param rd array of records with data to store
395  * @return #GNUNET_OK on success, else #GNUNET_SYSERR
396  */
397 static int
398 namestore_flat_store_records (void *cls,
399                               const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone_key,
400                               const char *label,
401                               unsigned int rd_count,
402                               const struct GNUNET_GNSRECORD_Data *rd)
403 {
404   struct Plugin *plugin = cls;
405   uint64_t rvalue;
406   size_t key_len;
407   char *key;
408   struct GNUNET_HashCode hkey;
409   struct FlatFileEntry *entry;
410
411   rvalue = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
412                                      UINT64_MAX);
413   key_len = strlen (label) + sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey);
414   key = GNUNET_malloc (key_len);
415   GNUNET_memcpy (key,
416                  label,
417                  strlen (label));
418   GNUNET_memcpy (key + strlen(label),
419                  zone_key,
420                  sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
421   GNUNET_CRYPTO_hash (key,
422                       key_len,
423                       &hkey);
424   GNUNET_CONTAINER_multihashmap_remove_all (plugin->hm,
425                                             &hkey);
426   if (0 == rd_count)
427   {
428     GNUNET_log_from (GNUNET_ERROR_TYPE_DEBUG,
429                      "sqlite",
430                      "Record deleted\n");
431     return GNUNET_OK;
432   }
433   entry = GNUNET_new (struct FlatFileEntry);
434   entry->private_key = GNUNET_new (struct GNUNET_CRYPTO_EcdsaPrivateKey);
435   GNUNET_asprintf (&entry->label,
436                    label,
437                    strlen (label));
438   GNUNET_memcpy (entry->private_key,
439                  zone_key,
440                  sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
441   entry->rvalue = rvalue;
442   entry->record_count = rd_count;
443   entry->record_data = GNUNET_new_array (rd_count,
444                                          struct GNUNET_GNSRECORD_Data);
445   for (unsigned int i = 0; i < rd_count; i++)
446   {
447     entry->record_data[i].expiration_time = rd[i].expiration_time;
448     entry->record_data[i].record_type = rd[i].record_type;
449     entry->record_data[i].flags = rd[i].flags;
450     entry->record_data[i].data_size = rd[i].data_size;
451     entry->record_data[i].data = GNUNET_malloc (rd[i].data_size);
452     GNUNET_memcpy ((char*)entry->record_data[i].data,
453                    rd[i].data,
454                    rd[i].data_size);
455   }
456   return GNUNET_CONTAINER_multihashmap_put (plugin->hm,
457                                             &hkey,
458                                             entry,
459                                             GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
460 }
461
462
463 /**
464  * Lookup records in the datastore for which we are the authority.
465  *
466  * @param cls closure (internal context for the plugin)
467  * @param zone private key of the zone
468  * @param label name of the record in the zone
469  * @param iter function to call with the result
470  * @param iter_cls closure for @a iter
471  * @return #GNUNET_OK on success, #GNUNET_NO for no results, else #GNUNET_SYSERR
472  */
473 static int
474 namestore_flat_lookup_records (void *cls,
475                                const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
476                                const char *label,
477                                GNUNET_NAMESTORE_RecordIterator iter,
478                                void *iter_cls)
479 {
480   struct Plugin *plugin = cls;
481   struct FlatFileEntry *entry;
482   struct GNUNET_HashCode hkey;
483   char *key;
484   size_t key_len;
485
486   if (NULL == zone)
487   {
488     GNUNET_break (0);
489     return GNUNET_SYSERR;
490   }
491   key_len = strlen (label) + sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey);
492   key = GNUNET_malloc (key_len);
493   GNUNET_memcpy (key,
494                  label,
495                  strlen (label));
496   GNUNET_memcpy (key+strlen(label),
497                  zone,
498                  sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey));
499   GNUNET_CRYPTO_hash (key,
500                       key_len,
501                       &hkey);
502   GNUNET_free (key);
503
504   entry = GNUNET_CONTAINER_multihashmap_get (plugin->hm,
505                                              &hkey);
506
507   if (NULL == entry)
508     return GNUNET_NO;
509   if (NULL != iter)
510     iter (iter_cls,
511           0,
512           entry->private_key,
513           entry->label,
514           entry->record_count,
515           entry->record_data);
516   return GNUNET_YES;
517 }
518
519
520 /**
521  * Closure for #iterate_zones.
522  */
523 struct IterateContext
524 {
525   /**
526    * How many more records should we skip before returning results?
527    */
528   uint64_t offset;
529
530   /**
531    * How many more records should we return?
532    */
533   uint64_t limit;
534
535   /**
536    * What is the position of the current entry, counting
537    * starts from 1.
538    */
539   uint64_t pos;
540
541   /**
542    * Target zone.
543    */
544   const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone;
545
546   /**
547    * Function to call on each record.
548    */
549   GNUNET_NAMESTORE_RecordIterator iter;
550
551   /**
552    * Closure for @e iter.
553    */
554   void *iter_cls;
555
556 };
557
558
559 /**
560  * Helper function for #namestore_flat_iterate_records().
561  *
562  * @param cls a `struct IterateContext`
563  * @param key unused
564  * @param value a `struct FlatFileEntry`
565  * @return #GNUNET_YES to continue the iteration
566  */
567 static int
568 iterate_zones (void *cls,
569                const struct GNUNET_HashCode *key,
570                void *value)
571 {
572   struct IterateContext *ic = cls;
573   struct FlatFileEntry *entry = value;
574
575   (void) key;
576   ic->pos++;
577   if (0 == ic->limit)
578     return GNUNET_NO;
579   if ( (NULL != ic->zone) &&
580        (0 != memcmp (entry->private_key,
581                      ic->zone,
582                      sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey))) )
583     return GNUNET_YES;
584   if (ic->offset > 0)
585   {
586     ic->offset--;
587     return GNUNET_YES;
588   }
589   ic->iter (ic->iter_cls,
590             ic->pos,
591             entry->private_key,
592             entry->label,
593             entry->record_count,
594             entry->record_data);
595   ic->limit--;
596   if (0 == ic->limit)
597     return GNUNET_NO;
598   return GNUNET_YES;
599 }
600
601
602 /**
603  * Iterate over the results for a particular key and zone in the
604  * datastore.  Will return at most one result to the iterator.
605  *
606  * @param cls closure (internal context for the plugin)
607  * @param zone hash of public key of the zone, NULL to iterate over all zones
608  * @param serial serial number to exclude in the list of all matching records
609  * @param limit maximum number of results to return to @a iter
610  * @param iter function to call with the result
611  * @param iter_cls closure for @a iter
612  * @return #GNUNET_OK on success, #GNUNET_NO if there were no more results, #GNUNET_SYSERR on error
613  */
614 static int
615 namestore_flat_iterate_records (void *cls,
616                                 const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
617                                 uint64_t serial,
618                                 uint64_t limit,
619                                 GNUNET_NAMESTORE_RecordIterator iter,
620                                 void *iter_cls)
621 {
622   struct Plugin *plugin = cls;
623   struct IterateContext ic;
624
625   ic.offset = serial;
626   ic.pos = 0;
627   ic.limit = limit;
628   ic.iter = iter;
629   ic.iter_cls = iter_cls;
630   ic.zone = zone;
631   GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
632                                          &iterate_zones,
633                                          &ic);
634   return (0 == ic.limit) ? GNUNET_OK : GNUNET_NO;
635 }
636
637
638 /**
639  * Closure for #zone_to_name.
640  */
641 struct ZoneToNameContext
642 {
643   const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone;
644   const struct GNUNET_CRYPTO_EcdsaPublicKey *value_zone;
645   GNUNET_NAMESTORE_RecordIterator iter;
646   void *iter_cls;
647
648   int result_found;
649 };
650
651
652 static int
653 zone_to_name (void *cls,
654               const struct GNUNET_HashCode *key,
655               void *value)
656 {
657   struct ZoneToNameContext *ztn = cls;
658   struct FlatFileEntry *entry = value;
659
660   (void) key;
661   if (0 != memcmp (entry->private_key,
662                    ztn->zone,
663                    sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey)))
664     return GNUNET_YES;
665
666   for (unsigned int i = 0; i < entry->record_count; i++)
667   {
668     if (GNUNET_GNSRECORD_TYPE_PKEY != entry->record_data[i].record_type)
669       continue;
670     if (0 == memcmp (ztn->value_zone,
671                      entry->record_data[i].data,
672                      sizeof (struct GNUNET_CRYPTO_EcdsaPublicKey)))
673     {
674       ztn->iter (ztn->iter_cls,
675                  0,
676                  entry->private_key,
677                  entry->label,
678                  entry->record_count,
679                  entry->record_data);
680       ztn->result_found = GNUNET_YES;
681     }
682   }
683   return GNUNET_YES;
684 }
685
686
687 /**
688  * Look for an existing PKEY delegation record for a given public key.
689  * Returns at most one result to the iterator.
690  *
691  * @param cls closure (internal context for the plugin)
692  * @param zone private key of the zone to look up in, never NULL
693  * @param value_zone public key of the target zone (value), never NULL
694  * @param iter function to call with the result
695  * @param iter_cls closure for @a iter
696  * @return #GNUNET_OK on success, #GNUNET_NO if there were no results, #GNUNET_SYSERR on error
697  */
698 static int
699 namestore_flat_zone_to_name (void *cls,
700                              const struct GNUNET_CRYPTO_EcdsaPrivateKey *zone,
701                              const struct GNUNET_CRYPTO_EcdsaPublicKey *value_zone,
702                              GNUNET_NAMESTORE_RecordIterator iter,
703                              void *iter_cls)
704 {
705   struct Plugin *plugin = cls;
706   struct ZoneToNameContext ztn = {
707     .iter = iter,
708     .iter_cls = iter_cls,
709     .zone = zone,
710     .value_zone = value_zone,
711     .result_found = GNUNET_NO
712   };
713
714   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
715               "Performing reverse lookup for `%s'\n",
716               GNUNET_GNSRECORD_z2s (value_zone));
717   GNUNET_CONTAINER_multihashmap_iterate (plugin->hm,
718                                          &zone_to_name,
719                                          &ztn);
720   return ztn.result_found;
721 }
722
723
724 /**
725  * Entry point for the plugin.
726  *
727  * @param cls the "struct GNUNET_NAMESTORE_PluginEnvironment*"
728  * @return NULL on error, otherwise the plugin context
729  */
730 void *
731 libgnunet_plugin_namestore_flat_init (void *cls)
732 {
733   static struct Plugin plugin;
734   const struct GNUNET_CONFIGURATION_Handle *cfg = cls;
735   struct GNUNET_NAMESTORE_PluginFunctions *api;
736
737   if (NULL != plugin.cfg)
738     return NULL;                /* can only initialize once! */
739   memset (&plugin,
740           0,
741           sizeof (struct Plugin));
742   plugin.cfg = cfg;
743   if (GNUNET_OK != database_setup (&plugin))
744   {
745     database_shutdown (&plugin);
746     return NULL;
747   }
748   api = GNUNET_new (struct GNUNET_NAMESTORE_PluginFunctions);
749   api->cls = &plugin;
750   api->store_records = &namestore_flat_store_records;
751   api->iterate_records = &namestore_flat_iterate_records;
752   api->zone_to_name = &namestore_flat_zone_to_name;
753   api->lookup_records = &namestore_flat_lookup_records;
754   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
755               _("flat file database running\n"));
756   return api;
757 }
758
759
760 /**
761  * Exit point from the plugin.
762  *
763  * @param cls the plugin context (as returned by "init")
764  * @return always NULL
765  */
766 void *
767 libgnunet_plugin_namestore_flat_done (void *cls)
768 {
769   struct GNUNET_NAMESTORE_PluginFunctions *api = cls;
770   struct Plugin *plugin = api->cls;
771
772   database_shutdown (plugin);
773   plugin->cfg = NULL;
774   GNUNET_free (api);
775   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
776               "flat file plugin is finished\n");
777   return NULL;
778 }
779
780 /* end of plugin_namestore_flat.c */