/* This file is part of GNUnet. (C) 2001, 2002, 2004, 2005, 2007, 2009, 2010 Christian Grothoff (and other contributing authors) GNUnet is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GNUnet; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /** * @file peerinfo/gnunet-service-peerinfo.c * @brief maintains list of known peers * * Code to maintain the list of currently known hosts (in memory * structure of data/hosts/ and data/credit/). * * @author Christian Grothoff * * TODO: * - HostEntries are never 'free'd (add expiration, upper bound?) */ #include "platform.h" #include "gnunet_crypto_lib.h" #include "gnunet_disk_lib.h" #include "gnunet_hello_lib.h" #include "gnunet_protocols.h" #include "gnunet_service_lib.h" #include "peerinfo.h" /** * How often do we scan the HOST_DIR for new entries? */ #define DATA_HOST_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15) /** * How often do we flush trust values to disk? */ #define TRUST_FLUSH_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 5) /** * How often do we discard old entries in data/hosts/? */ #define DATA_HOST_CLEAN_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 60) /** * In-memory cache of known hosts. */ struct HostEntry { /** * This is a linked list. */ struct HostEntry *next; /** * Identity of the peer. */ struct GNUNET_PeerIdentity identity; /** * Hello for the peer (can be NULL) */ struct GNUNET_HELLO_Message *hello; /** * Trust rating for this peer */ uint32_t trust; /** * Trust rating for this peer on disk. */ uint32_t disk_trust; }; /** * Entries that we still need to tell the client about. */ struct PendingEntry { /** * This is a linked list. */ struct PendingEntry *next; /** * Entry to tell the client about. */ struct HostEntry *he; }; /** * Clients to notify of changes to the peer information. */ struct NotifyList { /** * This is a linked list. */ struct NotifyList *next; /** * Client to notify. */ struct GNUNET_SERVER_Client *client; /** * Notifications pending for this entry. */ struct PendingEntry *pending; /** * Handle for a transmit ready request. */ struct GNUNET_CONNECTION_TransmitHandle *transmit_ctx; }; /** * The in-memory list of known hosts. */ static struct HostEntry *hosts; /** * Clients to immediately notify about all changes. */ static struct NotifyList *notify_list; /** * Directory where the hellos are stored in (data/hosts) */ static char *networkIdDirectory; /** * Where do we store trust information? */ static char *trustDirectory; /** * Transmit peer information messages from the pending queue * to the client. * * @param cls the 'struct NotifyList' that we are processing * @param size number of bytes we can transmit * @param vbuf where to write the messages * @return number of bytes written to vbuf */ static size_t transmit_pending_notification (void *cls, size_t size, void *vbuf) { struct NotifyList *nl = cls; char *buf = vbuf; struct PendingEntry *pos; struct PendingEntry *next; struct InfoMessage im; uint16_t hs; size_t left; nl->transmit_ctx = NULL; next = nl->pending; pos = nl->pending; left = size; while (pos != NULL) { hs = (pos->he->hello == NULL) ? 0 : GNUNET_HELLO_size (pos->he->hello); if (left < sizeof (struct InfoMessage) + hs) break; next = pos->next; im.header.size = htons (hs + sizeof (struct InfoMessage)); im.header.type = htons (GNUNET_MESSAGE_TYPE_PEERINFO_INFO); im.trust = htonl (pos->he->trust); im.peer = pos->he->identity; memcpy (&buf[size - left], &im, sizeof (struct InfoMessage)); memcpy (&buf[size - left + sizeof (struct InfoMessage)], pos->he->hello, hs); left -= hs + sizeof (struct InfoMessage); GNUNET_free (pos); pos = next; } nl->pending = next; if (nl->pending != NULL) { nl->transmit_ctx = GNUNET_SERVER_notify_transmit_ready (nl->client, sizeof (struct InfoMessage) + hs, GNUNET_TIME_UNIT_FOREVER_REL, &transmit_pending_notification, nl); } return size - left; } /** * Notify client about host change. Checks if the * respective host entry is already in the list of things * to send to the client, and if not, adds it. Also * triggers a new request for transmission if the pending * list was previously empty. * * @param nl client to notify * @param he entry to notify about */ static void do_notify (struct NotifyList *nl, struct HostEntry *he) { struct PendingEntry *pe; uint16_t hsize; pe = nl->pending; while (NULL != pe) { if (pe->he == he) return; /* already in list */ pe = pe->next; } pe = GNUNET_malloc (sizeof (struct PendingEntry)); pe->next = nl->pending; pe->he = he; nl->pending = pe; if (nl->transmit_ctx != NULL) return; /* already trying to transmit */ hsize = (he->hello == NULL) ? 0 : GNUNET_HELLO_size (he->hello); nl->transmit_ctx = GNUNET_SERVER_notify_transmit_ready (nl->client, sizeof (struct InfoMessage) + hsize, GNUNET_TIME_UNIT_FOREVER_REL, &transmit_pending_notification, nl); } /** * Notify all clients in the notify list about the * given host entry changing. */ static void notify_all (struct HostEntry *he) { struct NotifyList *nl; nl = notify_list; while (NULL != nl) { do_notify (nl, he); nl = nl->next; } } /** * Address iterator that causes expired entries to be discarded. * * @param cls pointer to the current time * @param tname name of the transport * @param expiration expiration time for the address * @param addr the address * @param addrlen length of addr in bytes * @return GNUNET_NO if expiration smaller than the current time */ static int discard_expired (void *cls, const char *tname, struct GNUNET_TIME_Absolute expiration, const void *addr, size_t addrlen) { const struct GNUNET_TIME_Absolute *now = cls; if (now->value > expiration.value) return GNUNET_NO; return GNUNET_OK; } /** * Get the filename under which we would store the GNUNET_HELLO_Message * for the given host and protocol. * @return filename of the form DIRECTORY/HOSTID */ static char * get_host_filename (const struct GNUNET_PeerIdentity *id) { struct GNUNET_CRYPTO_HashAsciiEncoded fil; char *fn; GNUNET_CRYPTO_hash_to_enc (&id->hashPubKey, &fil); GNUNET_asprintf (&fn, "%s%s%s", networkIdDirectory, DIR_SEPARATOR_STR, &fil); return fn; } /** * Get the filename under which we would store the GNUNET_HELLO_Message * for the given host and protocol. * @return filename of the form DIRECTORY/HOSTID */ static char * get_trust_filename (const struct GNUNET_PeerIdentity *id) { struct GNUNET_CRYPTO_HashAsciiEncoded fil; char *fn; GNUNET_CRYPTO_hash_to_enc (&id->hashPubKey, &fil); GNUNET_asprintf (&fn, "%s%s%s", trustDirectory, DIR_SEPARATOR_STR, &fil); return fn; } /** * Find the host entry for the given peer. Call * only when synchronized! * @return NULL if not found */ static struct HostEntry * lookup_host_entry (const struct GNUNET_PeerIdentity *id) { struct HostEntry *pos; pos = hosts; while ((pos != NULL) && (0 != memcmp (id, &pos->identity, sizeof (struct GNUNET_PeerIdentity)))) pos = pos->next; return pos; } /** * Add a host to the list. * * @param identity the identity of the host */ static void add_host_to_known_hosts (const struct GNUNET_PeerIdentity *identity) { struct HostEntry *entry; char *fn; uint32_t trust; char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE]; const struct GNUNET_HELLO_Message *hello; struct GNUNET_HELLO_Message *hello_clean; int size; struct GNUNET_TIME_Absolute now; entry = lookup_host_entry (identity); if (entry != NULL) return; entry = GNUNET_malloc (sizeof (struct HostEntry)); entry->identity = *identity; fn = get_trust_filename (identity); if ((GNUNET_DISK_file_test (fn) == GNUNET_YES) && (sizeof (trust) == GNUNET_DISK_fn_read (fn, &trust, sizeof (trust)))) entry->disk_trust = entry->trust = ntohl (trust); GNUNET_free (fn); fn = get_host_filename (identity); if (GNUNET_DISK_file_test (fn) == GNUNET_YES) { size = GNUNET_DISK_fn_read (fn, buffer, sizeof (buffer)); hello = (const struct GNUNET_HELLO_Message *) buffer; if ( (size < sizeof (struct GNUNET_MessageHeader)) || (size != ntohs((((const struct GNUNET_MessageHeader*) hello)->size))) || (size != GNUNET_HELLO_size (hello)) ) { GNUNET_break (0); if (0 != UNLINK (fn)) GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "unlink", fn); } else { now = GNUNET_TIME_absolute_get (); hello_clean = GNUNET_HELLO_iterate_addresses (hello, GNUNET_YES, &discard_expired, &now); entry->hello = hello_clean; } } GNUNET_free (fn); entry->next = hosts; hosts = entry; notify_all (entry); } /** * Increase the host credit by a value. * * @param hostId is the identity of the host * @param value is the int value by which the * host credit is to be increased or decreased * @returns the actual change in trust (positive or negative) */ static int change_host_trust (const struct GNUNET_PeerIdentity *hostId, int value) { struct HostEntry *host; unsigned int old_trust; if (value == 0) return 0; host = lookup_host_entry (hostId); if (host == NULL) { add_host_to_known_hosts (hostId); host = lookup_host_entry (hostId); } GNUNET_assert (host != NULL); old_trust = host->trust; if (value > 0) { if (host->trust + value < host->trust) { value = ((uint32_t) - 1) - host->trust; host->trust = (uint32_t) - 1; /* maximized */ } else host->trust += value; } else { if (host->trust < -value) { value = -host->trust; host->trust = 0; } else host->trust += value; } if (host->trust != old_trust) notify_all (host); return value; } /** * Remove a file that should not be there. LOG * success or failure. */ static void remove_garbage (const char *fullname) { if (0 == UNLINK (fullname)) GNUNET_log (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK, _ ("File `%s' in directory `%s' does not match naming convention. " "Removed.\n"), fullname, networkIdDirectory); else GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, "unlink", fullname); } static int hosts_directory_scan_callback (void *cls, const char *fullname) { unsigned int *matched = cls; struct GNUNET_PeerIdentity identity; const char *filename; if (GNUNET_DISK_file_test (fullname) != GNUNET_YES) return GNUNET_OK; /* ignore non-files */ if (strlen (fullname) < sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded)) { remove_garbage (fullname); return GNUNET_OK; } filename = &fullname[strlen (fullname) - sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) + 1]; if (filename[-1] != DIR_SEPARATOR) { remove_garbage (fullname); return GNUNET_OK; } if (GNUNET_OK != GNUNET_CRYPTO_hash_from_string (filename, &identity.hashPubKey)) { remove_garbage (fullname); return GNUNET_OK; } (*matched)++; add_host_to_known_hosts (&identity); return GNUNET_OK; } /** * Call this method periodically to scan data/hosts for new hosts. */ static void cron_scan_directory_data_hosts (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { static unsigned int retries; unsigned int count; if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN)) return; count = 0; GNUNET_DISK_directory_create (networkIdDirectory); GNUNET_DISK_directory_scan (networkIdDirectory, &hosts_directory_scan_callback, &count); if ((0 == count) && (0 == (++retries & 31))) GNUNET_log (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK, _("Still no peers found in `%s'!\n"), networkIdDirectory); GNUNET_SCHEDULER_add_delayed (tc->sched, DATA_HOST_FREQ, &cron_scan_directory_data_hosts, NULL); } /** * Bind a host address (hello) to a hostId. * * @param peer the peer for which this is a hello * @param hello the verified (!) hello message */ static void bind_address (const struct GNUNET_PeerIdentity *peer, const struct GNUNET_HELLO_Message *hello) { char *fn; struct HostEntry *host; struct GNUNET_HELLO_Message *mrg; add_host_to_known_hosts (peer); host = lookup_host_entry (peer); GNUNET_assert (host != NULL); if (host->hello == NULL) { host->hello = GNUNET_malloc (GNUNET_HELLO_size (hello)); memcpy (host->hello, hello, GNUNET_HELLO_size (hello)); } else { mrg = GNUNET_HELLO_merge (host->hello, hello); /* FIXME: check if old and merged hello are equal, and if so, bail out early... */ GNUNET_free (host->hello); host->hello = mrg; } fn = get_host_filename (peer); GNUNET_DISK_fn_write (fn, host->hello, GNUNET_HELLO_size (host->hello), GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE | GNUNET_DISK_PERM_GROUP_READ | GNUNET_DISK_PERM_OTHER_READ); GNUNET_free (fn); notify_all (host); } /** * Do transmit info either for only the host matching the given * argument or for all known hosts and change their trust values by * the given delta. * * @param only NULL to hit all hosts, otherwise specifies a particular target * @param trust_change how much should the trust be changed * @param client who is making the request (and will thus receive our confirmation) */ static void send_to_each_host (const struct GNUNET_PeerIdentity *only, int trust_change, struct GNUNET_SERVER_Client *client) { struct HostEntry *pos; struct InfoMessage *im; const struct GNUNET_MessageHeader *end; uint16_t hs; char buf[GNUNET_SERVER_MAX_MESSAGE_SIZE]; struct GNUNET_SERVER_TransmitContext *tc; tc = GNUNET_SERVER_transmit_context_create (client); pos = hosts; while (pos != NULL) { if ((only == NULL) || (0 == memcmp (only, &pos->identity, sizeof (struct GNUNET_PeerIdentity)))) { change_host_trust (&pos->identity, trust_change); hs = 0; im = (struct InfoMessage *) buf; if (pos->hello != NULL) { hs = GNUNET_HELLO_size (pos->hello); GNUNET_assert (hs < GNUNET_SERVER_MAX_MESSAGE_SIZE - sizeof (struct InfoMessage)); memcpy (&im[1], pos->hello, hs); } im->trust = htonl (pos->trust); im->peer = pos->identity; end = &im->header; GNUNET_SERVER_transmit_context_append (tc, &end[1], hs + sizeof (struct InfoMessage) - sizeof (struct GNUNET_MessageHeader), GNUNET_MESSAGE_TYPE_PEERINFO_INFO); } pos = pos->next; } GNUNET_SERVER_transmit_context_append (tc, NULL, 0, GNUNET_MESSAGE_TYPE_PEERINFO_INFO_END); GNUNET_SERVER_transmit_context_run (tc, GNUNET_TIME_UNIT_FOREVER_REL); } /** * Write host-trust information to a file - flush the buffer entry! * Assumes synchronized access. */ static void flush_trust (struct HostEntry *host) { char *fn; uint32_t trust; if (host->trust == host->disk_trust) return; /* unchanged */ fn = get_trust_filename (&host->identity); if (host->trust == 0) { if ((0 != UNLINK (fn)) && (errno != ENOENT)) GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK, "unlink", fn); } else { trust = htonl (host->trust); if (sizeof(uint32_t) == GNUNET_DISK_fn_write (fn, &trust, sizeof(uint32_t), GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE | GNUNET_DISK_PERM_GROUP_READ | GNUNET_DISK_PERM_OTHER_READ)) host->disk_trust = host->trust; } GNUNET_free (fn); } /** * Call this method periodically to scan data/hosts for new hosts. */ static void cron_flush_trust (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct HostEntry *pos; pos = hosts; while (pos != NULL) { flush_trust (pos); pos = pos->next; } if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN)) return; GNUNET_SCHEDULER_add_delayed (tc->sched, TRUST_FLUSH_FREQ, &cron_flush_trust, NULL); } /** * @brief delete expired HELLO entries in data/hosts/ */ static int discard_hosts_helper (void *cls, const char *fn) { struct GNUNET_TIME_Absolute *now = cls; char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE]; const struct GNUNET_HELLO_Message *hello; struct GNUNET_HELLO_Message *new_hello; int size; size = GNUNET_DISK_fn_read (fn, buffer, sizeof (buffer)); if ((size < sizeof (struct GNUNET_MessageHeader)) && (0 != UNLINK (fn))) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK, "unlink", fn); return GNUNET_OK; } hello = (const struct GNUNET_HELLO_Message *) buffer; new_hello = GNUNET_HELLO_iterate_addresses (hello, GNUNET_YES, &discard_expired, now); if ((new_hello == NULL) && (0 != UNLINK (fn))) GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK, "unlink", fn); if (new_hello != NULL) { GNUNET_DISK_fn_write (fn, new_hello, GNUNET_HELLO_size (new_hello), GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE | GNUNET_DISK_PERM_GROUP_READ | GNUNET_DISK_PERM_OTHER_READ); GNUNET_free (new_hello); } return GNUNET_OK; } /** * Call this method periodically to scan data/hosts for new hosts. */ static void cron_clean_data_hosts (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct GNUNET_TIME_Absolute now; if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN)) return; now = GNUNET_TIME_absolute_get (); GNUNET_DISK_directory_scan (networkIdDirectory, &discard_hosts_helper, &now); GNUNET_SCHEDULER_add_delayed (tc->sched, DATA_HOST_CLEAN_FREQ, &cron_clean_data_hosts, NULL); } /** * Handle ADD-message. * * @param cls closure * @param client identification of the client * @param message the actual message */ static void handle_add (void *cls, struct GNUNET_SERVER_Client *client, const struct GNUNET_MessageHeader *message) { const struct PeerAddMessage *pam; const struct GNUNET_MessageHeader *hello; uint16_t size; size = ntohs (message->size); if (size < sizeof (struct PeerAddMessage) + sizeof (struct GNUNET_MessageHeader)) { GNUNET_break (0); GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; } pam = (const struct PeerAddMessage *) message; hello = (const struct GNUNET_MessageHeader *) &pam[1]; if (size != sizeof (struct PeerAddMessage) + ntohs (hello->size)) { GNUNET_break (0); GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); return; } bind_address (&pam->peer, (const struct GNUNET_HELLO_Message *) hello); GNUNET_SERVER_receive_done (client, GNUNET_OK); } /** * Handle GET-message. * * @param cls closure * @param client identification of the client * @param message the actual message */ static void handle_get (void *cls, struct GNUNET_SERVER_Client *client, const struct GNUNET_MessageHeader *message) { const struct ListPeerMessage *lpm; lpm = (const struct ListPeerMessage *) message; send_to_each_host (&lpm->peer, ntohl (lpm->trust_change), client); } /** * Handle GET-ALL-message. * * @param cls closure * @param client identification of the client * @param message the actual message */ static void handle_get_all (void *cls, struct GNUNET_SERVER_Client *client, const struct GNUNET_MessageHeader *message) { const struct ListAllPeersMessage *lpm; lpm = (const struct ListAllPeersMessage *) message; send_to_each_host (NULL, ntohl (lpm->trust_change), client); } /** * Handle NOTIFY-message. * * @param cls closure * @param client identification of the client * @param message the actual message */ static void handle_notify (void *cls, struct GNUNET_SERVER_Client *client, const struct GNUNET_MessageHeader *message) { struct NotifyList *nl; struct HostEntry *pos; nl = GNUNET_malloc (sizeof (struct NotifyList)); nl->next = notify_list; nl->client = client; GNUNET_SERVER_client_keep (client); notify_list = nl; pos = hosts; while (NULL != pos) { do_notify (nl, pos); pos = pos->next; } } /** * List of handlers for the messages understood by this * service. */ static struct GNUNET_SERVER_MessageHandler handlers[] = { {&handle_add, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_ADD, 0}, {&handle_get, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_GET, sizeof (struct ListPeerMessage)}, {&handle_get_all, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_GET_ALL, sizeof (struct ListAllPeersMessage)}, {&handle_notify, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_NOTIFY, sizeof (struct GNUNET_MessageHeader)}, {NULL, NULL, 0, 0} }; /** * Function that is called when a client disconnects. */ static void notify_disconnect (void *cls, struct GNUNET_SERVER_Client *client) { struct NotifyList *pos; struct NotifyList *prev; struct NotifyList *next; struct PendingEntry *p; pos = notify_list; prev = NULL; while (pos != NULL) { next = pos->next; if (pos->client == client) { while (NULL != (p = pos->pending)) { pos->pending = p->next; GNUNET_free (p); } if (pos->transmit_ctx != NULL) { GNUNET_CONNECTION_notify_transmit_ready_cancel (pos->transmit_ctx); pos->transmit_ctx = NULL; } if (prev == NULL) notify_list = next; else prev->next = next; GNUNET_SERVER_client_drop (client); GNUNET_free (pos); } else { prev = pos; } pos = next; } } /** * Process statistics requests. * * @param cls closure * @param sched scheduler to use * @param server the initialized server * @param cfg configuration to use */ static void run (void *cls, struct GNUNET_SCHEDULER_Handle *sched, struct GNUNET_SERVER_Handle *server, const struct GNUNET_CONFIGURATION_Handle *cfg) { GNUNET_assert (GNUNET_OK == GNUNET_CONFIGURATION_get_value_filename (cfg, "peerinfo", "HOSTS", &networkIdDirectory)); GNUNET_assert (GNUNET_OK == GNUNET_CONFIGURATION_get_value_filename (cfg, "peerinfo", "TRUST", &trustDirectory)); GNUNET_DISK_directory_create (networkIdDirectory); GNUNET_DISK_directory_create (trustDirectory); GNUNET_SCHEDULER_add_with_priority (sched, GNUNET_SCHEDULER_PRIORITY_IDLE, &cron_scan_directory_data_hosts, NULL); GNUNET_SCHEDULER_add_with_priority (sched, GNUNET_SCHEDULER_PRIORITY_HIGH, &cron_flush_trust, NULL); GNUNET_SCHEDULER_add_with_priority (sched, GNUNET_SCHEDULER_PRIORITY_IDLE, &cron_clean_data_hosts, NULL); GNUNET_SERVER_disconnect_notify (server, ¬ify_disconnect, NULL); GNUNET_SERVER_add_handlers (server, handlers); } /** * The main function for the statistics service. * * @param argc number of arguments from the command line * @param argv command line arguments * @return 0 ok, 1 on error */ int main (int argc, char *const *argv) { int ret; ret = (GNUNET_OK == GNUNET_SERVICE_run (argc, argv, "peerinfo", GNUNET_SERVICE_OPTION_NONE, &run, NULL)) ? 0 : 1; GNUNET_free_non_null (networkIdDirectory); GNUNET_free_non_null (trustDirectory); return ret; } /* end of gnunet-service-peerinfo.c */