remove 'illegal' (non-reentrant) log logic from signal handler
[oweals/gnunet.git] / src / peerinfo-tool / gnunet-peerinfo.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2001-2014, 2016 GNUnet e.V.
4
5      GNUnet is free software: you can redistribute it and/or modify it
6      under the terms of the GNU Affero General Public License as published
7      by the Free Software Foundation, either version 3 of the License,
8      or (at your 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      Affero General Public License for more details.
14
15      You should have received a copy of the GNU Affero General Public License
16      along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18      SPDX-License-Identifier: AGPL3.0-or-later
19  */
20
21 /**
22  * @file peerinfo-tool/gnunet-peerinfo.c
23  * @brief Print information about other known peers.
24  * @author Christian Grothoff
25  * @author Matthias Wachs
26  */
27 #include "platform.h"
28 #include "gnunet_util_lib.h"
29 #include "gnunet_hello_lib.h"
30 #include "gnunet_transport_service.h"
31 #include "gnunet_transport_hello_service.h"
32 #include "gnunet_peerinfo_service.h"
33 #include "gnunet-peerinfo_plugins.h"
34
35 /**
36  * How long until we time out during address lookup?
37  */
38 #define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5)
39
40 /**
41  * Structure we use to collect printable address information.
42  */
43 struct PrintContext;
44
45 /**
46  * Record we keep for each printable address.
47  */
48 struct AddressRecord
49 {
50   /**
51    * Current address-to-string context (if active, otherwise NULL).
52    */
53   struct GNUNET_TRANSPORT_AddressToStringContext *atsc;
54
55   /**
56    * Address expiration time
57    */
58   struct GNUNET_TIME_Absolute expiration;
59
60   /**
61    * Printable address.
62    */
63   char *result;
64
65   /**
66    * Print context this address record belongs to.
67    */
68   struct PrintContext *pc;
69 };
70
71
72 /**
73  * Structure we use to collect printable address information.
74  */
75 struct PrintContext
76 {
77   /**
78    * Kept in DLL.
79    */
80   struct PrintContext *next;
81
82   /**
83    * Kept in DLL.
84    */
85   struct PrintContext *prev;
86
87   /**
88    * Identity of the peer.
89    */
90   struct GNUNET_PeerIdentity peer;
91
92   /**
93    * List of printable addresses.
94    */
95   struct AddressRecord *address_list;
96
97   /**
98    * Number of completed addresses in @e address_list.
99    */
100   unsigned int num_addresses;
101
102   /**
103    * Number of addresses allocated in @e address_list.
104    */
105   unsigned int address_list_size;
106
107   /**
108    * Current offset in @e address_list (counted down).
109    */
110   unsigned int off;
111
112   /**
113    * Hello was friend only, #GNUNET_YES or #GNUNET_NO
114    */
115   int friend_only;
116 };
117
118
119 /**
120  * Option '-n'
121  */
122 static int no_resolve;
123
124 /**
125  * Option '-q'
126  */
127 static int be_quiet;
128
129 /**
130  * Option '-f'
131  */
132 static int include_friend_only;
133
134 /**
135  * Option '-s'
136  */
137 static int get_self;
138
139 /**
140  * Option
141  */
142 static int get_uri;
143
144 /**
145  * Option
146  */
147 static int default_operation;
148
149 /**
150  * Option '-i'
151  */
152 static int get_info;
153
154 /**
155  * Option
156  */
157 static char *put_uri;
158
159 /**
160  * Option -d
161  */
162 static char *dump_hello;
163
164 /**
165  * Handle to peerinfo service.
166  */
167 static struct GNUNET_PEERINFO_Handle *peerinfo;
168
169 /**
170  * Configuration handle.
171  */
172 static const struct GNUNET_CONFIGURATION_Handle *cfg;
173
174 /**
175  * Main state machine task (if active).
176  */
177 static struct GNUNET_SCHEDULER_Task *tt;
178
179 /**
180  * Pending #GNUNET_TRANSPORT_hello_get() operation.
181  */
182 static struct GNUNET_TRANSPORT_HelloGetHandle *gh;
183
184 /**
185  * Current iterator context (if active, otherwise NULL).
186  */
187 static struct GNUNET_PEERINFO_IteratorContext *pic;
188
189 /**
190  * My peer identity.
191  */
192 static struct GNUNET_PeerIdentity my_peer_identity;
193
194 /**
195  * Head of list of print contexts.
196  */
197 static struct PrintContext *pc_head;
198
199 /**
200  * Tail of list of print contexts.
201  */
202 static struct PrintContext *pc_tail;
203
204 /**
205  * Handle to current #GNUNET_PEERINFO_add_peer() operation.
206  */
207 static struct GNUNET_MQ_Envelope *ac;
208
209 /**
210  * Hello of this peer (if initialized).
211  */
212 static struct GNUNET_HELLO_Message *my_hello;
213
214
215 /**
216  * Main state machine that goes over all options and
217  * runs the next requested function.
218  *
219  * @param cls unused
220  */
221 static void
222 state_machine (void *cls);
223
224
225 /* ********************* 'get_info' ******************* */
226
227 /**
228  * Print the collected address information to the console and free @a pc.
229  *
230  * @param pc printing context
231  */
232 static void
233 dump_pc (struct PrintContext *pc)
234 {
235   unsigned int i;
236
237   printf (_ ("%sPeer `%s'\n"),
238           (GNUNET_YES == pc->friend_only) ? "F2F: " : "",
239           GNUNET_i2s_full (&pc->peer));
240   for (i = 0; i < pc->num_addresses; i++)
241   {
242     if (NULL != pc->address_list[i].result)
243     {
244       printf (_ ("\tExpires: %s \t %s\n"),
245               GNUNET_STRINGS_absolute_time_to_string (
246                 pc->address_list[i].expiration),
247               pc->address_list[i].result);
248       GNUNET_free (pc->address_list[i].result);
249     }
250   }
251   printf ("\n");
252   GNUNET_free_non_null (pc->address_list);
253   GNUNET_CONTAINER_DLL_remove (pc_head, pc_tail, pc);
254   GNUNET_free (pc);
255   if ((NULL == pc_head) && (NULL == pic))
256     tt = GNUNET_SCHEDULER_add_now (&state_machine, NULL);
257 }
258
259
260 /* ************************* list all known addresses **************** */
261
262
263 /**
264  * Function to call with a human-readable format of an address
265  *
266  * @param cls closure
267  * @param address NULL on error, otherwise 0-terminated printable UTF-8 string
268  * @param res result of the address to string conversion:
269  *        if #GNUNET_OK: address was valid (conversion to
270  *                       string might still have failed)
271  *        if #GNUNET_SYSERR: address is invalid
272  */
273 static void
274 process_resolved_address (void *cls, const char *address, int res)
275 {
276   struct AddressRecord *ar = cls;
277   struct PrintContext *pc = ar->pc;
278
279   if (NULL != address)
280   {
281     if (0 != strlen (address))
282     {
283       if (NULL != ar->result)
284         GNUNET_free (ar->result);
285       ar->result = GNUNET_strdup (address);
286     }
287     return;
288   }
289   ar->atsc = NULL;
290   if (GNUNET_SYSERR == res)
291     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
292                 _ ("Failure: Cannot convert address to string for peer `%s'\n"),
293                 GNUNET_i2s (&ar->pc->peer));
294   pc->num_addresses++;
295   if (pc->num_addresses == pc->address_list_size)
296     dump_pc (pc);
297 }
298
299
300 /**
301  * Iterator callback to go over all addresses and count them.
302  *
303  * @param cls `struct PrintContext *` with `off` to increment
304  * @param address the address
305  * @param expiration expiration time
306  * @return #GNUNET_OK to keep the address and continue
307  */
308 static int
309 count_address (void *cls,
310                const struct GNUNET_HELLO_Address *address,
311                struct GNUNET_TIME_Absolute expiration)
312 {
313   struct PrintContext *pc = cls;
314
315   pc->off++;
316   return GNUNET_OK;
317 }
318
319
320 /**
321  * Iterator callback to go over all addresses.
322  *
323  * @param cls closure
324  * @param address the address
325  * @param expiration expiration time
326  * @return #GNUNET_OK to keep the address and continue
327  */
328 static int
329 print_address (void *cls,
330                const struct GNUNET_HELLO_Address *address,
331                struct GNUNET_TIME_Absolute expiration)
332 {
333   struct PrintContext *pc = cls;
334   struct AddressRecord *ar;
335
336   GNUNET_assert (0 < pc->off);
337   ar = &pc->address_list[--pc->off];
338   ar->pc = pc;
339   ar->expiration = expiration;
340   GNUNET_asprintf (&ar->result,
341                    "%s:%u:%u",
342                    address->transport_name,
343                    address->address_length,
344                    address->local_info);
345   ar->atsc = GNUNET_TRANSPORT_address_to_string (cfg,
346                                                  address,
347                                                  no_resolve,
348                                                  TIMEOUT,
349                                                  &process_resolved_address,
350                                                  ar);
351   return GNUNET_OK;
352 }
353
354
355 /**
356  * Print information about the peer.  Currently prints the `struct
357  * GNUNET_PeerIdentity` and the transport address.
358  *
359  * @param cls the `struct PrintContext *`
360  * @param peer identity of the peer
361  * @param hello addresses of the peer
362  * @param err_msg error message
363  */
364 static void
365 print_peer_info (void *cls,
366                  const struct GNUNET_PeerIdentity *peer,
367                  const struct GNUNET_HELLO_Message *hello,
368                  const char *err_msg)
369 {
370   struct PrintContext *pc;
371   int friend_only;
372
373   if (NULL == peer)
374   {
375     pic = NULL;   /* end of iteration */
376     if (NULL != err_msg)
377     {
378       fprintf (stderr,
379                _ ("Error in communication with PEERINFO service: %s\n"),
380                err_msg);
381     }
382     if (NULL == pc_head)
383       tt = GNUNET_SCHEDULER_add_now (&state_machine, NULL);
384     return;
385   }
386   friend_only = GNUNET_NO;
387   if (NULL != hello)
388     friend_only = GNUNET_HELLO_is_friend_only (hello);
389   if ((GNUNET_YES == be_quiet) || (NULL == hello))
390   {
391     printf ("%s%s\n",
392             (GNUNET_YES == friend_only) ? "F2F: " : "",
393             GNUNET_i2s_full (peer));
394     return;
395   }
396   pc = GNUNET_new (struct PrintContext);
397   GNUNET_CONTAINER_DLL_insert (pc_head, pc_tail, pc);
398   pc->peer = *peer;
399   pc->friend_only = friend_only;
400   GNUNET_HELLO_iterate_addresses (hello, GNUNET_NO, &count_address, pc);
401   if (0 == pc->off)
402   {
403     dump_pc (pc);
404     return;
405   }
406   pc->address_list_size = pc->off;
407   pc->address_list = GNUNET_malloc (sizeof(struct AddressRecord) * pc->off);
408   GNUNET_HELLO_iterate_addresses (hello, GNUNET_NO, &print_address, pc);
409 }
410
411
412 /* ************************* DUMP Hello  ************************** */
413
414 /**
415  * Count the number of addresses in the HELLO.
416  *
417  * @param cls pointer to an `int *` used for the counter
418  * @param address an address to count
419  * @param expiration (unused)
420  * @return #GNUNET_OK
421  */
422 static int
423 count_addr (void *cls,
424             const struct GNUNET_HELLO_Address *address,
425             struct GNUNET_TIME_Absolute expiration)
426 {
427   int *c = cls;
428
429   (*c)++;
430   return GNUNET_OK;
431 }
432
433
434 /**
435  * Write HELLO of my peer to a file.
436  *
437  * @param cls the `struct GetUriContext *`
438  * @param peer identity of the peer (unused)
439  * @param hello addresses of the peer
440  * @param err_msg error message
441  */
442 static void
443 dump_my_hello ()
444 {
445   unsigned int size;
446   unsigned int c_addr;
447
448   size = GNUNET_HELLO_size (my_hello);
449   if (0 == size)
450   {
451     fprintf (stderr, _ ("Failure: Received invalid %s\n"), "HELLO");
452     return;
453   }
454   if (GNUNET_SYSERR == GNUNET_DISK_fn_write (dump_hello,
455                                              my_hello,
456                                              size,
457                                              GNUNET_DISK_PERM_USER_READ
458                                              | GNUNET_DISK_PERM_USER_WRITE
459                                              | GNUNET_DISK_PERM_GROUP_READ
460                                              | GNUNET_DISK_PERM_OTHER_READ))
461   {
462     fprintf (stderr,
463              _ ("Failed to write HELLO with %u bytes to file `%s'\n"),
464              size,
465              dump_hello);
466     if (0 != unlink (dump_hello))
467       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING
468                                 | GNUNET_ERROR_TYPE_BULK,
469                                 "unlink",
470                                 dump_hello);
471   }
472   c_addr = 0;
473   GNUNET_HELLO_iterate_addresses (my_hello, GNUNET_NO, count_addr, &c_addr);
474
475   if (! be_quiet)
476   {
477     fprintf (
478       stderr,
479       _ ("Wrote %s HELLO containing %u addresses with %u bytes to file `%s'\n"),
480       (GNUNET_YES == GNUNET_HELLO_is_friend_only (my_hello)) ? "friend-only"
481       : "public",
482       c_addr,
483       size,
484       dump_hello);
485   }
486   GNUNET_free (dump_hello);
487   dump_hello = NULL;
488 }
489
490
491 /* ************************* GET URI ************************** */
492
493
494 /**
495  * Print URI of the peer.
496  *
497  * @param cls the `struct GetUriContext *`
498  * @param peer identity of the peer (unused)
499  * @param hello addresses of the peer
500  * @param err_msg error message
501  */
502 static void
503 print_my_uri (void *cls,
504               const struct GNUNET_PeerIdentity *peer,
505               const struct GNUNET_HELLO_Message *hello,
506               const char *err_msg)
507 {
508   char *uri;
509
510   if (NULL == peer)
511   {
512     pic = NULL;
513     if (NULL != err_msg)
514       fprintf (stderr,
515                _ ("Error in communication with PEERINFO service: %s\n"),
516                err_msg);
517     tt = GNUNET_SCHEDULER_add_now (&state_machine, NULL);
518     return;
519   }
520
521   if (NULL == hello)
522     return;
523   uri = GNUNET_HELLO_compose_uri (hello, &GPI_plugins_find);
524   if (NULL != uri)
525   {
526     printf ("%s\n", (const char *) uri);
527     GNUNET_free (uri);
528   }
529 }
530
531
532 /* ************************* import HELLO by URI ********************* */
533
534
535 /**
536  * Continuation called from #GNUNET_PEERINFO_add_peer()
537  *
538  * @param cls closure, NULL
539  */
540 static void
541 add_continuation (void *cls)
542 {
543   ac = NULL;
544   tt = GNUNET_SCHEDULER_add_now (&state_machine, NULL);
545 }
546
547
548 /**
549  * Parse the PUT URI given at the command line and add it to our peerinfo
550  * database.
551  *
552  * @param put_uri URI string to parse
553  * @return #GNUNET_OK on success,
554  *         #GNUNET_SYSERR if the URI was invalid,
555  *         #GNUNET_NO on other errors
556  */
557 static int
558 parse_hello_uri (const char *put_uri)
559 {
560   struct GNUNET_HELLO_Message *hello = NULL;
561
562   int ret = GNUNET_HELLO_parse_uri (put_uri,
563                                     &my_peer_identity.public_key,
564                                     &hello,
565                                     &GPI_plugins_find);
566
567   if (NULL != hello)
568   {
569     /* WARNING: this adds the address from URI WITHOUT verification! */
570     if (GNUNET_OK == ret)
571       ac = GNUNET_PEERINFO_add_peer (peerinfo, hello, &add_continuation, NULL);
572     else
573       tt = GNUNET_SCHEDULER_add_now (&state_machine, NULL);
574     GNUNET_free (hello);
575   }
576   return ret;
577 }
578
579
580 /* ************************ Main state machine ********************* */
581
582
583 /**
584  * Main state machine that goes over all options and
585  * runs the next requested function.
586  *
587  * @param cls unused
588  */
589 static void
590 shutdown_task (void *cls)
591 {
592   struct PrintContext *pc;
593   struct AddressRecord *ar;
594   unsigned int i;
595
596   if (NULL != ac)
597   {
598     GNUNET_MQ_send_cancel (ac);
599     ac = NULL;
600   }
601   if (NULL != tt)
602   {
603     GNUNET_SCHEDULER_cancel (tt);
604     tt = NULL;
605   }
606   if (NULL != pic)
607   {
608     GNUNET_PEERINFO_iterate_cancel (pic);
609     pic = NULL;
610   }
611   if (NULL != gh)
612   {
613     GNUNET_TRANSPORT_hello_get_cancel (gh);
614     gh = NULL;
615   }
616   while (NULL != (pc = pc_head))
617   {
618     GNUNET_CONTAINER_DLL_remove (pc_head, pc_tail, pc);
619     for (i = 0; i < pc->address_list_size; i++)
620     {
621       ar = &pc->address_list[i];
622       GNUNET_free_non_null (ar->result);
623       if (NULL != ar->atsc)
624       {
625         GNUNET_TRANSPORT_address_to_string_cancel (ar->atsc);
626         ar->atsc = NULL;
627       }
628     }
629     GNUNET_free_non_null (pc->address_list);
630     GNUNET_free (pc);
631   }
632   GPI_plugins_unload ();
633   if (NULL != peerinfo)
634   {
635     GNUNET_PEERINFO_disconnect (peerinfo);
636     peerinfo = NULL;
637   }
638   if (NULL != my_hello)
639   {
640     GNUNET_free (my_hello);
641     my_hello = NULL;
642   }
643 }
644
645
646 /**
647  * Function called with our peer's HELLO message.
648  * Used to obtain our peer's public key.
649  *
650  * @param cls NULL
651  * @param hello the HELLO message
652  */
653 static void
654 hello_callback (void *cls, const struct GNUNET_MessageHeader *hello)
655 {
656   if (NULL == hello)
657   {
658     fprintf (stderr, "Failed to get my own HELLO from this peer!\n");
659     GNUNET_SCHEDULER_shutdown ();
660     return;
661   }
662   my_hello = (struct GNUNET_HELLO_Message *) GNUNET_copy_message (hello);
663   GNUNET_assert (GNUNET_OK ==
664                  GNUNET_HELLO_get_id (my_hello, &my_peer_identity));
665   GNUNET_TRANSPORT_hello_get_cancel (gh);
666   gh = NULL;
667   if (NULL != dump_hello)
668     dump_my_hello ();
669   tt = GNUNET_SCHEDULER_add_now (&state_machine, NULL);
670 }
671
672
673 /**
674  * Main function that will be run by the scheduler.
675  *
676  * @param cls closure
677  * @param args remaining command-line arguments
678  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
679  * @param c configuration
680  */
681 static void
682 run (void *cls,
683      char *const *args,
684      const char *cfgfile,
685      const struct GNUNET_CONFIGURATION_Handle *c)
686 {
687   cfg = c;
688   if ((NULL != args[0]) && (NULL == put_uri) &&
689       (args[0] == strcasestr (args[0], "gnunet://hello/")))
690   {
691     put_uri = GNUNET_strdup (args[0]);
692     args++;
693   }
694   if (NULL != args[0])
695   {
696     fprintf (stderr, _ ("Invalid command line argument `%s'\n"), args[0]);
697     return;
698   }
699   if (NULL == (peerinfo = GNUNET_PEERINFO_connect (cfg)))
700   {
701     fprintf (stderr, "%s", "Could not access PEERINFO service.  Exiting.\n");
702     return;
703   }
704   if ((GNUNET_YES == get_self) || (GNUNET_YES == get_uri) ||
705       (NULL != dump_hello))
706   {
707     gh = GNUNET_TRANSPORT_hello_get (cfg,
708                                      GNUNET_TRANSPORT_AC_ANY,
709                                      &hello_callback,
710                                      NULL);
711   }
712   else
713   {
714     tt = GNUNET_SCHEDULER_add_now (&state_machine, NULL);
715   }
716   GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL);
717 }
718
719
720 /**
721  * Main state machine that goes over all options and
722  * runs the next requested function.
723  *
724  * @param cls unused
725  */
726 static void
727 state_machine (void *cls)
728 {
729   tt = NULL;
730
731   if (NULL != put_uri)
732   {
733     GPI_plugins_load (cfg);
734     if (GNUNET_SYSERR == parse_hello_uri (put_uri))
735     {
736       fprintf (stderr, _ ("Invalid URI `%s'\n"), put_uri);
737       GNUNET_SCHEDULER_shutdown ();
738     }
739     GNUNET_free (put_uri);
740     put_uri = NULL;
741   }
742   else if (GNUNET_YES == get_info)
743   {
744     get_info = GNUNET_NO;
745     GPI_plugins_load (cfg);
746     pic = GNUNET_PEERINFO_iterate (peerinfo,
747                                    include_friend_only,
748                                    NULL,
749                                    &print_peer_info,
750                                    NULL);
751   }
752   else if (GNUNET_YES == get_self)
753   {
754     get_self = GNUNET_NO;
755     if (be_quiet)
756       printf ("%s\n", GNUNET_i2s_full (&my_peer_identity));
757     else
758       printf (_ ("I am peer `%s'.\n"), GNUNET_i2s_full (&my_peer_identity));
759     tt = GNUNET_SCHEDULER_add_now (&state_machine, NULL);
760   }
761   else if (GNUNET_YES == get_uri)
762   {
763     GPI_plugins_load (cfg);
764     pic = GNUNET_PEERINFO_iterate (peerinfo,
765                                    include_friend_only,
766                                    &my_peer_identity,
767                                    &print_my_uri,
768                                    NULL);
769     get_uri = GNUNET_NO;
770   }
771   else if (GNUNET_YES == default_operation)
772   {
773     /* default operation list all */
774     default_operation = GNUNET_NO;
775     get_info = GNUNET_YES;
776     tt = GNUNET_SCHEDULER_add_now (&state_machine, NULL);
777   }
778   else
779   {
780     GNUNET_SCHEDULER_shutdown ();
781   }
782   default_operation = GNUNET_NO;
783 }
784
785
786 /**
787  * The main function to obtain peer information.
788  *
789  * @param argc number of arguments from the command line
790  * @param argv command line arguments
791  * @return 0 ok, 1 on error
792  */
793 int
794 main (int argc, char *const *argv)
795 {
796   struct GNUNET_GETOPT_CommandLineOption options[] =
797   { GNUNET_GETOPT_option_flag ('n',
798                                "numeric",
799                                gettext_noop ("don't resolve host names"),
800                                &no_resolve),
801
802     GNUNET_GETOPT_option_flag ('q',
803                                "quiet",
804                                gettext_noop (
805                                  "output only the identity strings"),
806                                &be_quiet),
807     GNUNET_GETOPT_option_flag ('f',
808                                "friends",
809                                gettext_noop (
810                                  "include friend-only information"),
811                                &include_friend_only),
812
813     GNUNET_GETOPT_option_flag ('s',
814                                "self",
815                                gettext_noop ("output our own identity only"),
816                                &get_self),
817
818     GNUNET_GETOPT_option_flag ('i',
819                                "info",
820                                gettext_noop ("list all known peers"),
821                                &get_info),
822
823     GNUNET_GETOPT_option_string ('d',
824                                  "dump-hello",
825                                  NULL,
826                                  gettext_noop ("dump hello to file"),
827                                  &dump_hello),
828
829     GNUNET_GETOPT_option_flag ('g',
830                                "get-hello",
831                                gettext_noop ("also output HELLO uri(s)"),
832                                &get_uri),
833
834     GNUNET_GETOPT_option_string ('p',
835                                  "put-hello",
836                                  "HELLO",
837                                  gettext_noop (
838                                    "add given HELLO uri to the database"),
839                                  &put_uri),
840
841     GNUNET_GETOPT_OPTION_END };
842   int ret;
843
844   default_operation = GNUNET_YES;
845   if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
846     return 2;
847
848   ret = (GNUNET_OK ==
849          GNUNET_PROGRAM_run (argc,
850                              argv,
851                              "gnunet-peerinfo",
852                              gettext_noop ("Print information about peers."),
853                              options,
854                              &run,
855                              NULL))
856         ? 0
857         : 1;
858   GNUNET_free ((void *) argv);
859   return ret;
860 }
861
862
863 /* end of gnunet-peerinfo.c */