fix
[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 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 2, 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/ and data/credit/).
27  *
28  * @author Christian Grothoff
29  */
30
31 #include "platform.h"
32 #include "gnunet_crypto_lib.h"
33 #include "gnunet_disk_lib.h"
34 #include "gnunet_hello_lib.h"
35 #include "gnunet_protocols.h"
36 #include "gnunet_service_lib.h"
37 #include "peerinfo.h"
38
39 /**
40  * How often do we scan the HOST_DIR for new entries?
41  */
42 #define DATA_HOST_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
43
44 /**
45  * How often do we flush trust values to disk?
46  */
47 #define TRUST_FLUSH_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 5)
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    * This is a linked list.
62    */
63   struct HostEntry *next;
64
65   /**
66    * Identity of the peer.
67    */
68   struct GNUNET_PeerIdentity identity;
69
70   /**
71    * Hello for the peer (can be NULL)
72    */
73   struct GNUNET_HELLO_Message *hello;
74
75   /**
76    * Trust rating for this peer
77    */
78   uint32_t trust;
79
80   /**
81    * Trust rating for this peer on disk.
82    */
83   uint32_t disk_trust;
84
85 };
86
87 /**
88  * The in-memory list of known hosts.
89  */
90 static struct HostEntry *hosts;
91
92 /**
93  * Directory where the hellos are stored in (data/hosts)
94  */
95 static char *networkIdDirectory;
96
97 /**
98  * Where do we store trust information?
99  */
100 static char *trustDirectory;
101
102
103 /**
104  * Address iterator that causes expired entries to be discarded.
105  *
106  * @param cls pointer to the current time
107  * @return GNUNET_NO if expiration smaller than the current time
108  */
109 static int
110 discard_expired (void *cls,
111                  const char *tname,
112                  struct GNUNET_TIME_Absolute expiration,
113                  const void *addr, size_t addrlen)
114 {
115   const struct GNUNET_TIME_Absolute *now = cls;
116   if (now->value > expiration.value)
117     return GNUNET_NO;
118   return GNUNET_OK;
119 }
120
121
122 /**
123  * Get the filename under which we would store the GNUNET_HELLO_Message
124  * for the given host and protocol.
125  * @return filename of the form DIRECTORY/HOSTID
126  */
127 static char *
128 get_host_filename (const struct GNUNET_PeerIdentity *id)
129 {
130   struct GNUNET_CRYPTO_HashAsciiEncoded fil;
131   char *fn;
132
133   GNUNET_CRYPTO_hash_to_enc (&id->hashPubKey, &fil);
134   GNUNET_asprintf (&fn,
135                    "%s%s%s", networkIdDirectory, DIR_SEPARATOR_STR, &fil);
136   return fn;
137 }
138
139
140 /**
141  * Get the filename under which we would store the GNUNET_HELLO_Message
142  * for the given host and protocol.
143  * @return filename of the form DIRECTORY/HOSTID
144  */
145 static char *
146 get_trust_filename (const struct GNUNET_PeerIdentity *id)
147 {
148   struct GNUNET_CRYPTO_HashAsciiEncoded fil;
149   char *fn;
150
151   GNUNET_CRYPTO_hash_to_enc (&id->hashPubKey, &fil);
152   GNUNET_asprintf (&fn, "%s%s%s", trustDirectory, DIR_SEPARATOR_STR, &fil);
153   return fn;
154 }
155
156 /**
157  * Find the host entry for the given peer.  Call
158  * only when synchronized!
159  * @return NULL if not found
160  */
161 static struct HostEntry *
162 lookup_host_entry (const struct GNUNET_PeerIdentity *id)
163 {
164   struct HostEntry *pos;
165
166   pos = hosts;
167   while ((pos != NULL) &&
168          (0 !=
169           memcmp (id, &pos->identity, sizeof (struct GNUNET_PeerIdentity))))
170     pos = pos->next;
171   return pos;
172 }
173
174
175 /**
176  * Add a host to the list.
177  *
178  * @param identity the identity of the host
179  * @param protocol the protocol for the host
180  */
181 static void
182 add_host_to_known_hosts (const struct GNUNET_PeerIdentity *identity)
183 {
184   struct HostEntry *entry;
185   char *fn;
186   uint32_t trust;
187   char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE];
188   const struct GNUNET_HELLO_Message *hello;
189   struct GNUNET_HELLO_Message *hello_clean;
190   int size;
191   struct GNUNET_TIME_Absolute now;
192
193   entry = lookup_host_entry (identity);
194   if (entry != NULL)
195     return;
196   entry = GNUNET_malloc (sizeof (struct HostEntry));
197   entry->identity = *identity;
198   fn = get_trust_filename (identity);
199   if ((GNUNET_DISK_file_test (fn) == GNUNET_YES) &&
200       (sizeof (trust) == GNUNET_DISK_fn_read (fn, &trust, sizeof (trust))))
201     entry->disk_trust = entry->trust = ntohl (trust);
202   GNUNET_free (fn);
203
204   fn = get_host_filename (identity);
205   if (GNUNET_DISK_file_test (fn) == GNUNET_YES)
206     {
207       size = GNUNET_DISK_fn_read (fn, buffer, sizeof (buffer));
208       hello = (const struct GNUNET_HELLO_Message *) buffer;
209       now = GNUNET_TIME_absolute_get ();
210       hello_clean = GNUNET_HELLO_iterate_addresses (hello,
211                                                     GNUNET_YES,
212                                                     &discard_expired, &now);
213       entry->hello = hello_clean;
214     }
215   GNUNET_free (fn);
216   entry->next = hosts;
217   hosts = entry;
218 }
219
220
221 /**
222  * Increase the host credit by a value.
223  *
224  * @param hostId is the identity of the host
225  * @param value is the int value by which the
226  *  host credit is to be increased or decreased
227  * @returns the actual change in trust (positive or negative)
228  */
229 static int
230 change_host_trust (const struct GNUNET_PeerIdentity *hostId, int value)
231 {
232   struct HostEntry *host;
233
234   if (value == 0)
235     return 0;
236   host = lookup_host_entry (hostId);
237   if (host == NULL)
238     {
239       add_host_to_known_hosts (hostId);
240       host = lookup_host_entry (hostId);
241     }
242   GNUNET_assert (host != NULL);
243   if (value > 0)
244     {
245       if (host->trust + value < host->trust)
246         {
247           value = ((uint32_t) - 1) - host->trust;
248           host->trust = (uint32_t) - 1; /* maximized */
249         }
250       else
251         host->trust += value;
252     }
253   else
254     {
255       if (host->trust < -value)
256         {
257           value = -host->trust;
258           host->trust = 0;
259         }
260       else
261         host->trust += value;
262     }
263   return value;
264 }
265
266
267 /**
268  * Remove a file that should not be there.  LOG
269  * success or failure.
270  */
271 static void
272 remove_garbage (const char *fullname)
273 {
274   if (0 == UNLINK (fullname))
275     GNUNET_log (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK,
276                 _
277                 ("File `%s' in directory `%s' does not match naming convention. "
278                  "Removed.\n"), fullname, networkIdDirectory);
279   else
280     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR |
281                               GNUNET_ERROR_TYPE_BULK, "unlink", fullname);
282 }
283
284
285 static int
286 hosts_directory_scan_callback (void *cls, const char *fullname)
287 {
288   unsigned int *matched = cls;
289   struct GNUNET_PeerIdentity identity;
290   const char *filename;
291
292   if (GNUNET_DISK_file_test (fullname) != GNUNET_YES)
293     return GNUNET_OK;           /* ignore non-files */
294   if (strlen (fullname) < sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded))
295     {
296       remove_garbage (fullname);
297       return GNUNET_OK;
298     }
299   filename =
300     &fullname[strlen (fullname) -
301               sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) + 1];
302   if (filename[-1] != DIR_SEPARATOR)
303     {
304       remove_garbage (fullname);
305       return GNUNET_OK;
306     }
307   if (GNUNET_OK != GNUNET_CRYPTO_hash_from_string (filename,
308                                                    &identity.hashPubKey))
309     {
310       remove_garbage (fullname);
311       return GNUNET_OK;
312     }
313   (*matched)++;
314   add_host_to_known_hosts (&identity);
315   return GNUNET_OK;
316 }
317
318
319 /**
320  * Call this method periodically to scan data/hosts for new hosts.
321  */
322 static void
323 cron_scan_directory_data_hosts (void *cls,
324                                 const struct GNUNET_SCHEDULER_TaskContext *tc)
325 {
326   static unsigned int retries;
327   unsigned int count;
328
329   count = 0;
330   GNUNET_DISK_directory_scan (networkIdDirectory,
331                               &hosts_directory_scan_callback, &count);
332   if ((0 == count) && (0 == (++retries & 31)))
333     GNUNET_log (GNUNET_ERROR_TYPE_WARNING |
334                 GNUNET_ERROR_TYPE_BULK,
335                 _("Still no peers found in `%s'!\n"), networkIdDirectory);
336   GNUNET_SCHEDULER_add_delayed (tc->sched,
337                                 GNUNET_NO,
338                                 GNUNET_SCHEDULER_PRIORITY_KEEP,
339                                 GNUNET_SCHEDULER_NO_PREREQUISITE_TASK,
340                                 DATA_HOST_FREQ,
341                                 &cron_scan_directory_data_hosts, NULL);
342 }
343
344
345 /**
346  * Bind a host address (hello) to a hostId.
347  *
348  * @param peer the peer for which this is a hello
349  * @param hello the verified (!) hello message
350  */
351 static void
352 bind_address (const struct GNUNET_PeerIdentity *peer,
353               const struct GNUNET_HELLO_Message *hello)
354 {
355   char *fn;
356   struct HostEntry *host;
357   struct GNUNET_HELLO_Message *mrg;
358
359   add_host_to_known_hosts (peer);
360   host = lookup_host_entry (peer);
361   GNUNET_assert (host != NULL);
362   if (host->hello == NULL)
363     {
364       host->hello = GNUNET_malloc (GNUNET_HELLO_size (hello));
365       memcpy (host->hello, hello, GNUNET_HELLO_size (hello));
366     }
367   else
368     {
369       mrg = GNUNET_HELLO_merge (host->hello, hello);
370       GNUNET_free (host->hello);
371       host->hello = mrg;
372     }
373   fn = get_host_filename (peer);
374   GNUNET_DISK_fn_write (fn, 
375                         host->hello, 
376                         GNUNET_HELLO_size (host->hello),
377                         GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE
378                         | GNUNET_DISK_PERM_GROUP_READ | GNUNET_DISK_PERM_OTHER_READ);
379   GNUNET_free (fn);
380 }
381
382
383 /**
384  * Do transmit info either for only the host matching the given
385  * argument or for all known hosts and change their trust values by
386  * the given delta.
387  *
388  * @param only NULL to hit all hosts
389  */
390 static void
391 send_to_each_host (const struct GNUNET_PeerIdentity *only,
392                    int trust_change, struct GNUNET_SERVER_Client *client)
393 {
394   struct HostEntry *pos;
395   struct InfoMessage *im;
396   const struct GNUNET_MessageHeader *end;
397   uint16_t hs;
398   char buf[GNUNET_SERVER_MAX_MESSAGE_SIZE];
399   struct GNUNET_SERVER_TransmitContext *tc;
400
401   tc = GNUNET_SERVER_transmit_context_create (client);
402   pos = hosts;
403   while (pos != NULL)
404     {
405       if ((only == NULL) ||
406           (0 ==
407            memcmp (only, &pos->identity,
408                    sizeof (struct GNUNET_PeerIdentity))))
409         {
410           change_host_trust (&pos->identity, trust_change);
411           hs = 0;
412           im = (struct InfoMessage *) buf;
413           if (pos->hello != NULL)
414             {
415               hs = GNUNET_HELLO_size (pos->hello);
416               GNUNET_assert (hs <
417                              GNUNET_SERVER_MAX_MESSAGE_SIZE -
418                              sizeof (struct InfoMessage));
419               memcpy (&im[1], pos->hello, hs);
420             }
421           im->trust = htonl (pos->trust);
422           im->peer = pos->identity;
423           end = &im->header;
424           GNUNET_SERVER_transmit_context_append (tc,
425                                                  &end[1],
426                                                  hs +
427                                                  sizeof (struct InfoMessage) -
428                                                  sizeof (struct
429                                                          GNUNET_MessageHeader),
430                                                  GNUNET_MESSAGE_TYPE_PEERINFO_INFO);
431         }
432       pos = pos->next;
433     }
434   GNUNET_SERVER_transmit_context_append (tc, NULL, 0,
435                                          GNUNET_MESSAGE_TYPE_PEERINFO_INFO_END);
436   GNUNET_SERVER_transmit_context_run (tc, GNUNET_TIME_UNIT_FOREVER_REL);
437 }
438
439
440 /**
441  * Write host-trust information to a file - flush the buffer entry!
442  * Assumes synchronized access.
443  */
444 static void
445 flush_trust (struct HostEntry *host)
446 {
447   char *fn;
448   uint32_t trust;
449
450   if (host->trust == host->disk_trust)
451     return;                     /* unchanged */
452   fn = get_trust_filename (&host->identity);
453   if (host->trust == 0)
454     {
455       if ((0 != UNLINK (fn)) && (errno != ENOENT))
456         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING |
457                                   GNUNET_ERROR_TYPE_BULK, "unlink", fn);
458     }
459   else
460     {
461       trust = htonl (host->trust);
462       if (GNUNET_OK == GNUNET_DISK_fn_write (fn, &trust, sizeof(uint32_t),
463           GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE
464               | GNUNET_DISK_PERM_GROUP_READ | GNUNET_DISK_PERM_OTHER_READ))
465         host->disk_trust = host->trust;
466     }
467   GNUNET_free (fn);
468 }
469
470 /**
471  * Call this method periodically to scan data/hosts for new hosts.
472  */
473 static void
474 cron_flush_trust (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
475 {
476   struct HostEntry *pos;
477
478   pos = hosts;
479   while (pos != NULL)
480     {
481       flush_trust (pos);
482       pos = pos->next;
483     }
484   if (0 == (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
485     GNUNET_SCHEDULER_add_delayed (tc->sched,
486                                   GNUNET_YES,
487                                   GNUNET_SCHEDULER_PRIORITY_KEEP,
488                                   GNUNET_SCHEDULER_NO_PREREQUISITE_TASK,
489                                   TRUST_FLUSH_FREQ, &cron_flush_trust, NULL);
490 }
491
492
493 /**
494  * @brief delete expired HELLO entries in data/hosts/
495  */
496 static int
497 discard_hosts_helper (void *cls, const char *fn)
498 {
499   struct GNUNET_TIME_Absolute *now = cls;
500   char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE];
501   const struct GNUNET_HELLO_Message *hello;
502   struct GNUNET_HELLO_Message *new_hello;
503   int size;
504
505   size = GNUNET_DISK_fn_read (fn, buffer, sizeof (buffer));
506   if ((size < sizeof (struct GNUNET_MessageHeader)) && (0 != UNLINK (fn)))
507     {
508       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING |
509                                 GNUNET_ERROR_TYPE_BULK, "unlink", fn);
510       return GNUNET_OK;
511     }
512   hello = (const struct GNUNET_HELLO_Message *) buffer;
513   new_hello = GNUNET_HELLO_iterate_addresses (hello,
514                                               GNUNET_YES,
515                                               &discard_expired, now);
516   if ((new_hello == NULL) && (0 != UNLINK (fn)))
517     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING |
518                               GNUNET_ERROR_TYPE_BULK, "unlink", fn);
519   if (new_hello != NULL)
520     {
521       GNUNET_DISK_fn_write (fn, 
522                             new_hello,
523                             GNUNET_HELLO_size (new_hello),
524                             GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE
525                             | GNUNET_DISK_PERM_GROUP_READ | GNUNET_DISK_PERM_OTHER_READ);
526       GNUNET_free (new_hello);
527     }
528   return GNUNET_OK;
529 }
530
531
532 /**
533  * Call this method periodically to scan data/hosts for new hosts.
534  */
535 static void
536 cron_clean_data_hosts (void *cls,
537                        const struct GNUNET_SCHEDULER_TaskContext *tc)
538 {
539   struct GNUNET_TIME_Absolute now;
540
541   now = GNUNET_TIME_absolute_get ();
542   GNUNET_DISK_directory_scan (networkIdDirectory,
543                               &discard_hosts_helper, &now);
544
545   GNUNET_SCHEDULER_add_delayed (tc->sched,
546                                 GNUNET_NO,
547                                 GNUNET_SCHEDULER_PRIORITY_KEEP,
548                                 GNUNET_SCHEDULER_NO_PREREQUISITE_TASK,
549                                 DATA_HOST_CLEAN_FREQ,
550                                 &cron_clean_data_hosts, NULL);
551 }
552
553
554 /**
555  * Handle ADD-message.
556  *
557  * @param cls closure
558  * @param client identification of the client
559  * @param message the actual message
560  */
561 static void
562 handle_add (void *cls,
563             struct GNUNET_SERVER_Client *client,
564             const struct GNUNET_MessageHeader *message)
565 {
566   const struct PeerAddMessage *pam;
567   const struct GNUNET_MessageHeader *hello;
568   uint16_t size;
569
570   size = ntohs (message->size);
571   if (size <
572       sizeof (struct PeerAddMessage) + sizeof (struct GNUNET_MessageHeader))
573     {
574       GNUNET_break (0);
575       GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
576       return;
577     }
578   pam = (const struct PeerAddMessage *) message;
579   hello = (const struct GNUNET_MessageHeader *) &pam[1];
580   if (size != sizeof (struct PeerAddMessage) + ntohs (hello->size))
581     {
582       GNUNET_break (0);
583       GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
584       return;
585     }
586   bind_address (&pam->peer, (const struct GNUNET_HELLO_Message *) hello);
587   GNUNET_SERVER_receive_done (client, GNUNET_OK);
588 }
589
590
591 /**
592  * Handle GET-message.
593  *
594  * @param cls closure
595  * @param client identification of the client
596  * @param message the actual message
597  */
598 static void
599 handle_get (void *cls,
600             struct GNUNET_SERVER_Client *client,
601             const struct GNUNET_MessageHeader *message)
602 {
603   const struct ListPeerMessage *lpm;
604
605   lpm = (const struct ListPeerMessage *) message;
606   send_to_each_host (&lpm->peer, ntohl (lpm->trust_change), client);
607 }
608
609
610 /**
611  * Handle GET-ALL-message.
612  *
613  * @param cls closure
614  * @param client identification of the client
615  * @param message the actual message
616  */
617 static void
618 handle_get_all (void *cls,
619                 struct GNUNET_SERVER_Client *client,
620                 const struct GNUNET_MessageHeader *message)
621 {
622   const struct ListAllPeersMessage *lpm;
623
624   lpm = (const struct ListAllPeersMessage *) message;
625   send_to_each_host (NULL, ntohl (lpm->trust_change), client);
626 }
627
628
629 /**
630  * List of handlers for the messages understood by this
631  * service.
632  */
633 static struct GNUNET_SERVER_MessageHandler handlers[] = {
634   {&handle_add, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_ADD, 0},
635   {&handle_get, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_GET,
636    sizeof (struct ListPeerMessage)},
637   {&handle_get_all, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_GET_ALL,
638    sizeof (struct ListAllPeersMessage)},
639   {NULL, NULL, 0, 0}
640 };
641
642
643 /**
644  * Process statistics requests.
645  *
646  * @param cls closure
647  * @param sched scheduler to use
648  * @param server the initialized server
649  * @param cfg configuration to use
650  */
651 static void
652 run (void *cls,
653      struct GNUNET_SCHEDULER_Handle *sched,
654      struct GNUNET_SERVER_Handle *server,
655      struct GNUNET_CONFIGURATION_Handle *cfg)
656 {
657   GNUNET_assert (GNUNET_OK ==
658                  GNUNET_CONFIGURATION_get_value_filename (cfg,
659                                                           "peerinfo",
660                                                           "HOSTS",
661                                                           &networkIdDirectory));
662   GNUNET_assert (GNUNET_OK ==
663                  GNUNET_CONFIGURATION_get_value_filename (cfg,
664                                                           "peerinfo",
665                                                           "TRUST",
666                                                           &trustDirectory));
667   GNUNET_DISK_directory_create (networkIdDirectory);
668   GNUNET_DISK_directory_create (trustDirectory);
669   GNUNET_SCHEDULER_add_delayed (sched,
670                                 GNUNET_NO,
671                                 GNUNET_SCHEDULER_PRIORITY_IDLE,
672                                 GNUNET_SCHEDULER_NO_PREREQUISITE_TASK,
673                                 GNUNET_TIME_UNIT_MILLISECONDS,
674                                 &cron_scan_directory_data_hosts, NULL);
675   GNUNET_SCHEDULER_add_delayed (sched,
676                                 GNUNET_YES,
677                                 GNUNET_SCHEDULER_PRIORITY_HIGH,
678                                 GNUNET_SCHEDULER_NO_PREREQUISITE_TASK,
679                                 TRUST_FLUSH_FREQ, &cron_flush_trust, NULL);
680   GNUNET_SCHEDULER_add_delayed (sched,
681                                 GNUNET_NO,
682                                 GNUNET_SCHEDULER_PRIORITY_IDLE,
683                                 GNUNET_SCHEDULER_NO_PREREQUISITE_TASK,
684                                 DATA_HOST_CLEAN_FREQ,
685                                 &cron_clean_data_hosts, NULL);
686   GNUNET_SERVER_add_handlers (server, handlers);
687 }
688
689
690 /**
691  * The main function for the statistics service.
692  *
693  * @param argc number of arguments from the command line
694  * @param argv command line arguments
695  * @return 0 ok, 1 on error
696  */
697 int
698 main (int argc, char *const *argv)
699 {
700   return (GNUNET_OK ==
701           GNUNET_SERVICE_run (argc,
702                               argv,
703                               "peerinfo", &run, NULL, NULL, NULL)) ? 0 : 1;
704 }
705
706
707 /* end of gnunet-service-peerinfo.c */