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