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