2 This file is part of GNUnet.
3 (C) 2009 Christian Grothoff (and other contributing authors)
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.
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.
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.
22 * @file dv/gnunet-service-dv.c
23 * @brief the distance vector service, primarily handles gossip of nearby
24 * peers and sending/receiving DV messages from core and decapsulating
27 * @author Christian Grothoff
28 * @author Nathan Evans
32 #include "gnunet_client_lib.h"
33 #include "gnunet_getopt_lib.h"
34 #include "gnunet_os_lib.h"
35 #include "gnunet_protocols.h"
36 #include "gnunet_service_lib.h"
37 #include "gnunet_core_service.h"
38 #include "gnunet_signal_lib.h"
39 #include "gnunet_util_lib.h"
43 * DV Service Context stuff goes here...
47 * Handle to the core service api.
49 static struct GNUNET_CORE_Handle *coreAPI;
52 * The identity of our peer.
54 const struct GNUNET_PeerIdentity *my_identity;
57 * The configuration for this service.
59 const struct GNUNET_CONFIGURATION_Handle *cfg;
62 * The scheduler for this service.
64 static struct GNUNET_SCHEDULER_Handle *sched;
67 * How often do we check about sending out more peer information (if
68 * we are connected to no peers previously).
70 #define GNUNET_DV_DEFAULT_SEND_INTERVAL (500 * GNUNET_CRON_MILLISECONDS)
73 * How long do we wait at most between sending out information?
75 #define GNUNET_DV_MAX_SEND_INTERVAL (5000 * GNUNET_CRON_MILLISECONDS)
78 * How long can we have not heard from a peer and
79 * still have it in our tables?
81 #define GNUNET_DV_PEER_EXPIRATION_TIME (3000 * GNUNET_CRON_SECONDS)
84 * Priority for gossip.
86 #define GNUNET_DV_DHT_GOSSIP_PRIORITY (GNUNET_EXTREME_PRIORITY / 10)
89 * How often should we check if expiration time has elapsed for
92 #define GNUNET_DV_MAINTAIN_FREQUENCY (5 * GNUNET_CRON_SECONDS)
95 * How long to allow a message to be delayed?
97 #define DV_DELAY (5000 * GNUNET_CRON_MILLISECONDS)
100 * Priority to use for DV data messages.
102 #define DV_PRIORITY 0
105 * The client, should be the DV plugin connected to us. Hopefully
106 * this client will never change, although if the plugin dies
107 * and returns for some reason it may happen.
109 static struct GNUNET_SERVER_Client * client_handle;
111 GNUNET_SCHEDULER_TaskIdentifier cleanup_task;
114 * Struct where neighbor information is stored.
116 struct DistantNeighbor *referees;
119 * Struct to hold information for updating existing neighbors
121 struct NeighborUpdateInfo
129 * The existing neighbor
131 struct DistantNeighbor *neighbor;
134 * The referrer of the possibly existing peer
136 struct DirectNeighbor *referrer;
139 * The time we heard about this peer
141 struct GNUNET_TIME_Absolute now;
145 * Struct where actual neighbor information is stored,
146 * referenced by min_heap and max_heap. Freeing dealt
147 * with when items removed from hashmap.
149 struct DirectNeighbor
152 * Identity of neighbor.
154 struct GNUNET_PeerIdentity identity;
157 * Head of DLL of nodes that this direct neighbor referred to us.
159 struct DistantNeighbor *referee_head;
162 * Tail of DLL of nodes that this direct neighbor referred to us.
164 struct DistantNeighbor *referee_tail;
167 * Is this one of the direct neighbors that we are "hiding"
175 * Struct where actual neighbor information is stored,
176 * referenced by min_heap and max_heap. Freeing dealt
177 * with when items removed from hashmap.
179 struct DistantNeighbor
182 * We keep distant neighbor's of the same referrer in a DLL.
184 struct DistantNeighbor *next;
187 * We keep distant neighbor's of the same referrer in a DLL.
189 struct DistantNeighbor *prev;
194 struct GNUNET_CONTAINER_HeapNode *min_loc;
199 struct GNUNET_CONTAINER_HeapNode *max_loc;
202 * Identity of referrer (next hop towards 'neighbor').
204 struct DirectNeighbor *referrer;
207 * Identity of neighbor.
209 struct GNUNET_PeerIdentity identity;
212 * Last time we received routing information from this peer
214 struct GNUNET_TIME_Absolute last_activity;
217 * Cost to neighbor, used for actual distance vector computations
222 * Random identifier *we* use for this peer, to be used as shortcut
223 * instead of sending full peer id for each message
228 * Random identifier the *referrer* uses for this peer.
230 unsigned int referrer_id;
233 * Is this one of the direct neighbors that we are "hiding"
243 struct GNUNET_DV_Context
246 * Map of PeerIdentifiers to 'struct GNUNET_dv_neighbor*'s for all
247 * directly connected peers.
249 struct GNUNET_CONTAINER_MultiHashMap *direct_neighbors;
252 * Map of PeerIdentifiers to 'struct GNUNET_dv_neighbor*'s for
253 * peers connected via DV (extended neighborhood). Does ALSO
254 * include any peers that are in 'direct_neighbors'; for those
255 * peers, the cost will be zero and the referrer all zeros.
257 struct GNUNET_CONTAINER_MultiHashMap *extended_neighbors;
260 * We use the min heap (min refers to cost) to prefer
261 * gossipping about peers with small costs.
263 struct GNUNET_CONTAINER_Heap *neighbor_min_heap;
266 * We use the max heap (max refers to cost) for general
267 * iterations over all peers and to remove the most costly
268 * connection if we have too many.
270 struct GNUNET_CONTAINER_Heap *neighbor_max_heap;
272 unsigned long long fisheye_depth;
274 unsigned long long max_table_size;
276 unsigned int send_interval;
278 unsigned int neighbor_id_loc;
283 static char shortID[5];
285 static struct GNUNET_DV_Context ctx;
287 struct FindDestinationContext
290 struct DistantNeighbor *dest;
295 * We've been given a target ID based on the random numbers that
296 * we assigned to our DV-neighborhood. Find the entry for the
297 * respective neighbor.
300 find_destination (void *cls,
301 struct GNUNET_CONTAINER_HeapNode *node,
302 void *element, GNUNET_CONTAINER_HeapCostType cost)
304 struct FindDestinationContext *fdc = cls;
305 struct DistantNeighbor *dn = element;
307 if (fdc->tid != dn->our_id)
314 * Function called to notify a client about the socket
315 * begin ready to queue more data. "buf" will be
316 * NULL and "size" zero if the socket was closed for
317 * writing in the meantime.
320 * @param size number of bytes available in buf
321 * @param buf where the callee should write the message
322 * @return number of bytes written to buf
324 size_t transmit_to_plugin (void *cls,
325 size_t size, void *buf)
327 struct GNUNET_DV_MessageReceived *msg = cls;
332 GNUNET_assert(size >= ntohs(msg->header.size));
334 memcpy(buf, msg, size);
340 void send_to_plugin(const struct GNUNET_PeerIdentity * sender, const struct GNUNET_MessageHeader *message, size_t message_size, struct DistantNeighbor *distant_neighbor)
342 struct GNUNET_DV_MessageReceived *received_msg;
345 if (ntohs(msg->size) < sizeof(struct GNUNET_DV_MessageReceived))
348 size = sizeof(struct GNUNET_DV_MessageReceived) + message_size + sizeof(struct GNUNET_PeerIdentity);
349 received_msg = GNUNET_malloc(size);
350 received_msg->header.size = htons(size);
351 received_msg->header.type = htons(GNUNET_MESSAGE_TYPE_TRANSPORT_DV_RECEIVE);
352 received_msg->sender_address_len = sizeof(struct GNUNET_PeerIdentity);
353 received_msg->distance = htonl(distant_neighbor->cost);
354 /* Set the sender in this message to be the original sender! */
355 memcpy(&received_msg->sender, &distant_neighbor->identity, sizeof(struct GNUNET_PeerIdentity));
356 /* Copy the intermediate sender to the end of the message, this is how the transport identifies this peer */
357 memcpy(&received_msg[1], sender, sizeof(struct GNUNET_PeerIdentity));
359 /* FIXME: Send to the client please */
360 GNUNET_SERVER_notify_transmit_ready (client_handle,
361 size, CLIENT_TRANSMIT_TIMEOUT,
362 &transmit_to_plugin, &received_msg);
367 * Core handler for dv data messages. Whatever this message
368 * contains all we really have to do is rip it out of its
369 * DV layering and give it to our pal the DV plugin to report
373 * @param peer peer which sent the message (immediate sender)
374 * @param message the message
375 * @param latency the latency of the connection we received the message from
376 * @param distance the distance to the immediate peer
378 static int handle_dv_data_message (void *cls,
379 const struct GNUNET_PeerIdentity * peer,
380 const struct GNUNET_MessageHeader * message,
381 struct GNUNET_TIME_Relative latency,
385 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
386 "%s: Receives %s message!\n", "dv", "DV DATA");
389 const p2p_dv_MESSAGE_Data *incoming = (const p2p_dv_MESSAGE_Data *) message;
390 const struct GNUNET_MessageHeader *packed_message = (const struct GNUNET_MessageHeader *) &incoming[1];
391 struct DirectNeighbor *dn;
392 struct DistantNeighbor *pos;
393 unsigned int sid; /* Sender id */
394 unsigned int tid; /* Target id */
395 struct GNUNET_PeerIdentity original_sender;
396 struct GNUNET_PeerIdentity destination;
397 struct FindDestinationContext fdc;
400 if ((ntohs (incoming->header.size) <
401 sizeof (p2p_dv_MESSAGE_Data) + sizeof (struct GNUNET_MessageHeader))
402 || (ntohs (incoming->header.size) !=
403 (sizeof (p2p_dv_MESSAGE_Data) + ntohs (packed_message->size))))
405 return GNUNET_SYSERR;
408 dn = GNUNET_CONTAINER_multihashmap_get (ctx.direct_neighbors,
414 sid = ntohl (incoming->sender);
415 pos = dn->referee_head;
416 while ((NULL != pos) && (pos->referrer_id != sid))
423 original_sender = pos->identity;
424 tid = ntohl (incoming->recipient);
429 /* FIXME: Will we support wrapped messages being these types? Probably not, they should
430 * be encrypted messages that need decrypting and junk like that.
432 GNUNET_break_op (ntohs (packed_message->type) != GNUNET_MESSAGE_TYPE_DV_GOSSIP);
433 GNUNET_break_op (ntohs (packed_message->type) != GNUNET_MESSAGE_TYPE_DV_DATA);
434 if ( (ntohs (packed_message->type) != GNUNET_MESSAGE_TYPE_DV_GOSSIP) &&
435 (ntohs (packed_message->type) != GNUNET_MESSAGE_TYPE_DV_DATA) )
437 /* FIXME: send the message, wrap it up and return it to the DV plugin */
438 /*coreAPI->loopback_send (&original_sender, (const char *) packed_message,
439 ntohs (packed_message->size), GNUNET_YES, NULL);*/
440 send_to_plugin(peer, packed_message, ntohs(packed_message->size), pos);
446 /* FIXME: this is the *only* per-request operation we have in DV
447 that is O(n) in relation to the number of connected peers; a
448 hash-table lookup could easily solve this (minor performance
452 GNUNET_CONTAINER_heap_iterate (ctx.neighbor_max_heap,
453 &find_destination, &fdc);
454 if (fdc.dest == NULL)
458 destination = fdc.dest->identity;
460 if (0 == memcmp (&destination, peer, sizeof (struct GNUNET_PeerIdentity)))
462 /* FIXME: create stat: routing loop-discard! */
466 /* FIXME: Can't send message on, we have to behave.
467 * We have to tell core we have a message for the next peer, and let
468 * transport do transport selection on how to get this message to 'em */
469 /*ret = send_message (&destination,
471 packed_message, DV_PRIORITY, DV_DELAY);*/
472 send_to_core(&destination, &original_sender, packed_message, DV_PRIORITY, DV_DELAY);
478 * Core handler for dv gossip messages. These will be used
479 * by us to create a HELLO message for the newly peer containing
480 * which direct peer we can connect through, and what the cost
481 * is. This HELLO will then be scheduled for validation by the
482 * transport service so that it can be used by all others.
485 * @param peer peer which sent the message (immediate sender)
486 * @param message the message
487 * @param latency the latency of the connection we received the message from
488 * @param distance the distance to the immediate peer
490 static int handle_dv_gossip_message (void *cls,
491 const struct GNUNET_PeerIdentity *peer,
492 const struct GNUNET_MessageHeader *message,
493 struct GNUNET_TIME_Relative latency,
497 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
498 "%s: Receives %s message!\n", "dv", "DV GOSSIP");
506 * Service server's handler for message send requests (which come
507 * bubbling up to us through the DV plugin).
510 * @param client identification of the client
511 * @param message the actual message
513 void send_dv_message (void *cls,
514 struct GNUNET_SERVER_Client * client,
515 const struct GNUNET_MessageHeader * message)
518 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
519 "%s: Receives %s message!\n", "dv", "SEND");
521 if (client_handle == NULL)
523 client_handle = client;
525 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
526 "%s: Setting initial client handle!\n", "dv");
529 else if (client_handle != client)
531 client_handle = client;
532 /* What should we do in this case, assert fail or just log the warning? */
533 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
534 "%s: Setting client handle (was a different client!)!\n", "dv");
537 GNUNET_SERVER_receive_done(client, GNUNET_OK);
542 * List of handlers for the messages understood by this
545 * Hmm... will we need to register some handlers with core and
546 * some handlers with our server here? Because core should be
547 * getting the incoming DV messages (from whichever lower level
548 * transport) and then our server should be getting messages
549 * from the dv_plugin, right?
551 static struct GNUNET_CORE_MessageHandler core_handlers[] = {
552 {&handle_dv_data_message, GNUNET_MESSAGE_TYPE_DV_DATA, 0},
553 {&handle_dv_gossip_message, GNUNET_MESSAGE_TYPE_DV_GOSSIP, 0},
558 static struct GNUNET_SERVER_MessageHandler plugin_handlers[] = {
559 {&send_dv_message, NULL, GNUNET_MESSAGE_TYPE_TRANSPORT_DV_SEND, 0},
565 * Task run during shutdown.
571 shutdown_task (void *cls,
572 const struct GNUNET_SCHEDULER_TaskContext *tc)
575 GNUNET_CORE_disconnect (coreAPI);
579 * To be called on core init/fail.
581 void core_init (void *cls,
582 struct GNUNET_CORE_Handle * server,
583 const struct GNUNET_PeerIdentity *identity,
584 const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded * publicKey)
589 GNUNET_SCHEDULER_cancel(sched, cleanup_task);
590 GNUNET_SCHEDULER_add_now(sched, &shutdown_task, NULL);
594 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
595 "%s: Core connection initialized, I am peer: %s\n", "dv", GNUNET_i2s(identity));
597 my_identity = identity;
603 * Iterator over hash map entries.
606 * @param key current key code
607 * @param value value in the hash map
608 * @return GNUNET_YES if we should continue to
612 static int update_matching_neighbors (void *cls,
613 const GNUNET_HashCode * key,
616 struct NeighborUpdateInfo * update_info = cls;
617 struct DirectNeighbor *direct_neighbor = value;
619 if (update_info->referrer == direct_neighbor) /* Direct neighbor matches, update it's info and return GNUNET_NO */
621 /* same referrer, cost change! */
622 GNUNET_CONTAINER_heap_update_cost (ctx.neighbor_max_heap,
623 update_info->neighbor->max_loc, update_info->cost);
624 GNUNET_CONTAINER_heap_update_cost (ctx.neighbor_min_heap,
625 update_info->neighbor->min_loc, update_info->cost);
626 update_info->neighbor->last_activity = update_info->now;
627 update_info->neighbor->cost = update_info->cost;
636 * Free a DistantNeighbor node, including removing it
637 * from the referer's list.
640 distant_neighbor_free (struct DistantNeighbor *referee)
642 struct DirectNeighbor *referrer;
644 referrer = referee->referrer;
645 if (referrer != NULL)
647 GNUNET_CONTAINER_DLL_remove (referrer->referee_head,
648 referrer->referee_tail, referee);
650 GNUNET_CONTAINER_heap_remove_node (ctx.neighbor_max_heap, referee->max_loc);
651 GNUNET_CONTAINER_heap_remove_node (ctx.neighbor_min_heap, referee->min_loc);
652 GNUNET_CONTAINER_multihashmap_remove_all (ctx.extended_neighbors,
653 &referee->identity.hashPubKey);
654 GNUNET_free (referee);
659 * Handles when a peer is either added due to being newly connected
660 * or having been gossiped about, also called when a cost for a neighbor
661 * needs to be updated.
663 * @param peer identity of the peer whose info is being added/updated
664 * @param peer_id id to use when sending to 'peer'
665 * @param referrer if this is a gossiped peer, who did we hear it from?
666 * @param cost the cost of communicating with this peer via 'referrer'
669 addUpdateNeighbor (const struct GNUNET_PeerIdentity * peer,
670 unsigned int referrer_peer_id,
671 struct DirectNeighbor *referrer, unsigned int cost)
673 struct DistantNeighbor *neighbor;
674 struct DistantNeighbor *max;
675 struct GNUNET_TIME_Absolute now;
676 struct NeighborUpdateInfo *neighbor_update;
679 now = GNUNET_TIME_absolute_get ();
680 our_id = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, RAND_MAX - 1) + 1;
682 neighbor = GNUNET_CONTAINER_multihashmap_get (ctx.extended_neighbors,
684 neighbor_update = GNUNET_malloc(sizeof(struct NeighborUpdateInfo));
685 neighbor_update->neighbor = neighbor;
686 neighbor_update->cost = cost;
687 neighbor_update->now = now;
688 neighbor_update->referrer = referrer;
690 /* Either we do not know this peer, or we already do but via a different immediate peer */
691 if ((neighbor == NULL) ||
692 (GNUNET_CONTAINER_multihashmap_get_multiple(ctx.extended_neighbors,
694 &update_matching_neighbors,
695 neighbor_update) != GNUNET_SYSERR))
698 if (cost > ctx.fisheye_depth)
703 if (ctx.max_table_size <=
704 GNUNET_CONTAINER_multihashmap_size (ctx.extended_neighbors))
706 /* remove most expensive entry */
707 max = GNUNET_CONTAINER_heap_peek (ctx.neighbor_max_heap);
708 if (cost > max->cost)
710 /* new entry most expensive, don't create */
715 /* only free if this is not a direct connection;
716 we could theoretically have more direct
717 connections than DV entries allowed total! */
718 distant_neighbor_free (max);
722 neighbor = GNUNET_malloc (sizeof (struct DistantNeighbor));
723 GNUNET_CONTAINER_DLL_insert (referrer->referee_head,
724 referrer->referee_tail, neighbor);
725 neighbor->max_loc = GNUNET_CONTAINER_heap_insert (ctx.neighbor_max_heap,
727 neighbor->min_loc = GNUNET_CONTAINER_heap_insert (ctx.neighbor_min_heap,
729 neighbor->referrer = referrer;
730 memcpy (&neighbor->identity, peer, sizeof (struct GNUNET_PeerIdentity));
731 neighbor->last_activity = now;
732 neighbor->cost = cost;
733 neighbor->referrer_id = referrer_peer_id;
734 neighbor->our_id = our_id;
736 (cost == 0) ? (GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 4) ==
738 GNUNET_CONTAINER_multihashmap_put (ctx.extended_neighbors, &peer->hashPubKey,
740 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
745 /* Old logic to remove entry and replace, not needed now as we only want to remove when full
746 * or when the referring peer disconnects from us.
749 GNUNET_DLL_remove (neighbor->referrer->referee_head,
750 neighbor->referrer->referee_tail, neighbor);
751 neighbor->referrer = referrer;
752 GNUNET_DLL_insert (referrer->referee_head,
753 referrer->referee_tail, neighbor);
754 GNUNET_CONTAINER_heap_update_cost (ctx.neighbor_max_heap,
755 neighbor->max_loc, cost);
756 GNUNET_CONTAINER_heap_update_cost (ctx.neighbor_min_heap,
757 neighbor->min_loc, cost);
758 neighbor->referrer_id = referrer_peer_id;
759 neighbor->last_activity = now;
760 neighbor->cost = cost;
766 * Method called whenever a given peer either connects.
769 * @param peer peer identity this notification is about
770 * @param latency reported latency of the connection with peer
771 * @param distance reported distance (DV) to peer
773 void handle_core_connect (void *cls,
774 const struct GNUNET_PeerIdentity * peer,
775 struct GNUNET_TIME_Relative latency,
778 struct DirectNeighbor *neighbor;
780 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
781 "%s: Receives core connect message for peer %s distance %d!\n", "dv", GNUNET_i2s(peer), distance);
784 neighbor = GNUNET_malloc (sizeof (struct DirectNeighbor));
785 memcpy (&neighbor->identity, peer, sizeof (struct GNUNET_PeerIdentity));
786 GNUNET_CONTAINER_multihashmap_put (ctx.direct_neighbors,
788 neighbor, GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
789 addUpdateNeighbor (peer, 0, neighbor, 0);
793 * Method called whenever a given peer either connects.
796 * @param peer peer identity this notification is about
798 void handle_core_disconnect (void *cls,
799 const struct GNUNET_PeerIdentity * peer)
801 struct DirectNeighbor *neighbor;
802 struct DistantNeighbor *referee;
805 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
806 "%s: Receives core peer disconnect message!\n", "dv");
810 GNUNET_CONTAINER_multihashmap_get (ctx.direct_neighbors, &peer->hashPubKey);
811 if (neighbor == NULL)
815 while (NULL != (referee = neighbor->referee_head))
816 distant_neighbor_free (referee);
817 GNUNET_assert (neighbor->referee_tail == NULL);
818 GNUNET_CONTAINER_multihashmap_remove (ctx.direct_neighbors,
819 &peer->hashPubKey, neighbor);
820 GNUNET_free (neighbor);
825 * Process dv requests.
828 * @param scheduler scheduler to use
829 * @param server the initialized server
830 * @param c configuration to use
834 struct GNUNET_SCHEDULER_Handle *scheduler,
835 struct GNUNET_SERVER_Handle *server,
836 const struct GNUNET_CONFIGURATION_Handle *c)
838 struct GNUNET_TIME_Relative timeout;
840 timeout = GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, 5);
843 GNUNET_SERVER_add_handlers (server, plugin_handlers);
845 GNUNET_CORE_connect (sched,
848 NULL, /* FIXME: anything we want to pass around? */
850 NULL, /* Don't care about pre-connects */
851 &handle_core_connect,
852 &handle_core_disconnect,
861 /* load (server); Huh? */
863 /* Scheduled the task to clean up when shutdown is called */
865 cleanup_task = GNUNET_SCHEDULER_add_delayed (sched,
866 GNUNET_TIME_UNIT_FOREVER_REL,
873 * The main function for the dv service.
875 * @param argc number of arguments from the command line
876 * @param argv command line arguments
877 * @return 0 ok, 1 on error
880 main (int argc, char *const *argv)
883 GNUNET_SERVICE_run (argc,
886 GNUNET_SERVICE_OPTION_NONE,
887 &run, NULL)) ? 0 : 1;