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