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