vminko: implementing BINDTO option for hostlist service (#2140)
[oweals/gnunet.git] / src / peerinfo / gnunet-service-peerinfo.c
1 /*
2      This file is part of GNUnet.
3      (C) 2001, 2002, 2004, 2005, 2007, 2009, 2010 Christian Grothoff (and other contributing authors)
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., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file peerinfo/gnunet-service-peerinfo.c
23  * @brief maintains list of known peers
24  *
25  * Code to maintain the list of currently known hosts (in memory
26  * structure of data/hosts/).
27  *
28  * @author Christian Grothoff
29  *
30  * TODO:
31  * - HostEntries are never 'free'd (add expiration, upper bound?)
32  */
33
34 #include "platform.h"
35 #include "gnunet_crypto_lib.h"
36 #include "gnunet_container_lib.h"
37 #include "gnunet_disk_lib.h"
38 #include "gnunet_hello_lib.h"
39 #include "gnunet_protocols.h"
40 #include "gnunet_service_lib.h"
41 #include "gnunet_statistics_service.h"
42 #include "peerinfo.h"
43
44 /**
45  * How often do we scan the HOST_DIR for new entries?
46  */
47 #define DATA_HOST_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
48
49 /**
50  * How often do we discard old entries in data/hosts/?
51  */
52 #define DATA_HOST_CLEAN_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 60)
53
54 /**
55  * In-memory cache of known hosts.
56  */
57 struct HostEntry
58 {
59
60   /**
61    * Identity of the peer.
62    */
63   struct GNUNET_PeerIdentity identity;
64
65   /**
66    * Hello for the peer (can be NULL)
67    */
68   struct GNUNET_HELLO_Message *hello;
69
70 };
71
72
73 /**
74  * The in-memory list of known hosts, mapping of
75  * host IDs to 'struct HostEntry*' values.
76  */
77 static struct GNUNET_CONTAINER_MultiHashMap *hostmap;
78
79 /**
80  * Clients to immediately notify about all changes.
81  */
82 static struct GNUNET_SERVER_NotificationContext *notify_list;
83
84 /**
85  * Directory where the hellos are stored in (data/hosts)
86  */
87 static char *networkIdDirectory;
88
89 /**
90  * Handle for reporting statistics.
91  */
92 static struct GNUNET_STATISTICS_Handle *stats;
93
94
95 /**
96  * Notify all clients in the notify list about the
97  * given host entry changing.
98  */
99 static struct InfoMessage *
100 make_info_message (const struct HostEntry *he)
101 {
102   struct InfoMessage *im;
103   size_t hs;
104
105   hs = (he->hello == NULL) ? 0 : GNUNET_HELLO_size (he->hello);
106   im = GNUNET_malloc (sizeof (struct InfoMessage) + hs);
107   im->header.size = htons (hs + sizeof (struct InfoMessage));
108   im->header.type = htons (GNUNET_MESSAGE_TYPE_PEERINFO_INFO);
109   im->peer = he->identity;
110   if (he->hello != NULL)
111     memcpy (&im[1], he->hello, hs);
112   return im;
113 }
114
115
116 /**
117  * Address iterator that causes expired entries to be discarded.
118  *
119  * @param cls pointer to the current time
120  * @param address the address
121  * @param expiration expiration time for the address
122  * @return GNUNET_NO if expiration smaller than the current time
123  */
124 static int
125 discard_expired (void *cls, const struct GNUNET_HELLO_Address *address,
126                  struct GNUNET_TIME_Absolute expiration)
127 {
128   const struct GNUNET_TIME_Absolute *now = cls;
129
130   if (now->abs_value > expiration.abs_value)
131   {
132     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
133                 _("Removing expired address of transport `%s'\n"),
134                 address->transport_name);
135     return GNUNET_NO;
136   }
137   return GNUNET_OK;
138 }
139
140
141 /**
142  * Get the filename under which we would store the GNUNET_HELLO_Message
143  * for the given host and protocol.
144  * @return filename of the form DIRECTORY/HOSTID
145  */
146 static char *
147 get_host_filename (const struct GNUNET_PeerIdentity *id)
148 {
149   struct GNUNET_CRYPTO_HashAsciiEncoded fil;
150   char *fn;
151
152   GNUNET_CRYPTO_hash_to_enc (&id->hashPubKey, &fil);
153   GNUNET_asprintf (&fn, "%s%s%s", networkIdDirectory, DIR_SEPARATOR_STR, &fil);
154   return fn;
155 }
156
157
158 /**
159  * Broadcast information about the given entry to all
160  * clients that care.
161  *
162  * @param entry entry to broadcast about
163  */
164 static void
165 notify_all (struct HostEntry *entry)
166 {
167   struct InfoMessage *msg;
168
169   msg = make_info_message (entry);
170   GNUNET_SERVER_notification_context_broadcast (notify_list, &msg->header,
171                                                 GNUNET_NO);
172   GNUNET_free (msg);
173 }
174
175
176 /**
177  * Add a host to the list.
178  *
179  * @param identity the identity of the host
180  */
181 static void
182 add_host_to_known_hosts (const struct GNUNET_PeerIdentity *identity)
183 {
184   struct HostEntry *entry;
185   char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE - 1];
186   const struct GNUNET_HELLO_Message *hello;
187   struct GNUNET_HELLO_Message *hello_clean;
188   int size;
189   struct GNUNET_TIME_Absolute now;
190   char *fn;
191
192   entry = GNUNET_CONTAINER_multihashmap_get (hostmap, &identity->hashPubKey);
193   if (entry != NULL)
194     return;
195   GNUNET_STATISTICS_update (stats, gettext_noop ("# peers known"), 1,
196                             GNUNET_NO);
197   entry = GNUNET_malloc (sizeof (struct HostEntry));
198   entry->identity = *identity;
199
200   fn = get_host_filename (identity);
201   if (GNUNET_DISK_file_test (fn) == GNUNET_YES)
202   {
203     size = GNUNET_DISK_fn_read (fn, buffer, sizeof (buffer));
204     hello = (const struct GNUNET_HELLO_Message *) buffer;
205     if ((size < sizeof (struct GNUNET_MessageHeader)) ||
206         (size != ntohs ((((const struct GNUNET_MessageHeader *) hello)->size)))
207         || (size != GNUNET_HELLO_size (hello)))
208     {
209       GNUNET_break (0);
210       if (0 != UNLINK (fn))
211         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "unlink", fn);
212     }
213     else
214     {
215       now = GNUNET_TIME_absolute_get ();
216       hello_clean =
217           GNUNET_HELLO_iterate_addresses (hello, GNUNET_YES, &discard_expired,
218                                           &now);
219       entry->hello = hello_clean;
220     }
221   }
222   GNUNET_free (fn);
223   GNUNET_CONTAINER_multihashmap_put (hostmap, &identity->hashPubKey, entry,
224                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
225   notify_all (entry);
226 }
227
228
229 /**
230  * Remove a file that should not be there.  LOG
231  * success or failure.
232  */
233 static void
234 remove_garbage (const char *fullname)
235 {
236   if (0 == UNLINK (fullname))
237     GNUNET_log (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK,
238                 _
239                 ("File `%s' in directory `%s' does not match naming convention. "
240                  "Removed.\n"), fullname, networkIdDirectory);
241   else
242     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
243                               "unlink", fullname);
244 }
245
246
247 static int
248 hosts_directory_scan_callback (void *cls, const char *fullname)
249 {
250   unsigned int *matched = cls;
251   struct GNUNET_PeerIdentity identity;
252   const char *filename;
253
254   if (GNUNET_DISK_file_test (fullname) != GNUNET_YES)
255     return GNUNET_OK;           /* ignore non-files */
256   if (strlen (fullname) < sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded))
257   {
258     remove_garbage (fullname);
259     return GNUNET_OK;
260   }
261   filename =
262       &fullname[strlen (fullname) -
263                 sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) + 1];
264   if (filename[-1] != DIR_SEPARATOR)
265   {
266     remove_garbage (fullname);
267     return GNUNET_OK;
268   }
269   if (GNUNET_OK !=
270       GNUNET_CRYPTO_hash_from_string (filename, &identity.hashPubKey))
271   {
272     remove_garbage (fullname);
273     return GNUNET_OK;
274   }
275   (*matched)++;
276   add_host_to_known_hosts (&identity);
277   return GNUNET_OK;
278 }
279
280
281 /**
282  * Call this method periodically to scan data/hosts for new hosts.
283  */
284 static void
285 cron_scan_directory_data_hosts (void *cls,
286                                 const struct GNUNET_SCHEDULER_TaskContext *tc)
287 {
288   static unsigned int retries;
289   unsigned int count;
290
291   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
292     return;
293   count = 0;
294   if (GNUNET_SYSERR == GNUNET_DISK_directory_create (networkIdDirectory))
295   {
296     GNUNET_SCHEDULER_add_delayed_with_priority (DATA_HOST_FREQ,
297                                                 GNUNET_SCHEDULER_PRIORITY_IDLE,
298                                                 &cron_scan_directory_data_hosts, NULL);
299     return;
300   }
301   GNUNET_DISK_directory_scan (networkIdDirectory,
302                               &hosts_directory_scan_callback, &count);
303   if ((0 == count) && (0 == (++retries & 31)))
304     GNUNET_log (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK,
305                 _("Still no peers found in `%s'!\n"), networkIdDirectory);
306   GNUNET_SCHEDULER_add_delayed_with_priority (DATA_HOST_FREQ, 
307                                               GNUNET_SCHEDULER_PRIORITY_IDLE,
308                                               &cron_scan_directory_data_hosts,
309                                               NULL);
310 }
311
312
313 /**
314  * Bind a host address (hello) to a hostId.
315  *
316  * @param peer the peer for which this is a hello
317  * @param hello the verified (!) hello message
318  */
319 static void
320 bind_address (const struct GNUNET_PeerIdentity *peer,
321               const struct GNUNET_HELLO_Message *hello)
322 {
323   char *fn;
324   struct HostEntry *host;
325   struct GNUNET_HELLO_Message *mrg;
326   struct GNUNET_TIME_Absolute delta;
327
328   add_host_to_known_hosts (peer);
329   host = GNUNET_CONTAINER_multihashmap_get (hostmap, &peer->hashPubKey);
330   GNUNET_assert (host != NULL);
331   if (host->hello == NULL)
332   {
333     host->hello = GNUNET_malloc (GNUNET_HELLO_size (hello));
334     memcpy (host->hello, hello, GNUNET_HELLO_size (hello));
335   }
336   else
337   {
338     mrg = GNUNET_HELLO_merge (host->hello, hello);
339     delta = GNUNET_HELLO_equals (mrg, host->hello, GNUNET_TIME_absolute_get ());
340     if (delta.abs_value == GNUNET_TIME_UNIT_FOREVER_ABS.abs_value)
341     {
342       GNUNET_free (mrg);
343       return;
344     }
345     GNUNET_free (host->hello);
346     host->hello = mrg;
347   }
348   fn = get_host_filename (peer);
349   if (GNUNET_OK == GNUNET_DISK_directory_create_for_file (fn))
350   {
351     if (GNUNET_SYSERR ==
352         GNUNET_DISK_fn_write (fn, host->hello, GNUNET_HELLO_size (host->hello),
353                               GNUNET_DISK_PERM_USER_READ |
354                               GNUNET_DISK_PERM_USER_WRITE |
355                               GNUNET_DISK_PERM_GROUP_READ |
356                               GNUNET_DISK_PERM_OTHER_READ))
357       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "write", fn);
358
359   }
360   GNUNET_free (fn);
361   notify_all (host);
362 }
363
364
365
366 /**
367  * Do transmit info about peer to given host.
368  *
369  * @param cls NULL to hit all hosts, otherwise specifies a particular target
370  * @param key hostID
371  * @param value information to transmit
372  * @return GNUNET_YES (continue to iterate)
373  */
374 static int
375 add_to_tc (void *cls, const GNUNET_HashCode * key, void *value)
376 {
377   struct GNUNET_SERVER_TransmitContext *tc = cls;
378   struct HostEntry *pos = value;
379   struct InfoMessage *im;
380   uint16_t hs;
381   char buf[GNUNET_SERVER_MAX_MESSAGE_SIZE - 1];
382
383   hs = 0;
384   im = (struct InfoMessage *) buf;
385   if (pos->hello != NULL)
386   {
387     hs = GNUNET_HELLO_size (pos->hello);
388     GNUNET_assert (hs <
389                    GNUNET_SERVER_MAX_MESSAGE_SIZE -
390                    sizeof (struct InfoMessage));
391     memcpy (&im[1], pos->hello, hs);
392   }
393   im->header.type = htons (GNUNET_MESSAGE_TYPE_PEERINFO_INFO);
394   im->header.size = htons (sizeof (struct InfoMessage) + hs);
395   im->reserved = htonl (0);
396   im->peer = pos->identity;
397   GNUNET_SERVER_transmit_context_append_message (tc, &im->header);
398   return GNUNET_YES;
399 }
400
401
402 /**
403  * @brief delete expired HELLO entries in data/hosts/
404  */
405 static int
406 discard_hosts_helper (void *cls, const char *fn)
407 {
408   struct GNUNET_TIME_Absolute *now = cls;
409   char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE - 1];
410   const struct GNUNET_HELLO_Message *hello;
411   struct GNUNET_HELLO_Message *new_hello;
412   int size;
413
414   size = GNUNET_DISK_fn_read (fn, buffer, sizeof (buffer));
415   if (size < sizeof (struct GNUNET_MessageHeader))
416   {
417     if (0 != UNLINK (fn))
418       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING |
419                                 GNUNET_ERROR_TYPE_BULK, "unlink", fn);
420     return GNUNET_OK;
421   }
422   hello = (const struct GNUNET_HELLO_Message *) buffer;
423   new_hello =
424       GNUNET_HELLO_iterate_addresses (hello, GNUNET_YES, &discard_expired, now);
425   if (new_hello != NULL)
426   {
427     GNUNET_DISK_fn_write (fn, new_hello, GNUNET_HELLO_size (new_hello),
428                           GNUNET_DISK_PERM_USER_READ |
429                           GNUNET_DISK_PERM_USER_WRITE |
430                           GNUNET_DISK_PERM_GROUP_READ |
431                           GNUNET_DISK_PERM_OTHER_READ);
432     GNUNET_free (new_hello);
433   }
434   else
435   {
436     if (0 != UNLINK (fn))
437       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING |
438                                 GNUNET_ERROR_TYPE_BULK, "unlink", fn);
439   }
440   return GNUNET_OK;
441 }
442
443
444 /**
445  * Call this method periodically to scan data/hosts for new hosts.
446  */
447 static void
448 cron_clean_data_hosts (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
449 {
450   struct GNUNET_TIME_Absolute now;
451
452   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
453     return;
454   now = GNUNET_TIME_absolute_get ();
455   GNUNET_DISK_directory_scan (networkIdDirectory, &discard_hosts_helper, &now);
456   GNUNET_SCHEDULER_add_delayed (DATA_HOST_CLEAN_FREQ, &cron_clean_data_hosts,
457                                 NULL);
458 }
459
460
461 /**
462  * Handle HELLO-message.
463  *
464  * @param cls closure
465  * @param client identification of the client
466  * @param message the actual message
467  */
468 static void
469 handle_hello (void *cls, struct GNUNET_SERVER_Client *client,
470               const struct GNUNET_MessageHeader *message)
471 {
472   const struct GNUNET_HELLO_Message *hello;
473   struct GNUNET_PeerIdentity pid;
474
475   hello = (const struct GNUNET_HELLO_Message *) message;
476   if (GNUNET_OK != GNUNET_HELLO_get_id (hello, &pid))
477   {
478     GNUNET_break (0);
479     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
480     return;
481   }
482 #if DEBUG_PEERINFO
483   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "`%s' message received for peer `%4s'\n",
484               "HELLO", GNUNET_i2s (&pid));
485 #endif
486   bind_address (&pid, hello);
487   GNUNET_SERVER_receive_done (client, GNUNET_OK);
488 }
489
490
491 /**
492  * Handle GET-message.
493  *
494  * @param cls closure
495  * @param client identification of the client
496  * @param message the actual message
497  */
498 static void
499 handle_get (void *cls, struct GNUNET_SERVER_Client *client,
500             const struct GNUNET_MessageHeader *message)
501 {
502   const struct ListPeerMessage *lpm;
503   struct GNUNET_SERVER_TransmitContext *tc;
504
505   lpm = (const struct ListPeerMessage *) message;
506   GNUNET_break (0 == ntohl (lpm->reserved));
507 #if DEBUG_PEERINFO
508   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "`%s' message received for peer `%4s'\n",
509               "GET", GNUNET_i2s (&lpm->peer));
510 #endif
511   tc = GNUNET_SERVER_transmit_context_create (client);
512   GNUNET_CONTAINER_multihashmap_get_multiple (hostmap, &lpm->peer.hashPubKey,
513                                               &add_to_tc, tc);
514   GNUNET_SERVER_transmit_context_append_data (tc, NULL, 0,
515                                               GNUNET_MESSAGE_TYPE_PEERINFO_INFO_END);
516   GNUNET_SERVER_transmit_context_run (tc, GNUNET_TIME_UNIT_FOREVER_REL);
517 }
518
519
520 /**
521  * Handle GET-ALL-message.
522  *
523  * @param cls closure
524  * @param client identification of the client
525  * @param message the actual message
526  */
527 static void
528 handle_get_all (void *cls, struct GNUNET_SERVER_Client *client,
529                 const struct GNUNET_MessageHeader *message)
530 {
531   struct GNUNET_SERVER_TransmitContext *tc;
532
533 #if DEBUG_PEERINFO
534   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "`%s' message received\n", "GET_ALL");
535 #endif
536   tc = GNUNET_SERVER_transmit_context_create (client);
537   GNUNET_CONTAINER_multihashmap_iterate (hostmap, &add_to_tc, tc);
538   GNUNET_SERVER_transmit_context_append_data (tc, NULL, 0,
539                                               GNUNET_MESSAGE_TYPE_PEERINFO_INFO_END);
540   GNUNET_SERVER_transmit_context_run (tc, GNUNET_TIME_UNIT_FOREVER_REL);
541 }
542
543
544 static int
545 do_notify_entry (void *cls, const GNUNET_HashCode * key, void *value)
546 {
547   struct GNUNET_SERVER_Client *client = cls;
548   struct HostEntry *he = value;
549   struct InfoMessage *msg;
550
551   msg = make_info_message (he);
552   GNUNET_SERVER_notification_context_unicast (notify_list, client, &msg->header,
553                                               GNUNET_NO);
554   GNUNET_free (msg);
555   return GNUNET_YES;
556 }
557
558
559 /**
560  * Handle NOTIFY-message.
561  *
562  * @param cls closure
563  * @param client identification of the client
564  * @param message the actual message
565  */
566 static void
567 handle_notify (void *cls, struct GNUNET_SERVER_Client *client,
568                const struct GNUNET_MessageHeader *message)
569 {
570 #if DEBUG_PEERINFO
571   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "`%s' message received\n", "NOTIFY");
572 #endif
573   GNUNET_SERVER_notification_context_add (notify_list, client);
574   GNUNET_CONTAINER_multihashmap_iterate (hostmap, &do_notify_entry, client);
575   GNUNET_SERVER_receive_done (client, GNUNET_OK);
576 }
577
578
579 static int
580 free_host_entry (void *cls, const GNUNET_HashCode * key, void *value)
581 {
582   struct HostEntry *he = value;
583
584   GNUNET_free_non_null (he->hello);
585   GNUNET_free (he);
586   return GNUNET_YES;
587 }
588
589 /**
590  * Clean up our state.  Called during shutdown.
591  *
592  * @param cls unused
593  * @param tc scheduler task context, unused
594  */
595 static void
596 shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
597 {
598   GNUNET_SERVER_notification_context_destroy (notify_list);
599   notify_list = NULL;
600   GNUNET_CONTAINER_multihashmap_iterate (hostmap, &free_host_entry, NULL);
601   GNUNET_CONTAINER_multihashmap_destroy (hostmap);
602   if (stats != NULL)
603   {
604     GNUNET_STATISTICS_destroy (stats, GNUNET_NO);
605     stats = NULL;
606   }
607 }
608
609
610 /**
611  * Process statistics requests.
612  *
613  * @param cls closure
614  * @param server the initialized server
615  * @param cfg configuration to use
616  */
617 static void
618 run (void *cls, struct GNUNET_SERVER_Handle *server,
619      const struct GNUNET_CONFIGURATION_Handle *cfg)
620 {
621   static const struct GNUNET_SERVER_MessageHandler handlers[] = {
622     {&handle_hello, NULL, GNUNET_MESSAGE_TYPE_HELLO, 0},
623     {&handle_get, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_GET,
624      sizeof (struct ListPeerMessage)},
625     {&handle_get_all, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_GET_ALL,
626      sizeof (struct GNUNET_MessageHeader)},
627     {&handle_notify, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_NOTIFY,
628      sizeof (struct GNUNET_MessageHeader)},
629     {NULL, NULL, 0, 0}
630   };
631
632   hostmap = GNUNET_CONTAINER_multihashmap_create (1024);
633   stats = GNUNET_STATISTICS_create ("peerinfo", cfg);
634   notify_list = GNUNET_SERVER_notification_context_create (server, 0);
635   GNUNET_assert (GNUNET_OK ==
636                  GNUNET_CONFIGURATION_get_value_filename (cfg, "peerinfo",
637                                                           "HOSTS",
638                                                           &networkIdDirectory));
639   GNUNET_DISK_directory_create (networkIdDirectory);
640   GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_IDLE,
641                                       &cron_scan_directory_data_hosts, NULL);
642   GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_IDLE,
643                                       &cron_clean_data_hosts, NULL);
644   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &shutdown_task,
645                                 NULL);
646   GNUNET_SERVER_add_handlers (server, handlers);
647 }
648
649
650 /**
651  * The main function for the statistics service.
652  *
653  * @param argc number of arguments from the command line
654  * @param argv command line arguments
655  * @return 0 ok, 1 on error
656  */
657 int
658 main (int argc, char *const *argv)
659 {
660   int ret;
661
662   ret =
663       (GNUNET_OK ==
664        GNUNET_SERVICE_run (argc, argv, "peerinfo", GNUNET_SERVICE_OPTION_NONE,
665                            &run, NULL)) ? 0 : 1;
666   GNUNET_free_non_null (networkIdDirectory);
667   return ret;
668 }
669
670
671 /* end of gnunet-service-peerinfo.c */