indentation
[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 tname name of the transport
121  * @param expiration expiration time for the address
122  * @param addr the address
123  * @param addrlen length of addr in bytes
124  * @return GNUNET_NO if expiration smaller than the current time
125  */
126 static int
127 discard_expired (void *cls,
128                  const char *tname,
129                  struct GNUNET_TIME_Absolute expiration,
130                  const void *addr, uint16_t addrlen)
131 {
132   const struct GNUNET_TIME_Absolute *now = cls;
133
134   if (now->abs_value > expiration.abs_value)
135   {
136     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
137                 _("Removing expired address of transport `%s'\n"), tname);
138     return GNUNET_NO;
139   }
140   return GNUNET_OK;
141 }
142
143
144 /**
145  * Get the filename under which we would store the GNUNET_HELLO_Message
146  * for the given host and protocol.
147  * @return filename of the form DIRECTORY/HOSTID
148  */
149 static char *
150 get_host_filename (const struct GNUNET_PeerIdentity *id)
151 {
152   struct GNUNET_CRYPTO_HashAsciiEncoded fil;
153   char *fn;
154
155   GNUNET_CRYPTO_hash_to_enc (&id->hashPubKey, &fil);
156   GNUNET_asprintf (&fn, "%s%s%s", networkIdDirectory, DIR_SEPARATOR_STR, &fil);
157   return fn;
158 }
159
160
161 /**
162  * Broadcast information about the given entry to all 
163  * clients that care.
164  *
165  * @param entry entry to broadcast about
166  */
167 static void
168 notify_all (struct HostEntry *entry)
169 {
170   struct InfoMessage *msg;
171
172   msg = make_info_message (entry);
173   GNUNET_SERVER_notification_context_broadcast (notify_list,
174                                                 &msg->header, GNUNET_NO);
175   GNUNET_free (msg);
176 }
177
178
179 /**
180  * Add a host to the list.
181  *
182  * @param identity the identity of the host
183  */
184 static void
185 add_host_to_known_hosts (const struct GNUNET_PeerIdentity *identity)
186 {
187   struct HostEntry *entry;
188   char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE - 1];
189   const struct GNUNET_HELLO_Message *hello;
190   struct GNUNET_HELLO_Message *hello_clean;
191   int size;
192   struct GNUNET_TIME_Absolute now;
193   char *fn;
194
195   entry = GNUNET_CONTAINER_multihashmap_get (hostmap, &identity->hashPubKey);
196   if (entry != NULL)
197     return;
198   GNUNET_STATISTICS_update (stats,
199                             gettext_noop ("# peers known"), 1, GNUNET_NO);
200   entry = GNUNET_malloc (sizeof (struct HostEntry));
201   entry->identity = *identity;
202
203   fn = get_host_filename (identity);
204   if (GNUNET_DISK_file_test (fn) == GNUNET_YES)
205   {
206     size = GNUNET_DISK_fn_read (fn, buffer, sizeof (buffer));
207     hello = (const struct GNUNET_HELLO_Message *) buffer;
208     if ((size < sizeof (struct GNUNET_MessageHeader)) ||
209         (size != ntohs ((((const struct GNUNET_MessageHeader *) hello)->size)))
210         || (size != GNUNET_HELLO_size (hello)))
211     {
212       GNUNET_break (0);
213       if (0 != UNLINK (fn))
214         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "unlink", fn);
215     }
216     else
217     {
218       now = GNUNET_TIME_absolute_get ();
219       hello_clean = GNUNET_HELLO_iterate_addresses (hello,
220                                                     GNUNET_YES,
221                                                     &discard_expired, &now);
222       entry->hello = hello_clean;
223     }
224   }
225   GNUNET_free (fn);
226   GNUNET_CONTAINER_multihashmap_put (hostmap,
227                                      &identity->hashPubKey,
228                                      entry,
229                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
230   notify_all (entry);
231 }
232
233
234 /**
235  * Remove a file that should not be there.  LOG
236  * success or failure.
237  */
238 static void
239 remove_garbage (const char *fullname)
240 {
241   if (0 == UNLINK (fullname))
242     GNUNET_log (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK,
243                 _
244                 ("File `%s' in directory `%s' does not match naming convention. "
245                  "Removed.\n"), fullname, networkIdDirectory);
246   else
247     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR |
248                               GNUNET_ERROR_TYPE_BULK, "unlink", fullname);
249 }
250
251
252 static int
253 hosts_directory_scan_callback (void *cls, const char *fullname)
254 {
255   unsigned int *matched = cls;
256   struct GNUNET_PeerIdentity identity;
257   const char *filename;
258
259   if (GNUNET_DISK_file_test (fullname) != GNUNET_YES)
260     return GNUNET_OK;           /* ignore non-files */
261   if (strlen (fullname) < sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded))
262   {
263     remove_garbage (fullname);
264     return GNUNET_OK;
265   }
266   filename =
267       &fullname[strlen (fullname) -
268                 sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) + 1];
269   if (filename[-1] != DIR_SEPARATOR)
270   {
271     remove_garbage (fullname);
272     return GNUNET_OK;
273   }
274   if (GNUNET_OK != GNUNET_CRYPTO_hash_from_string (filename,
275                                                    &identity.hashPubKey))
276   {
277     remove_garbage (fullname);
278     return GNUNET_OK;
279   }
280   (*matched)++;
281   add_host_to_known_hosts (&identity);
282   return GNUNET_OK;
283 }
284
285
286 /**
287  * Call this method periodically to scan data/hosts for new hosts.
288  */
289 static void
290 cron_scan_directory_data_hosts (void *cls,
291                                 const struct GNUNET_SCHEDULER_TaskContext *tc)
292 {
293   static unsigned int retries;
294   unsigned int count;
295
296   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
297     return;
298   count = 0;
299   if (GNUNET_SYSERR == GNUNET_DISK_directory_create (networkIdDirectory))
300   {
301     GNUNET_SCHEDULER_add_delayed (DATA_HOST_FREQ,
302                                   &cron_scan_directory_data_hosts, NULL);
303     return;
304   }
305   GNUNET_DISK_directory_scan (networkIdDirectory,
306                               &hosts_directory_scan_callback, &count);
307   if ((0 == count) && (0 == (++retries & 31)))
308     GNUNET_log (GNUNET_ERROR_TYPE_WARNING |
309                 GNUNET_ERROR_TYPE_BULK,
310                 _("Still no peers found in `%s'!\n"), networkIdDirectory);
311   GNUNET_SCHEDULER_add_delayed (DATA_HOST_FREQ,
312                                 &cron_scan_directory_data_hosts, NULL);
313 }
314
315
316 /**
317  * Bind a host address (hello) to a hostId.
318  *
319  * @param peer the peer for which this is a hello
320  * @param hello the verified (!) hello message
321  */
322 static void
323 bind_address (const struct GNUNET_PeerIdentity *peer,
324               const struct GNUNET_HELLO_Message *hello)
325 {
326   char *fn;
327   struct HostEntry *host;
328   struct GNUNET_HELLO_Message *mrg;
329   struct GNUNET_TIME_Absolute delta;
330
331   add_host_to_known_hosts (peer);
332   host = GNUNET_CONTAINER_multihashmap_get (hostmap, &peer->hashPubKey);
333   GNUNET_assert (host != NULL);
334   if (host->hello == NULL)
335   {
336     host->hello = GNUNET_malloc (GNUNET_HELLO_size (hello));
337     memcpy (host->hello, hello, GNUNET_HELLO_size (hello));
338   }
339   else
340   {
341     mrg = GNUNET_HELLO_merge (host->hello, hello);
342     delta = GNUNET_HELLO_equals (mrg, host->hello, GNUNET_TIME_absolute_get ());
343     if (delta.abs_value == GNUNET_TIME_UNIT_FOREVER_ABS.abs_value)
344     {
345       GNUNET_free (mrg);
346       return;
347     }
348     GNUNET_free (host->hello);
349     host->hello = mrg;
350   }
351   fn = get_host_filename (peer);
352   if (GNUNET_OK == GNUNET_DISK_directory_create_for_file (fn))
353   {
354     if (GNUNET_SYSERR ==
355         GNUNET_DISK_fn_write (fn,
356                               host->hello,
357                               GNUNET_HELLO_size (host->hello),
358                               GNUNET_DISK_PERM_USER_READ |
359                               GNUNET_DISK_PERM_USER_WRITE |
360                               GNUNET_DISK_PERM_GROUP_READ |
361                               GNUNET_DISK_PERM_OTHER_READ))
362       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "write", fn);
363
364   }
365   GNUNET_free (fn);
366   notify_all (host);
367 }
368
369
370
371 /**
372  * Do transmit info about peer to given host.
373  *
374  * @param cls NULL to hit all hosts, otherwise specifies a particular target
375  * @param key hostID
376  * @param value information to transmit
377  * @return GNUNET_YES (continue to iterate)
378  */
379 static int
380 add_to_tc (void *cls, const GNUNET_HashCode * key, void *value)
381 {
382   struct GNUNET_SERVER_TransmitContext *tc = cls;
383   struct HostEntry *pos = value;
384   struct InfoMessage *im;
385   uint16_t hs;
386   char buf[GNUNET_SERVER_MAX_MESSAGE_SIZE - 1];
387
388   hs = 0;
389   im = (struct InfoMessage *) buf;
390   if (pos->hello != NULL)
391   {
392     hs = GNUNET_HELLO_size (pos->hello);
393     GNUNET_assert (hs <
394                    GNUNET_SERVER_MAX_MESSAGE_SIZE -
395                    sizeof (struct InfoMessage));
396     memcpy (&im[1], pos->hello, hs);
397   }
398   im->header.type = htons (GNUNET_MESSAGE_TYPE_PEERINFO_INFO);
399   im->header.size = htons (sizeof (struct InfoMessage) + hs);
400   im->reserved = htonl (0);
401   im->peer = pos->identity;
402   GNUNET_SERVER_transmit_context_append_message (tc, &im->header);
403   return GNUNET_YES;
404 }
405
406
407 /**
408  * @brief delete expired HELLO entries in data/hosts/
409  */
410 static int
411 discard_hosts_helper (void *cls, const char *fn)
412 {
413   struct GNUNET_TIME_Absolute *now = cls;
414   char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE - 1];
415   const struct GNUNET_HELLO_Message *hello;
416   struct GNUNET_HELLO_Message *new_hello;
417   int size;
418
419   size = GNUNET_DISK_fn_read (fn, buffer, sizeof (buffer));
420   if (size < sizeof (struct GNUNET_MessageHeader))
421   {
422     if (0 != UNLINK (fn))
423       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING |
424                                 GNUNET_ERROR_TYPE_BULK, "unlink", fn);
425     return GNUNET_OK;
426   }
427   hello = (const struct GNUNET_HELLO_Message *) buffer;
428   new_hello = GNUNET_HELLO_iterate_addresses (hello,
429                                               GNUNET_YES,
430                                               &discard_expired, now);
431   if (new_hello != NULL)
432   {
433     GNUNET_DISK_fn_write (fn,
434                           new_hello,
435                           GNUNET_HELLO_size (new_hello),
436                           GNUNET_DISK_PERM_USER_READ |
437                           GNUNET_DISK_PERM_USER_WRITE |
438                           GNUNET_DISK_PERM_GROUP_READ |
439                           GNUNET_DISK_PERM_OTHER_READ);
440     GNUNET_free (new_hello);
441   }
442   else
443   {
444     if (0 != UNLINK (fn))
445       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING |
446                                 GNUNET_ERROR_TYPE_BULK, "unlink", fn);
447   }
448   return GNUNET_OK;
449 }
450
451
452 /**
453  * Call this method periodically to scan data/hosts for new hosts.
454  */
455 static void
456 cron_clean_data_hosts (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
457 {
458   struct GNUNET_TIME_Absolute now;
459
460   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
461     return;
462   now = GNUNET_TIME_absolute_get ();
463   GNUNET_DISK_directory_scan (networkIdDirectory, &discard_hosts_helper, &now);
464   GNUNET_SCHEDULER_add_delayed (DATA_HOST_CLEAN_FREQ,
465                                 &cron_clean_data_hosts, NULL);
466 }
467
468
469 /**
470  * Handle HELLO-message.
471  *
472  * @param cls closure
473  * @param client identification of the client
474  * @param message the actual message
475  */
476 static void
477 handle_hello (void *cls,
478               struct GNUNET_SERVER_Client *client,
479               const struct GNUNET_MessageHeader *message)
480 {
481   const struct GNUNET_HELLO_Message *hello;
482   struct GNUNET_PeerIdentity pid;
483
484   hello = (const struct GNUNET_HELLO_Message *) message;
485   if (GNUNET_OK != GNUNET_HELLO_get_id (hello, &pid))
486   {
487     GNUNET_break (0);
488     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
489     return;
490   }
491 #if DEBUG_PEERINFO
492   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
493               "`%s' message received for peer `%4s'\n",
494               "HELLO", GNUNET_i2s (&pid));
495 #endif
496   bind_address (&pid, hello);
497   GNUNET_SERVER_receive_done (client, GNUNET_OK);
498 }
499
500
501 /**
502  * Handle GET-message.
503  *
504  * @param cls closure
505  * @param client identification of the client
506  * @param message the actual message
507  */
508 static void
509 handle_get (void *cls,
510             struct GNUNET_SERVER_Client *client,
511             const struct GNUNET_MessageHeader *message)
512 {
513   const struct ListPeerMessage *lpm;
514   struct GNUNET_SERVER_TransmitContext *tc;
515
516   lpm = (const struct ListPeerMessage *) message;
517   GNUNET_break (0 == ntohl (lpm->reserved));
518 #if DEBUG_PEERINFO
519   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
520               "`%s' message received for peer `%4s'\n",
521               "GET", GNUNET_i2s (&lpm->peer));
522 #endif
523   tc = GNUNET_SERVER_transmit_context_create (client);
524   GNUNET_CONTAINER_multihashmap_get_multiple (hostmap,
525                                               &lpm->peer.hashPubKey,
526                                               &add_to_tc, tc);
527   GNUNET_SERVER_transmit_context_append_data (tc, NULL, 0,
528                                               GNUNET_MESSAGE_TYPE_PEERINFO_INFO_END);
529   GNUNET_SERVER_transmit_context_run (tc, GNUNET_TIME_UNIT_FOREVER_REL);
530 }
531
532
533 /**
534  * Handle GET-ALL-message.
535  *
536  * @param cls closure
537  * @param client identification of the client
538  * @param message the actual message
539  */
540 static void
541 handle_get_all (void *cls,
542                 struct GNUNET_SERVER_Client *client,
543                 const struct GNUNET_MessageHeader *message)
544 {
545   struct GNUNET_SERVER_TransmitContext *tc;
546
547 #if DEBUG_PEERINFO
548   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "`%s' message received\n", "GET_ALL");
549 #endif
550   tc = GNUNET_SERVER_transmit_context_create (client);
551   GNUNET_CONTAINER_multihashmap_iterate (hostmap, &add_to_tc, tc);
552   GNUNET_SERVER_transmit_context_append_data (tc, NULL, 0,
553                                               GNUNET_MESSAGE_TYPE_PEERINFO_INFO_END);
554   GNUNET_SERVER_transmit_context_run (tc, GNUNET_TIME_UNIT_FOREVER_REL);
555 }
556
557
558 static int
559 do_notify_entry (void *cls, const GNUNET_HashCode * key, void *value)
560 {
561   struct GNUNET_SERVER_Client *client = cls;
562   struct HostEntry *he = value;
563   struct InfoMessage *msg;
564
565   msg = make_info_message (he);
566   GNUNET_SERVER_notification_context_unicast (notify_list,
567                                               client, &msg->header, GNUNET_NO);
568   GNUNET_free (msg);
569   return GNUNET_YES;
570 }
571
572
573 /**
574  * Handle NOTIFY-message.
575  *
576  * @param cls closure
577  * @param client identification of the client
578  * @param message the actual message
579  */
580 static void
581 handle_notify (void *cls,
582                struct GNUNET_SERVER_Client *client,
583                const struct GNUNET_MessageHeader *message)
584 {
585 #if DEBUG_PEERINFO
586   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "`%s' message received\n", "NOTIFY");
587 #endif
588   GNUNET_SERVER_notification_context_add (notify_list, client);
589   GNUNET_CONTAINER_multihashmap_iterate (hostmap, &do_notify_entry, client);
590   GNUNET_SERVER_receive_done (client, GNUNET_OK);
591 }
592
593
594 static int
595 free_host_entry (void *cls, const GNUNET_HashCode * key, void *value)
596 {
597   struct HostEntry *he = value;
598
599   GNUNET_free_non_null (he->hello);
600   GNUNET_free (he);
601   return GNUNET_YES;
602 }
603
604 /**
605  * Clean up our state.  Called during shutdown.
606  *
607  * @param cls unused
608  * @param tc scheduler task context, unused
609  */
610 static void
611 shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
612 {
613   GNUNET_SERVER_notification_context_destroy (notify_list);
614   notify_list = NULL;
615   GNUNET_CONTAINER_multihashmap_iterate (hostmap, &free_host_entry, NULL);
616   GNUNET_CONTAINER_multihashmap_destroy (hostmap);
617   if (stats != NULL)
618   {
619     GNUNET_STATISTICS_destroy (stats, GNUNET_NO);
620     stats = NULL;
621   }
622 }
623
624
625 /**
626  * Process statistics requests.
627  *
628  * @param cls closure
629  * @param server the initialized server
630  * @param cfg configuration to use
631  */
632 static void
633 run (void *cls,
634      struct GNUNET_SERVER_Handle *server,
635      const struct GNUNET_CONFIGURATION_Handle *cfg)
636 {
637   static const struct GNUNET_SERVER_MessageHandler handlers[] = {
638     {&handle_hello, NULL, GNUNET_MESSAGE_TYPE_HELLO, 0},
639     {&handle_get, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_GET,
640      sizeof (struct ListPeerMessage)},
641     {&handle_get_all, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_GET_ALL,
642      sizeof (struct GNUNET_MessageHeader)},
643     {&handle_notify, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_NOTIFY,
644      sizeof (struct GNUNET_MessageHeader)},
645     {NULL, NULL, 0, 0}
646   };
647
648   hostmap = GNUNET_CONTAINER_multihashmap_create (1024);
649   stats = GNUNET_STATISTICS_create ("peerinfo", cfg);
650   notify_list = GNUNET_SERVER_notification_context_create (server, 0);
651   GNUNET_assert (GNUNET_OK ==
652                  GNUNET_CONFIGURATION_get_value_filename (cfg,
653                                                           "peerinfo",
654                                                           "HOSTS",
655                                                           &networkIdDirectory));
656   GNUNET_DISK_directory_create (networkIdDirectory);
657   GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_IDLE,
658                                       &cron_scan_directory_data_hosts, NULL);
659   GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_IDLE,
660                                       &cron_clean_data_hosts, NULL);
661   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
662                                 &shutdown_task, NULL);
663   GNUNET_SERVER_add_handlers (server, handlers);
664 }
665
666
667 /**
668  * The main function for the statistics service.
669  *
670  * @param argc number of arguments from the command line
671  * @param argv command line arguments
672  * @return 0 ok, 1 on error
673  */
674 int
675 main (int argc, char *const *argv)
676 {
677   int ret;
678
679   ret = (GNUNET_OK ==
680          GNUNET_SERVICE_run (argc,
681                              argv,
682                              "peerinfo",
683                              GNUNET_SERVICE_OPTION_NONE, &run, NULL)) ? 0 : 1;
684   GNUNET_free_non_null (networkIdDirectory);
685   return ret;
686 }
687
688
689 /* end of gnunet-service-peerinfo.c */