X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=src%2Farm%2Fgnunet-service-arm.c;h=19088c5cb7ec57bd526ead12252651986169bba1;hb=03512957fb04969d08fb7eac0952a747aa9596ae;hp=d99020d843cd51c5aa2f77bc5912eccdb206e052;hpb=d234059375e77fe46c57e98751dc8491dff46e85;p=oweals%2Fgnunet.git diff --git a/src/arm/gnunet-service-arm.c b/src/arm/gnunet-service-arm.c index d99020d84..19088c5cb 100644 --- a/src/arm/gnunet-service-arm.c +++ b/src/arm/gnunet-service-arm.c @@ -1,6 +1,6 @@ /* This file is part of GNUnet. - (C) 2009, 2010 Christian Grothoff (and other contributing authors) + Copyright (C) 2009-2011, 2015, 2016 GNUnet e.V. GNUnet is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published @@ -14,40 +14,46 @@ 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. + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. */ /** * @file arm/gnunet-service-arm.c * @brief the automated restart manager service * @author Christian Grothoff - * - * TODO: - * - need to test auto-restart code on configuration changes; - * - should refine restart code to check if *relevant* parts of the - * configuration were changed (anything in the section for the service) - * - should have a way to specify dependencies between services and - * manage restarts of groups of services - * - * + install handler for disconnecting clients!? */ #include "platform.h" #include "gnunet_util_lib.h" +#include "gnunet_arm_service.h" #include "gnunet_protocols.h" -#include "gnunet-service-arm.h" #include "arm.h" +#define LOG(kind,...) GNUNET_log_from (kind, "util", __VA_ARGS__) + +#define LOG_STRERROR(kind,syscall) GNUNET_log_from_strerror (kind, "util", syscall) + +#if HAVE_WAIT4 /** - * Check for configuration file changes every 5s. + * Name of the file for writing resource utilization summaries to. */ -#define MAINT_FREQUENCY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5) +static char *wait_filename; /** - * Threshold after which exponential backoff shouldn't increase (in ms); 30m + * Handle for the file for writing resource summaries. */ -#define EXPONENTIAL_BACKOFF_THRESHOLD (1000 * 60 * 30) +static FILE *wait_file; +#endif + + +/** + * How many messages do we queue up at most for optional + * notifications to a client? (this can cause notifications + * about outgoing messages to be dropped). + */ +#define MAX_NOTIFY_QUEUE 1024 + /** * List of our services. @@ -55,6 +61,49 @@ struct ServiceList; +/** + * Record with information about a listen socket we have open. + */ +struct ServiceListeningInfo +{ + /** + * This is a linked list. + */ + struct ServiceListeningInfo *next; + + /** + * This is a linked list. + */ + struct ServiceListeningInfo *prev; + + /** + * Address this socket is listening on. + */ + struct sockaddr *service_addr; + + /** + * Service this listen socket is for. + */ + struct ServiceList *sl; + + /** + * Number of bytes in @e service_addr + */ + socklen_t service_addr_len; + + /** + * Our listening socket. + */ + struct GNUNET_NETWORK_Handle *listen_socket; + + /** + * Task doing the accepting. + */ + struct GNUNET_SCHEDULER_Task *accept_task; + +}; + + /** * List of our services. */ @@ -70,6 +119,16 @@ struct ServiceList */ struct ServiceList *prev; + /** + * Linked list of listen sockets associated with this service. + */ + struct ServiceListeningInfo *listen_head; + + /** + * Linked list of listen sockets associated with this service. + */ + struct ServiceListeningInfo *listen_tail; + /** * Name of the service. */ @@ -89,18 +148,17 @@ struct ServiceList * Client to notify upon kill completion (waitpid), NULL * if we should simply restart the process. */ - struct GNUNET_SERVER_Client *killing_client; + struct GNUNET_SERVICE_Client *killing_client; /** - * Process structure pointer of the child. + * ID of the request that killed the service (for reporting back). */ - struct GNUNET_OS_Process *proc; + uint64_t killing_client_request_id; /** - * Last time the config of this service was - * modified. + * Process structure pointer of the child. */ - time_t mtime; + struct GNUNET_OS_Process *proc; /** * Process exponential backoff time @@ -110,8 +168,26 @@ struct ServiceList /** * Absolute time at which the process is scheduled to restart in case of death */ - struct GNUNET_TIME_Absolute restartAt; + struct GNUNET_TIME_Absolute restart_at; + /** + * Time we asked the service to shut down (used to calculate time it took + * the service to terminate). + */ + struct GNUNET_TIME_Absolute killed_at; + + /** + * Is this service to be started by default (or did a client tell us explicitly + * to start it)? #GNUNET_NO if the service is started only upon 'accept' on a + * listen socket or possibly explicitly by a client changing the value. + */ + int force_start; + + /** + * Should we use pipes to signal this process? (YES for Java binaries and if we + * are on Windoze). + */ + int pipe_control; }; /** @@ -142,15 +218,13 @@ static char *final_option; /** * ID of task called whenever we get a SIGCHILD. */ -static GNUNET_SCHEDULER_TaskIdentifier child_death_task; +static struct GNUNET_SCHEDULER_Task *child_death_task; /** * ID of task called whenever the timeout for restarting a child * expires. */ -static GNUNET_SCHEDULER_TaskIdentifier child_restart_task; - - +static struct GNUNET_SCHEDULER_Task *child_restart_task; /** * Pipe used to communicate shutdown via signal. @@ -158,92 +232,417 @@ static GNUNET_SCHEDULER_TaskIdentifier child_restart_task; static struct GNUNET_DISK_PipeHandle *sigpipe; /** - * Reading end of the signal pipe. + * Are we in shutdown mode? */ -static const struct GNUNET_DISK_FileHandle *pr; +static int in_shutdown; /** - * Are we in shutdown mode? + * Are we starting user services? */ -static int in_shutdown; +static int start_user = GNUNET_YES; +/** + * Are we starting system services? + */ +static int start_system = GNUNET_YES; /** - * Handle to our server instance. Our server is a bit special in that + * Handle to our service instance. Our service is a bit special in that * its service is not immediately stopped once we get a shutdown * request (since we need to continue service until all of our child - * processes are dead). This handle is used to shut down the server + * processes are dead). This handle is used to shut down the service * (and thus trigger process termination) once all child processes are * also dead. A special option in the ARM configuration modifies the * behaviour of the service implementation to not do the shutdown * immediately. */ -static struct GNUNET_SERVER_Handle *server; +static struct GNUNET_SERVICE_Handle *service; + +/** + * Context for notifications we need to send to our clients. + */ +static struct GNUNET_NotificationContext *notifier; /** - * If the configuration file changes, restart tasks that depended on that - * option. + * Add the given UNIX domain path as an address to the + * list (as the first entry). * - * @param cls closure, NULL if we need to self-restart - * @param tc context + * @param saddrs array to update + * @param saddrlens where to store the address length + * @param unixpath path to add + * @param abstract #GNUNET_YES to add an abstract UNIX domain socket. This + * parameter is ignore on systems other than LINUX */ static void -config_change_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +add_unixpath (struct sockaddr **saddrs, + socklen_t *saddrlens, + const char *unixpath, + int abstract) { - struct ServiceList *pos; - struct stat sbuf; +#ifdef AF_UNIX + struct sockaddr_un *un; - pos = running_head; - while (pos != NULL) - { - /* FIXME: this test for config change may be a bit too coarse grained */ - if ((0 == STAT (pos->config, &sbuf)) && (pos->mtime < sbuf.st_mtime) && - (pos->proc != NULL)) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _ - ("Restarting service `%s' due to configuration file change.\n")); - if (0 != GNUNET_OS_process_kill (pos->proc, SIGTERM)) - GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill"); - else - pos->backoff = GNUNET_TIME_UNIT_MILLISECONDS; - } - pos = pos->next; - } + un = GNUNET_new (struct sockaddr_un); + un->sun_family = AF_UNIX; + strncpy (un->sun_path, unixpath, sizeof (un->sun_path) - 1); +#ifdef LINUX + if (GNUNET_YES == abstract) + un->sun_path[0] = '\0'; +#endif +#if HAVE_SOCKADDR_UN_SUN_LEN + un->sun_len = (u_char) sizeof (struct sockaddr_un); +#endif + *saddrs = (struct sockaddr *) un; + *saddrlens = sizeof (struct sockaddr_un); +#else + /* this function should never be called + * unless AF_UNIX is defined! */ + GNUNET_assert (0); +#endif } /** - * Transmit a status result message. + * Get the list of addresses that a server for the given service + * should bind to. * - * @param cls pointer to "unit16_t*" with message type - * @param size number of bytes available in buf - * @param buf where to copy the message, NULL on error - * @return number of bytes copied to buf + * @param service_name name of the service + * @param cfg configuration (which specifies the addresses) + * @param addrs set (call by reference) to an array of pointers to the + * addresses the server should bind to and listen on; the + * array will be NULL-terminated (on success) + * @param addr_lens set (call by reference) to an array of the lengths + * of the respective `struct sockaddr` struct in the @a addrs + * array (on success) + * @return number of addresses found on success, + * #GNUNET_SYSERR if the configuration + * did not specify reasonable finding information or + * if it specified a hostname that could not be resolved; + * #GNUNET_NO if the number of addresses configured is + * zero (in this case, `*addrs` and `*addr_lens` will be + * set to NULL). */ -static size_t -write_result (void *cls, size_t size, void *buf) +static int +get_server_addresses (const char *service_name, + const struct GNUNET_CONFIGURATION_Handle *cfg, + struct sockaddr ***addrs, + socklen_t ** addr_lens) { - uint16_t *res = cls; - struct GNUNET_MessageHeader *msg; + int disablev6; + struct GNUNET_NETWORK_Handle *desc; + unsigned long long port; + char *unixpath; + struct addrinfo hints; + struct addrinfo *res; + struct addrinfo *pos; + struct addrinfo *next; + unsigned int i; + int resi; + int ret; + int abstract; + struct sockaddr **saddrs; + socklen_t *saddrlens; + char *hostname; + + *addrs = NULL; + *addr_lens = NULL; + desc = NULL; + if (GNUNET_CONFIGURATION_have_value (cfg, service_name, "DISABLEV6")) + { + if (GNUNET_SYSERR == + (disablev6 = + GNUNET_CONFIGURATION_get_value_yesno (cfg, service_name, "DISABLEV6"))) + return GNUNET_SYSERR; + } + else + disablev6 = GNUNET_NO; - if (buf == NULL) + if (! disablev6) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - _("Could not send status result to client\n")); - return 0; /* error, not much we can do */ + /* probe IPv6 support */ + desc = GNUNET_NETWORK_socket_create (PF_INET6, SOCK_STREAM, 0); + if (NULL == desc) + { + if ((ENOBUFS == errno) || (ENOMEM == errno) || (ENFILE == errno) || + (EACCES == errno)) + { + LOG_STRERROR (GNUNET_ERROR_TYPE_ERROR, "socket"); + return GNUNET_SYSERR; + } + LOG (GNUNET_ERROR_TYPE_INFO, + _("Disabling IPv6 support for service `%s', failed to create IPv6 socket: %s\n"), + service_name, STRERROR (errno)); + disablev6 = GNUNET_YES; + } + else + { + GNUNET_break (GNUNET_OK == GNUNET_NETWORK_socket_close (desc)); + desc = NULL; + } + } + + port = 0; + if (GNUNET_CONFIGURATION_have_value (cfg, service_name, "PORT")) + { + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (cfg, service_name, + "PORT", &port)) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Require valid port number for service `%s' in configuration!\n"), + service_name); + } + if (port > 65535) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Require valid port number for service `%s' in configuration!\n"), + service_name); + return GNUNET_SYSERR; + } + } + + if (GNUNET_CONFIGURATION_have_value (cfg, service_name, "BINDTO")) + { + GNUNET_break (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (cfg, service_name, + "BINDTO", &hostname)); + } + else + hostname = NULL; + + unixpath = NULL; + abstract = GNUNET_NO; +#ifdef AF_UNIX + if ((GNUNET_YES == + GNUNET_CONFIGURATION_have_value (cfg, service_name, "UNIXPATH")) && + (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_filename (cfg, service_name, "UNIXPATH", + &unixpath)) && + (0 < strlen (unixpath))) + { + /* probe UNIX support */ + struct sockaddr_un s_un; + + if (strlen (unixpath) >= sizeof (s_un.sun_path)) + { + LOG (GNUNET_ERROR_TYPE_WARNING, + _("UNIXPATH `%s' too long, maximum length is %llu\n"), unixpath, + (unsigned long long) sizeof (s_un.sun_path)); + unixpath = GNUNET_NETWORK_shorten_unixpath (unixpath); + LOG (GNUNET_ERROR_TYPE_INFO, + _("Using `%s' instead\n"), + unixpath); + } +#ifdef LINUX + abstract = GNUNET_CONFIGURATION_get_value_yesno (cfg, + "TESTING", + "USE_ABSTRACT_SOCKETS"); + if (GNUNET_SYSERR == abstract) + abstract = GNUNET_NO; +#endif + if ((GNUNET_YES != abstract) + && (GNUNET_OK != + GNUNET_DISK_directory_create_for_file (unixpath))) + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "mkdir", + unixpath); + } + if (NULL != unixpath) + { + desc = GNUNET_NETWORK_socket_create (AF_UNIX, SOCK_STREAM, 0); + if (NULL == desc) + { + if ((ENOBUFS == errno) || (ENOMEM == errno) || (ENFILE == errno) || + (EACCES == errno)) + { + LOG_STRERROR (GNUNET_ERROR_TYPE_ERROR, "socket"); + GNUNET_free_non_null (hostname); + GNUNET_free (unixpath); + return GNUNET_SYSERR; + } + LOG (GNUNET_ERROR_TYPE_INFO, + _("Disabling UNIX domain socket support for service `%s', failed to create UNIX domain socket: %s\n"), + service_name, + STRERROR (errno)); + GNUNET_free (unixpath); + unixpath = NULL; + } + else + { + GNUNET_break (GNUNET_OK == GNUNET_NETWORK_socket_close (desc)); + desc = NULL; + } } -#if DEBUG_ARM - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending status response %u to client\n", - (unsigned int) *res); #endif - GNUNET_assert (size >= sizeof (struct GNUNET_MessageHeader)); - msg = buf; - msg->size = htons (sizeof (struct GNUNET_MessageHeader)); - msg->type = htons (*res); - GNUNET_free (res); - return sizeof (struct GNUNET_MessageHeader); + + if ((0 == port) && (NULL == unixpath)) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Have neither PORT nor UNIXPATH for service `%s', but one is required\n"), + service_name); + GNUNET_free_non_null (hostname); + return GNUNET_SYSERR; + } + if (0 == port) + { + saddrs = GNUNET_malloc (2 * sizeof (struct sockaddr *)); + saddrlens = GNUNET_malloc (2 * sizeof (socklen_t)); + add_unixpath (saddrs, saddrlens, unixpath, abstract); + GNUNET_free_non_null (unixpath); + GNUNET_free_non_null (hostname); + *addrs = saddrs; + *addr_lens = saddrlens; + return 1; + } + + if (NULL != hostname) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Resolving `%s' since that is where `%s' will bind to.\n", + hostname, + service_name); + memset (&hints, 0, sizeof (struct addrinfo)); + if (disablev6) + hints.ai_family = AF_INET; + hints.ai_protocol = IPPROTO_TCP; + if ((0 != (ret = getaddrinfo (hostname, NULL, &hints, &res))) || + (NULL == res)) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Failed to resolve `%s': %s\n"), + hostname, + gai_strerror (ret)); + GNUNET_free (hostname); + GNUNET_free_non_null (unixpath); + return GNUNET_SYSERR; + } + next = res; + i = 0; + while (NULL != (pos = next)) + { + next = pos->ai_next; + if ((disablev6) && (pos->ai_family == AF_INET6)) + continue; + i++; + } + if (0 == i) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Failed to find %saddress for `%s'.\n"), + disablev6 ? "IPv4 " : "", + hostname); + freeaddrinfo (res); + GNUNET_free (hostname); + GNUNET_free_non_null (unixpath); + return GNUNET_SYSERR; + } + resi = i; + if (NULL != unixpath) + resi++; + saddrs = GNUNET_malloc ((resi + 1) * sizeof (struct sockaddr *)); + saddrlens = GNUNET_malloc ((resi + 1) * sizeof (socklen_t)); + i = 0; + if (NULL != unixpath) + { + add_unixpath (saddrs, saddrlens, unixpath, abstract); + i++; + } + next = res; + while (NULL != (pos = next)) + { + next = pos->ai_next; + if ((disablev6) && (AF_INET6 == pos->ai_family)) + continue; + if ((IPPROTO_TCP != pos->ai_protocol) && (0 != pos->ai_protocol)) + continue; /* not TCP */ + if ((SOCK_STREAM != pos->ai_socktype) && (0 != pos->ai_socktype)) + continue; /* huh? */ + LOG (GNUNET_ERROR_TYPE_DEBUG, "Service `%s' will bind to `%s'\n", + service_name, GNUNET_a2s (pos->ai_addr, pos->ai_addrlen)); + if (AF_INET == pos->ai_family) + { + GNUNET_assert (sizeof (struct sockaddr_in) == pos->ai_addrlen); + saddrlens[i] = pos->ai_addrlen; + saddrs[i] = GNUNET_malloc (saddrlens[i]); + GNUNET_memcpy (saddrs[i], pos->ai_addr, saddrlens[i]); + ((struct sockaddr_in *) saddrs[i])->sin_port = htons (port); + } + else + { + GNUNET_assert (AF_INET6 == pos->ai_family); + GNUNET_assert (sizeof (struct sockaddr_in6) == pos->ai_addrlen); + saddrlens[i] = pos->ai_addrlen; + saddrs[i] = GNUNET_malloc (saddrlens[i]); + GNUNET_memcpy (saddrs[i], pos->ai_addr, saddrlens[i]); + ((struct sockaddr_in6 *) saddrs[i])->sin6_port = htons (port); + } + i++; + } + GNUNET_free (hostname); + freeaddrinfo (res); + resi = i; + } + else + { + /* will bind against everything, just set port */ + if (disablev6) + { + /* V4-only */ + resi = 1; + if (NULL != unixpath) + resi++; + i = 0; + saddrs = GNUNET_malloc ((resi + 1) * sizeof (struct sockaddr *)); + saddrlens = GNUNET_malloc ((resi + 1) * sizeof (socklen_t)); + if (NULL != unixpath) + { + add_unixpath (saddrs, saddrlens, unixpath, abstract); + i++; + } + saddrlens[i] = sizeof (struct sockaddr_in); + saddrs[i] = GNUNET_malloc (saddrlens[i]); +#if HAVE_SOCKADDR_IN_SIN_LEN + ((struct sockaddr_in *) saddrs[i])->sin_len = saddrlens[i]; +#endif + ((struct sockaddr_in *) saddrs[i])->sin_family = AF_INET; + ((struct sockaddr_in *) saddrs[i])->sin_port = htons (port); + } + else + { + /* dual stack */ + resi = 2; + if (NULL != unixpath) + resi++; + saddrs = GNUNET_malloc ((resi + 1) * sizeof (struct sockaddr *)); + saddrlens = GNUNET_malloc ((resi + 1) * sizeof (socklen_t)); + i = 0; + if (NULL != unixpath) + { + add_unixpath (saddrs, saddrlens, unixpath, abstract); + i++; + } + saddrlens[i] = sizeof (struct sockaddr_in6); + saddrs[i] = GNUNET_malloc (saddrlens[i]); +#if HAVE_SOCKADDR_IN_SIN_LEN + ((struct sockaddr_in6 *) saddrs[i])->sin6_len = saddrlens[0]; +#endif + ((struct sockaddr_in6 *) saddrs[i])->sin6_family = AF_INET6; + ((struct sockaddr_in6 *) saddrs[i])->sin6_port = htons (port); + i++; + saddrlens[i] = sizeof (struct sockaddr_in); + saddrs[i] = GNUNET_malloc (saddrlens[i]); +#if HAVE_SOCKADDR_IN_SIN_LEN + ((struct sockaddr_in *) saddrs[i])->sin_len = saddrlens[1]; +#endif + ((struct sockaddr_in *) saddrs[i])->sin_family = AF_INET; + ((struct sockaddr_in *) saddrs[i])->sin_port = htons (port); + } + } + GNUNET_free_non_null (unixpath); + *addrs = saddrs; + *addr_lens = saddrlens; + return resi; } @@ -253,32 +652,312 @@ write_result (void *cls, size_t size, void *buf) * * @param client who is being signalled * @param name name of the service + * @param request_id id of the request that is being responded to. * @param result message type to send * @return NULL if it was not found */ static void -signal_result (struct GNUNET_SERVER_Client *client, const char *name, - uint16_t result) +signal_result (struct GNUNET_SERVICE_Client *client, + const char *name, + uint64_t request_id, + enum GNUNET_ARM_Result result) { - uint16_t *res; + struct GNUNET_MQ_Envelope *env; + struct GNUNET_ARM_ResultMessage *msg; + + env = GNUNET_MQ_msg (msg, + GNUNET_MESSAGE_TYPE_ARM_RESULT); + msg->result = htonl (result); + msg->arm_msg.request_id = GNUNET_htonll (request_id); + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), + env); +} - if (NULL == client) + +/** + * Tell all clients about status change of a service. + * + * @param name name of the service + * @param status message type to send + * @param unicast if not NULL, send to this client only. + * otherwise, send to all clients in the notifier + */ +static void +broadcast_status (const char *name, + enum GNUNET_ARM_ServiceStatus status, + struct GNUNET_SERVICE_Client *unicast) +{ + struct GNUNET_MQ_Envelope *env; + struct GNUNET_ARM_StatusMessage *msg; + size_t namelen; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending status %u of service `%s' to client\n", + (unsigned int) status, + name); + namelen = strlen (name) + 1; + env = GNUNET_MQ_msg_extra (msg, + namelen, + GNUNET_MESSAGE_TYPE_ARM_STATUS); + msg->status = htonl ((uint32_t) (status)); + GNUNET_memcpy ((char *) &msg[1], + name, + namelen); + if (NULL == unicast) { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _("Not sending status result to client: no client known\n")); - return; + if (NULL != notifier) + GNUNET_notification_context_broadcast (notifier, + &msg->header, + GNUNET_YES); + GNUNET_MQ_discard (env); } -#if DEBUG_ARM - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Telling client that service `%s' is now %s\n", name, - result == GNUNET_MESSAGE_TYPE_ARM_IS_DOWN ? "down" : "up"); + else + { + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (unicast), + env); + } +} + + +/** + * Actually start the process for the given service. + * + * @param sl identifies service to start + * @param client that asked to start the service (may be NULL) + * @param request_id id of the request in response to which the process is + * being started. 0 if starting was not requested. + */ +static void +start_process (struct ServiceList *sl, + struct GNUNET_SERVICE_Client *client, + uint64_t request_id) +{ + char *loprefix; + char *options; + int use_debug; + int is_simple_service; + struct ServiceListeningInfo *sli; + SOCKTYPE *lsocks; + unsigned int ls; + char *binary; + char *quotedbinary; + + /* calculate listen socket list */ + lsocks = NULL; + ls = 0; + for (sli = sl->listen_head; NULL != sli; sli = sli->next) + { + GNUNET_array_append (lsocks, ls, + GNUNET_NETWORK_get_fd (sli->listen_socket)); + if (NULL != sli->accept_task) + { + GNUNET_SCHEDULER_cancel (sli->accept_task); + sli->accept_task = NULL; + } + } +#if WINDOWS + GNUNET_array_append (lsocks, + ls, + INVALID_SOCKET); +#else + GNUNET_array_append (lsocks, + ls, + -1); #endif - res = GNUNET_malloc (sizeof (uint16_t)); - *res = result; - GNUNET_SERVER_notify_transmit_ready (client, - sizeof (struct GNUNET_MessageHeader), - GNUNET_TIME_UNIT_FOREVER_REL, - &write_result, res); + + /* obtain configuration */ + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + sl->name, + "PREFIX", + &loprefix)) + loprefix = GNUNET_strdup (prefix_command); + else + loprefix = GNUNET_CONFIGURATION_expand_dollar (cfg, + loprefix); + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + sl->name, + "OPTIONS", + &options)) + options = NULL; + else + options = GNUNET_CONFIGURATION_expand_dollar (cfg, + options); + { + char *new_options; + char *optpos; + char *fin_options; + + fin_options = GNUNET_strdup (final_option); + /* replace '{}' with service name */ + while (NULL != (optpos = strstr (fin_options, + "{}"))) + { + /* terminate string at opening parenthesis */ + *optpos = 0; + GNUNET_asprintf (&new_options, + "%s%s%s", + fin_options, + sl->name, + optpos + 2); + GNUNET_free (fin_options); + fin_options = new_options; + } + if (NULL != options) + { + /* combine "fin_options" with "options" */ + optpos = options; + GNUNET_asprintf (&options, + "%s %s", + fin_options, + optpos); + GNUNET_free (fin_options); + GNUNET_free (optpos); + } + else + { + /* only have "fin_options", use that */ + options = fin_options; + } + } + options = GNUNET_CONFIGURATION_expand_dollar (cfg, + options); + use_debug = GNUNET_CONFIGURATION_get_value_yesno (cfg, + sl->name, + "DEBUG"); + { + const char *service_type = NULL; + const char *choices[] = { "GNUNET", "SIMPLE", NULL }; + + is_simple_service = GNUNET_NO; + if ( (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_choice (cfg, + sl->name, + "TYPE", + choices, + &service_type)) && + (0 == strcasecmp (service_type, "SIMPLE")) ) + is_simple_service = GNUNET_YES; + } + + GNUNET_assert (NULL == sl->proc); + if (GNUNET_YES == is_simple_service) + { + /* A simple service will receive no GNUnet specific + command line options. */ + binary = GNUNET_strdup (sl->binary); + binary = GNUNET_CONFIGURATION_expand_dollar (cfg, binary); + GNUNET_asprintf ("edbinary, + "\"%s\"", + sl->binary); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Starting simple service `%s' using binary `%s'\n", + sl->name, sl->binary); + /* FIXME: dollar expansion should only be done outside + * of ''-quoted strings, escaping should be considered. */ + if (NULL != options) + options = GNUNET_CONFIGURATION_expand_dollar (cfg, options); + sl->proc = + GNUNET_OS_start_process_s (sl->pipe_control, + GNUNET_OS_INHERIT_STD_OUT_AND_ERR, + lsocks, + loprefix, + quotedbinary, + options, + NULL); + } + else + { + /* actually start process */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Starting service `%s' using binary `%s' and configuration `%s'\n", + sl->name, sl->binary, sl->config); + binary = GNUNET_OS_get_libexec_binary_path (sl->binary); + GNUNET_asprintf ("edbinary, + "\"%s\"", + binary); + + if (GNUNET_YES == use_debug) + { + if (NULL == sl->config) + sl->proc = + GNUNET_OS_start_process_s (sl->pipe_control, + GNUNET_OS_INHERIT_STD_OUT_AND_ERR, + lsocks, + loprefix, + quotedbinary, + "-L", "DEBUG", + options, + NULL); + else + sl->proc = + GNUNET_OS_start_process_s (sl->pipe_control, + GNUNET_OS_INHERIT_STD_OUT_AND_ERR, + lsocks, + loprefix, + quotedbinary, + "-c", sl->config, + "-L", "DEBUG", + options, + NULL); + } + else + { + if (NULL == sl->config) + sl->proc = + GNUNET_OS_start_process_s (sl->pipe_control, + GNUNET_OS_INHERIT_STD_OUT_AND_ERR, + lsocks, + loprefix, + quotedbinary, + options, + NULL); + else + sl->proc = + GNUNET_OS_start_process_s (sl->pipe_control, + GNUNET_OS_INHERIT_STD_OUT_AND_ERR, + lsocks, + loprefix, + quotedbinary, + "-c", sl->config, + options, + NULL); + } + } + GNUNET_free (binary); + GNUNET_free (quotedbinary); + if (NULL == sl->proc) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Failed to start service `%s'\n"), + sl->name); + if (client) + signal_result (client, + sl->name, + request_id, + GNUNET_ARM_RESULT_START_FAILED); + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _("Starting service `%s'\n"), + sl->name); + broadcast_status (sl->name, + GNUNET_ARM_SERVICE_STARTING, + NULL); + if (client) + signal_result (client, + sl->name, + request_id, + GNUNET_ARM_RESULT_STARTING); + } + /* clean up */ + GNUNET_free (loprefix); + GNUNET_free (options); + GNUNET_array_grow (lsocks, + ls, + 0); } @@ -292,341 +971,493 @@ signal_result (struct GNUNET_SERVER_Client *client, const char *name, static struct ServiceList * find_service (const char *name) { - struct ServiceList *pos; + struct ServiceList *sl; - pos = running_head; - while (pos != NULL) - { - if (0 == strcmp (pos->name, name)) - return pos; - pos = pos->next; - } + sl = running_head; + while (sl != NULL) + { + if (0 == strcasecmp (sl->name, name)) + return sl; + sl = sl->next; + } return NULL; } /** - * Remove and free an entry in the service list. + * First connection has come to the listening socket associated with the service, + * create the service in order to relay the incoming connection to it * - * @param pos entry to free + * @param cls callback data, `struct ServiceListeningInfo` describing a listen socket */ static void -free_service (struct ServiceList *pos) +accept_connection (void *cls) { - GNUNET_CONTAINER_DLL_remove (running_head, running_tail, pos); - GNUNET_free_non_null (pos->config); - GNUNET_free_non_null (pos->binary); - GNUNET_free (pos->name); - GNUNET_free (pos); -} - + struct ServiceListeningInfo *sli = cls; + struct ServiceList *sl = sli->sl; -#include "do_start_process.c" + sli->accept_task = NULL; + GNUNET_assert (GNUNET_NO == in_shutdown); + start_process (sl, NULL, 0); +} /** - * Actually start the process for the given service. + * Creating a listening socket for each of the service's addresses and + * wait for the first incoming connection to it * - * @param sl identifies service to start - * @param lsocks -1 terminated list of listen sockets to pass (systemd style), or NULL + * @param sa address associated with the service + * @param addr_len length of @a sa + * @param sl service entry for the service in question */ static void -start_process (struct ServiceList *sl, const SOCKTYPE *lsocks) +create_listen_socket (struct sockaddr *sa, + socklen_t addr_len, + struct ServiceList *sl) { - char *loprefix; - char *options; - char *optpos; - char *optend; - const char *next; - int use_debug; - char b; - char *val; + static int on = 1; + struct GNUNET_NETWORK_Handle *sock; + struct ServiceListeningInfo *sli; +#ifndef WINDOWS + int match_uid; + int match_gid; +#endif - /* start service */ + switch (sa->sa_family) + { + case AF_INET: + sock = GNUNET_NETWORK_socket_create (PF_INET, + SOCK_STREAM, + 0); + break; + case AF_INET6: + sock = GNUNET_NETWORK_socket_create (PF_INET6, + SOCK_STREAM, + 0); + break; + case AF_UNIX: + if (0 == strcmp (GNUNET_a2s (sa, + addr_len), + "@")) /* Do not bind to blank UNIX path! */ + return; + sock = GNUNET_NETWORK_socket_create (PF_UNIX, + SOCK_STREAM, + 0); + break; + default: + GNUNET_break (0); + sock = NULL; + errno = EAFNOSUPPORT; + break; + } + if (NULL == sock) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Unable to create socket for service `%s': %s\n"), + sl->name, + STRERROR (errno)); + GNUNET_free (sa); + return; + } if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, sl->name, "PREFIX", - &loprefix)) - loprefix = GNUNET_strdup (prefix_command); + GNUNET_NETWORK_socket_setsockopt (sock, + SOL_SOCKET, + SO_REUSEADDR, + &on, + sizeof (on))) + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "setsockopt"); +#ifdef IPV6_V6ONLY + if ( (sa->sa_family == AF_INET6) && + (GNUNET_OK != + GNUNET_NETWORK_socket_setsockopt (sock, + IPPROTO_IPV6, + IPV6_V6ONLY, + &on, + sizeof (on))) ) + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "setsockopt"); +#endif +#ifndef WINDOWS + if (AF_UNIX == sa->sa_family) + GNUNET_NETWORK_unix_precheck ((struct sockaddr_un *) sa); +#endif if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, sl->name, "OPTIONS", - &options)) + GNUNET_NETWORK_socket_bind (sock, + (const struct sockaddr *) sa, + addr_len)) { - options = GNUNET_strdup (final_option); - if (NULL == strstr (options, "%")) - { - /* replace '{}' with service name */ - while (NULL != (optpos = strstr (options, "{}"))) - { - optpos[0] = '%'; - optpos[1] = 's'; - GNUNET_asprintf (&optpos, options, sl->name); - GNUNET_free (options); - options = optpos; - } - /* replace '$PATH' with value associated with "PATH" */ - while (NULL != (optpos = strstr (options, "$"))) - { - optend = optpos + 1; - while (isupper ((unsigned char) *optend)) - optend++; - b = *optend; - if ('\0' == b) - next = ""; - else - next = optend + 1; - *optend = '\0'; - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, "PATHS", optpos + 1, - &val)) - val = GNUNET_strdup (""); - *optpos = '\0'; - GNUNET_asprintf (&optpos, "%s%s%c%s", options, val, b, next); - GNUNET_free (options); - GNUNET_free (val); - options = optpos; - } - } + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + _("Unable to bind listening socket for service `%s' to address `%s': %s\n"), + sl->name, + GNUNET_a2s (sa, + addr_len), + STRERROR (errno)); + GNUNET_break (GNUNET_OK == + GNUNET_NETWORK_socket_close (sock)); + GNUNET_free (sa); + return; } - use_debug = GNUNET_CONFIGURATION_get_value_yesno (cfg, sl->name, "DEBUG"); +#ifndef WINDOWS + if ((AF_UNIX == sa->sa_family) +#ifdef LINUX + /* Permission settings are not required when abstract sockets are used */ + && ('\0' != ((const struct sockaddr_un *)sa)->sun_path[0]) +#endif + ) + { + match_uid = + GNUNET_CONFIGURATION_get_value_yesno (cfg, + sl->name, + "UNIX_MATCH_UID"); + match_gid = + GNUNET_CONFIGURATION_get_value_yesno (cfg, + sl->name, + "UNIX_MATCH_GID"); + GNUNET_DISK_fix_permissions (((const struct sockaddr_un *)sa)->sun_path, + match_uid, + match_gid); -#if DEBUG_ARM - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Starting service `%s' using binary `%s' and configuration `%s'\n", - sl->name, sl->binary, sl->config); + } #endif - if (GNUNET_YES == use_debug) - sl->proc = - do_start_process (lsocks, loprefix, sl->binary, "-c", sl->config, "-L", - "DEBUG", options, NULL); - else - sl->proc = - do_start_process (lsocks, loprefix, sl->binary, "-c", sl->config, - options, NULL); - if (sl->proc == NULL) - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("Failed to start service `%s'\n"), - sl->name); - else - GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Starting service `%s'\n"), sl->name); - GNUNET_free (loprefix); - GNUNET_free (options); + if (GNUNET_OK != + GNUNET_NETWORK_socket_listen (sock, 5)) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "listen"); + GNUNET_break (GNUNET_OK == + GNUNET_NETWORK_socket_close (sock)); + GNUNET_free (sa); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _("ARM now monitors connections to service `%s' at `%s'\n"), + sl->name, + GNUNET_a2s (sa, + addr_len)); + sli = GNUNET_new (struct ServiceListeningInfo); + sli->service_addr = sa; + sli->service_addr_len = addr_len; + sli->listen_socket = sock; + sli->sl = sl; + sli->accept_task + = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, + sock, + &accept_connection, sli); + GNUNET_CONTAINER_DLL_insert (sl->listen_head, + sl->listen_tail, + sli); } /** - * Start the specified service. + * Remove and free an entry in the service list. Listen sockets + * must have already been cleaned up. Only to be called during shutdown. * - * @param client who is asking for this - * @param servicename name of the service to start - * @param lsocks -1 terminated list of listen sockets to pass (systemd style), or NULL - * @return GNUNET_OK on success, GNUNET_SYSERR on error + * @param sl entry to free */ -int -start_service (struct GNUNET_SERVER_Client *client, const char *servicename, - const SOCKTYPE *lsocks) +static void +free_service (struct ServiceList *sl) +{ + GNUNET_assert (GNUNET_YES == in_shutdown); + GNUNET_CONTAINER_DLL_remove (running_head, + running_tail, + sl); + GNUNET_assert (NULL == sl->listen_head); + GNUNET_free_non_null (sl->config); + GNUNET_free_non_null (sl->binary); + GNUNET_free (sl->name); + GNUNET_free (sl); +} + + +/** + * Check START-message. + * + * @param cls identification of the client + * @param amsg the actual message + * @return #GNUNET_OK to keep the connection open, + * #GNUNET_SYSERR to close it (signal serious error) + */ +static int +check_start (void *cls, + const struct GNUNET_ARM_Message *amsg) { + uint16_t size; + const char *servicename; + + size = ntohs (amsg->header.size) - sizeof (struct GNUNET_ARM_Message); + servicename = (const char *) &amsg[1]; + if ( (0 == size) || + (servicename[size - 1] != '\0') ) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Handle START-message. + * + * @param cls identification of the client + * @param amsg the actual message + */ +static void +handle_start (void *cls, + const struct GNUNET_ARM_Message *amsg) +{ + struct GNUNET_SERVICE_Client *client = cls; + const char *servicename; struct ServiceList *sl; - char *binary; - char *config; - struct stat sbuf; + uint64_t request_id; + request_id = GNUNET_ntohll (amsg->request_id); + servicename = (const char *) &amsg[1]; + GNUNET_SERVICE_client_continue (client); if (GNUNET_YES == in_shutdown) { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _("ARM is shutting down, service `%s' not started.\n"), - servicename); - signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN); - return GNUNET_SYSERR; + signal_result (client, + servicename, + request_id, + GNUNET_ARM_RESULT_IN_SHUTDOWN); + return; } sl = find_service (servicename); - if (sl != NULL) + if (NULL == sl) { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Service `%s' already running.\n"), - servicename); - signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_UP); - return GNUNET_SYSERR; + signal_result (client, + servicename, + request_id, + GNUNET_ARM_RESULT_IS_NOT_KNOWN); + return; } - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, servicename, "BINARY", - &binary)) + sl->force_start = GNUNET_YES; + if (NULL != sl->proc) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - _("Binary implementing service `%s' not known!\n"), - servicename); - signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN); - return GNUNET_SYSERR; + signal_result (client, + servicename, + request_id, + GNUNET_ARM_RESULT_IS_STARTED_ALREADY); + return; } - if ((GNUNET_OK != - GNUNET_CONFIGURATION_get_value_filename (cfg, servicename, "CONFIG", - &config)) || - (0 != STAT (config, &sbuf))) + start_process (sl, + client, + request_id); +} + + +/** + * Start a shutdown sequence. + * + * @param cls closure (refers to service) + */ +static void +trigger_shutdown (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Triggering shutdown\n"); + GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Check STOP-message. + * + * @param cls identification of the client + * @param amsg the actual message + * @return #GNUNET_OK to keep the connection open, + * #GNUNET_SYSERR to close it (signal serious error) + */ +static int +check_stop (void *cls, + const struct GNUNET_ARM_Message *amsg) +{ + uint16_t size; + const char *servicename; + + size = ntohs (amsg->header.size) - sizeof (struct GNUNET_ARM_Message); + servicename = (const char *) &amsg[1]; + if ( (0 == size) || + (servicename[size - 1] != '\0') ) { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - _("Configuration file `%s' for service `%s' not known!\n"), - config, servicename); - signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN); - GNUNET_free (binary); - GNUNET_free_non_null (config); + GNUNET_break (0); return GNUNET_SYSERR; } - (void) stop_listening (servicename); - sl = GNUNET_malloc (sizeof (struct ServiceList)); - sl->name = GNUNET_strdup (servicename); - sl->binary = binary; - sl->config = config; - sl->mtime = sbuf.st_mtime; - sl->backoff = GNUNET_TIME_UNIT_MILLISECONDS; - sl->restartAt = GNUNET_TIME_UNIT_FOREVER_ABS; - GNUNET_CONTAINER_DLL_insert (running_head, running_tail, sl); - start_process (sl, lsocks); - if (NULL != client) - signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_UP); return GNUNET_OK; } /** - * Stop the specified service. + * Handle STOP-message. * - * @param client who is asking for this - * @param servicename name of the service to stop + * @param cls identification of the client + * @param amsg the actual message */ static void -stop_service (struct GNUNET_SERVER_Client *client, const char *servicename) +handle_stop (void *cls, + const struct GNUNET_ARM_Message *amsg) { - struct ServiceList *pos; + struct GNUNET_SERVICE_Client *client = cls; + struct ServiceList *sl; + const char *servicename; + uint64_t request_id; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Preparing to stop `%s'\n"), - servicename); - pos = find_service (servicename); - if (pos == NULL) + request_id = GNUNET_ntohll (amsg->request_id); + servicename = (const char *) &amsg[1]; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _("Preparing to stop `%s'\n"), + servicename); + GNUNET_SERVICE_client_continue (client); + if (0 == strcasecmp (servicename, + "arm")) { - if (GNUNET_OK == stop_listening (servicename)) - signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN); - else - signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_UNKNOWN); - GNUNET_SERVER_receive_done (client, GNUNET_OK); + broadcast_status (servicename, + GNUNET_ARM_SERVICE_STOPPING, + NULL); + signal_result (client, + servicename, + request_id, + GNUNET_ARM_RESULT_STOPPING); + GNUNET_SERVICE_client_persist (client); + GNUNET_SCHEDULER_add_now (&trigger_shutdown, + NULL); return; } - if (pos->killing_client != NULL) + sl = find_service (servicename); + if (NULL == sl) { - /* killing already in progress */ -#if DEBUG_ARM - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Service `%s' is already down\n", - servicename); -#endif - signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN); - GNUNET_SERVER_receive_done (client, GNUNET_OK); + signal_result (client, + servicename, + request_id, + GNUNET_ARM_RESULT_IS_NOT_KNOWN); return; } - + sl->force_start = GNUNET_NO; if (GNUNET_YES == in_shutdown) { -#if DEBUG_ARM - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Termination request already sent to `%s' (since ARM is in shutdown).\n", - servicename); -#endif - signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN); - GNUNET_SERVER_receive_done (client, GNUNET_OK); + /* shutdown in progress */ + signal_result (client, + servicename, + request_id, + GNUNET_ARM_RESULT_IN_SHUTDOWN); + return; + } + if (NULL != sl->killing_client) + { + /* killing already in progress */ + signal_result (client, + servicename, + request_id, + GNUNET_ARM_RESULT_IS_STOPPING_ALREADY); return; } - if (pos->proc == NULL) + if (NULL == sl->proc) { - /* process is in delayed restart, simply remove it! */ - free_service (pos); - signal_result (client, servicename, GNUNET_MESSAGE_TYPE_ARM_IS_DOWN); - GNUNET_SERVER_receive_done (client, GNUNET_OK); + /* process is down */ + signal_result (client, + servicename, + request_id, + GNUNET_ARM_RESULT_IS_STOPPED_ALREADY); return; } -#if DEBUG_ARM GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Sending kill signal to service `%s', waiting for process to die.\n", - servicename); -#endif - if (0 != GNUNET_OS_process_kill (pos->proc, SIGTERM)) - GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill"); - pos->killing_client = client; - GNUNET_SERVER_client_keep (client); + "Sending kill signal to service `%s', waiting for process to die.\n", + servicename); + broadcast_status (servicename, + GNUNET_ARM_SERVICE_STOPPING, + NULL); + /* no signal_start - only when it's STOPPED */ + sl->killed_at = GNUNET_TIME_absolute_get (); + if (0 != GNUNET_OS_process_kill (sl->proc, + GNUNET_TERM_SIG)) + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "kill"); + sl->killing_client = client; + sl->killing_client_request_id = request_id; } /** - * Handle START-message. + * Handle LIST-message. * - * @param cls closure (always NULL) - * @param client identification of the client + * @param cls identification of the client * @param message the actual message - * @return GNUNET_OK to keep the connection open, - * GNUNET_SYSERR to close it (signal serious error) */ static void -handle_start (void *cls, struct GNUNET_SERVER_Client *client, - const struct GNUNET_MessageHeader *message) +handle_list (void *cls, + const struct GNUNET_ARM_Message *request) { - const char *servicename; - uint16_t size; + struct GNUNET_SERVICE_Client *client = cls; + struct GNUNET_MQ_Envelope *env; + struct GNUNET_ARM_ListResultMessage *msg; + size_t string_list_size; + struct ServiceList *sl; + uint16_t count; + char *pos; + + GNUNET_break (0 == ntohl (request->reserved)); + count = 0; + string_list_size = 0; - size = ntohs (message->size); - size -= sizeof (struct GNUNET_MessageHeader); - servicename = (const char *) &message[1]; - if ((size == 0) || (servicename[size - 1] != '\0')) + /* first count the running processes get their name's size */ + for (sl = running_head; NULL != sl; sl = sl->next) { - GNUNET_break (0); - GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); - return; + if (NULL != sl->proc) + { + string_list_size += strlen (sl->name); + string_list_size += strlen (sl->binary); + string_list_size += 4; + count++; + } } - start_service (client, servicename, NULL); - GNUNET_SERVER_receive_done (client, GNUNET_OK); -} - -/** - * Handle STOP-message. - * - * @param cls closure (always NULL) - * @param client identification of the client - * @param message the actual message - * @return GNUNET_OK to keep the connection open, - * GNUNET_SYSERR to close it (signal serious error) - */ -static void -handle_stop (void *cls, struct GNUNET_SERVER_Client *client, - const struct GNUNET_MessageHeader *message) -{ - const char *servicename; - uint16_t size; + env = GNUNET_MQ_msg_extra (msg, + string_list_size, + GNUNET_MESSAGE_TYPE_ARM_LIST_RESULT); + msg->arm_msg.request_id = request->request_id; + msg->count = htons (count); - size = ntohs (message->size); - size -= sizeof (struct GNUNET_MessageHeader); - servicename = (const char *) &message[1]; - if ((size == 0) || (servicename[size - 1] != '\0')) + pos = (char *) &msg[1]; + for (sl = running_head; NULL != sl; sl = sl->next) { - GNUNET_break (0); - GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); - return; + if (NULL != sl->proc) + { + size_t s = strlen (sl->name) + strlen (sl->binary) + 4; + GNUNET_snprintf (pos, + s, + "%s (%s)", + sl->name, + sl->binary); + pos += s; + } } - stop_service (client, servicename); + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), + env); + GNUNET_SERVICE_client_continue (client); } /** - * Remove all entries for tasks that are not running - * (proc = NULL) from the running list (they will no longer - * be restarted since we are shutting down). + * Handle TEST-message by sending back TEST. + * + * @param cls identification of the client + * @param message the actual message */ static void -clean_up_running () +handle_test (void *cls, + const struct GNUNET_MessageHeader *message) { - struct ServiceList *pos; - struct ServiceList *next; + struct GNUNET_SERVICE_Client *client = cls; + struct GNUNET_MQ_Envelope *env; + struct GNUNET_MessageHeader *msg; - next = running_head; - while (NULL != (pos = next)) - { - next = pos->next; - if (pos->proc == NULL) - free_service (pos); - } + env = GNUNET_MQ_msg (msg, + GNUNET_MESSAGE_TYPE_ARM_TEST); + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), + env); + GNUNET_SERVICE_client_continue (client); } @@ -637,59 +1468,113 @@ clean_up_running () static void do_shutdown () { - if (NULL != server) + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Last shutdown phase\n"); + if (NULL != notifier) + { + GNUNET_notification_context_destroy (notifier); + notifier = NULL; + } + if (NULL != service) { - GNUNET_SERVER_destroy (server); - server = NULL; + GNUNET_SERVICE_shutdown (service); + service = NULL; } - if (GNUNET_SCHEDULER_NO_TASK != child_death_task) + if (NULL != child_death_task) { GNUNET_SCHEDULER_cancel (child_death_task); - child_death_task = GNUNET_SCHEDULER_NO_TASK; + child_death_task = NULL; } } +/** + * Count how many services are still active. + * + * @param running_head list of services + * @return number of active services found + */ +static unsigned int +list_count (struct ServiceList *running_head) +{ + struct ServiceList *i; + unsigned int res; + + for (res = 0, i = running_head; i; i = i->next, res++) + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%s\n", + i->name); + return res; +} + + /** * Task run for shutdown. * * @param cls closure, NULL if we need to self-restart - * @param tc context */ static void -shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +shutdown_task (void *cls) { struct ServiceList *pos; struct ServiceList *nxt; + struct ServiceListeningInfo *sli; -#if DEBUG_ARM - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, _("Stopping all services\n")); -#endif - if (GNUNET_SCHEDULER_NO_TASK != child_restart_task) + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "First shutdown phase\n"); + if (NULL != child_restart_task) { GNUNET_SCHEDULER_cancel (child_restart_task); - child_restart_task = GNUNET_SCHEDULER_NO_TASK; + child_restart_task = NULL; } in_shutdown = GNUNET_YES; - stop_listening (NULL); - pos = running_head; - while (NULL != pos) + /* first, stop listening */ + for (pos = running_head; NULL != pos; pos = pos->next) + { + while (NULL != (sli = pos->listen_head)) + { + GNUNET_CONTAINER_DLL_remove (pos->listen_head, + pos->listen_tail, + sli); + if (NULL != sli->accept_task) + { + GNUNET_SCHEDULER_cancel (sli->accept_task); + sli->accept_task = NULL; + } + GNUNET_break (GNUNET_OK == + GNUNET_NETWORK_socket_close (sli->listen_socket)); + GNUNET_free (sli->service_addr); + GNUNET_free (sli); + } + } + /* then, shutdown all existing service processes */ + nxt = running_head; + while (NULL != (pos = nxt)) { nxt = pos->next; - if (pos->proc != NULL) + if (NULL != pos->proc) { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Stopping service `%s'\n", pos->name); - if (0 != GNUNET_OS_process_kill (pos->proc, SIGTERM)) - GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "kill"); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Stopping service `%s'\n", + pos->name); + pos->killed_at = GNUNET_TIME_absolute_get (); + if (0 != GNUNET_OS_process_kill (pos->proc, + GNUNET_TERM_SIG)) + GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, + "kill"); } else { free_service (pos); } - pos = nxt; } - if (running_head == NULL) + /* finally, should all service processes be already gone, terminate for real */ + if (NULL == running_head) do_shutdown (); + else + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Delaying shutdown, have %u childs still running\n", + list_count (running_head)); } @@ -697,52 +1582,74 @@ shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) * Task run whenever it is time to restart a child that died. * * @param cls closure, always NULL - * @param tc context */ static void -delayed_restart_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +delayed_restart_task (void *cls) + { - struct ServiceList *pos; + struct ServiceList *sl; struct GNUNET_TIME_Relative lowestRestartDelay; + struct ServiceListeningInfo *sli; - child_restart_task = GNUNET_SCHEDULER_NO_TASK; - if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN)) - return; + child_restart_task = NULL; GNUNET_assert (GNUNET_NO == in_shutdown); lowestRestartDelay = GNUNET_TIME_UNIT_FOREVER_REL; /* check for services that need to be restarted due to * configuration changes or because the last restart failed */ - pos = running_head; - while (pos != NULL) + for (sl = running_head; NULL != sl; sl = sl->next) { - if (pos->proc == NULL) + if (NULL != sl->proc) + continue; + /* service is currently not running */ + if (0 == GNUNET_TIME_absolute_get_remaining (sl->restart_at).rel_value_us) { - if (GNUNET_TIME_absolute_get_remaining (pos->restartAt).rel_value == 0) + /* restart is now allowed */ + if (sl->force_start) { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Restarting service `%s'.\n"), - pos->name); - start_process (pos, NULL); + /* process should run by default, start immediately */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _("Restarting service `%s'.\n"), + sl->name); + start_process (sl, + NULL, + 0); } else { - lowestRestartDelay = - GNUNET_TIME_relative_min (lowestRestartDelay, - GNUNET_TIME_absolute_get_remaining - (pos->restartAt)); + /* process is run on-demand, ensure it is re-started if there is demand */ + for (sli = sl->listen_head; NULL != sli; sli = sli->next) + if (NULL == sli->accept_task) + { + /* accept was actually paused, so start it again */ + sli->accept_task + = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, + sli->listen_socket, + &accept_connection, + sli); + } } } - pos = pos->next; + else + { + /* update calculation for earliest time to reactivate a service */ + lowestRestartDelay = + GNUNET_TIME_relative_min (lowestRestartDelay, + GNUNET_TIME_absolute_get_remaining + (sl->restart_at)); + } } - if (lowestRestartDelay.rel_value != GNUNET_TIME_UNIT_FOREVER_REL.rel_value) + if (lowestRestartDelay.rel_value_us != GNUNET_TIME_UNIT_FOREVER_REL.rel_value_us) { -#if DEBUG_ARM - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Will restart process in %llums\n", - (unsigned long long) lowestRestartDelay.rel_value); -#endif + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Will restart process in %s\n", + GNUNET_STRINGS_relative_time_to_string (lowestRestartDelay, + GNUNET_YES)); child_restart_task = - GNUNET_SCHEDULER_add_delayed (lowestRestartDelay, &delayed_restart_task, - NULL); + GNUNET_SCHEDULER_add_delayed_with_priority (lowestRestartDelay, + GNUNET_SCHEDULER_PRIORITY_IDLE, + &delayed_restart_task, + NULL); } } @@ -752,43 +1659,112 @@ delayed_restart_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) * process died). * * @param cls closure, NULL if we need to self-restart - * @param tc context */ static void -maint_child_death (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +maint_child_death (void *cls) { struct ServiceList *pos; struct ServiceList *next; + struct ServiceListeningInfo *sli; const char *statstr; int statcode; int ret; char c[16]; enum GNUNET_OS_ProcessStatusType statusType; unsigned long statusCode; + const struct GNUNET_DISK_FileHandle *pr; - child_death_task = GNUNET_SCHEDULER_NO_TASK; - if (0 == (tc->reason & GNUNET_SCHEDULER_REASON_READ_READY)) - { - /* shutdown scheduled us, ignore! */ - child_death_task = - GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, pr, - &maint_child_death, NULL); - return; - } + pr = GNUNET_DISK_pipe_handle (sigpipe, + GNUNET_DISK_PIPE_END_READ); + child_death_task = NULL; /* consume the signal */ - GNUNET_break (0 < GNUNET_DISK_file_read (pr, &c, sizeof (c))); + GNUNET_break (0 < GNUNET_DISK_file_read (pr, + &c, + sizeof (c))); /* check for services that died (WAITPID) */ next = running_head; while (NULL != (pos = next)) { next = pos->next; - if (pos->proc == NULL) + + if (NULL == pos->proc) + { + if (GNUNET_YES == in_shutdown) + free_service (pos); continue; - if ((GNUNET_SYSERR == - (ret = GNUNET_OS_process_status (pos->proc, &statusType, &statusCode))) - || ((ret == GNUNET_NO) || (statusType == GNUNET_OS_PROCESS_STOPPED) || - (statusType == GNUNET_OS_PROCESS_RUNNING))) + } +#if HAVE_WAIT4 + if (NULL != wait_file) + { + /* need to use 'wait4()' to obtain and log performance data */ + struct rusage ru; + int status; + pid_t pid; + + pid = GNUNET_OS_process_get_pid (pos->proc); + ret = wait4 (pid, + &status, + WNOHANG, + &ru); + if (ret <= 0) + continue; /* no process done */ + if (WIFEXITED (status)) + { + statusType = GNUNET_OS_PROCESS_EXITED; + statusCode = WEXITSTATUS (status); + } + else if (WIFSIGNALED (status)) + { + statusType = GNUNET_OS_PROCESS_SIGNALED; + statusCode = WTERMSIG (status); + } + else if (WIFSTOPPED (status)) + { + statusType = GNUNET_OS_PROCESS_SIGNALED; + statusCode = WSTOPSIG (status); + } +#ifdef WIFCONTINUED + else if (WIFCONTINUED (status)) + { + statusType = GNUNET_OS_PROCESS_RUNNING; + statusCode = 0; + } +#endif + else + { + statusType = GNUNET_OS_PROCESS_UNKNOWN; + statusCode = 0; + } + if ( (GNUNET_OS_PROCESS_EXITED == statusType) || + (GNUNET_OS_PROCESS_SIGNALED == statusType) ) + { + double utime = ru.ru_utime.tv_sec + (ru.ru_utime.tv_usec / 10e6); + double stime = ru.ru_stime.tv_sec + (ru.ru_stime.tv_usec / 10e6); + fprintf (wait_file, + "%s(%u) %.3f %.3f %llu %llu %llu %llu %llu\n", + pos->binary, + (unsigned int) pid, + utime, + stime, + (unsigned long long) ru.ru_maxrss, + (unsigned long long) ru.ru_inblock, + (unsigned long long) ru.ru_oublock, + (unsigned long long) ru.ru_nvcsw, + (unsigned long long) ru.ru_nivcsw); + } + } + else /* continue with JUST this "if" as "else" (intentionally no brackets!) */ +#endif + if ( (GNUNET_SYSERR == + (ret = + GNUNET_OS_process_status (pos->proc, + &statusType, + &statusCode))) || + (ret == GNUNET_NO) || + (statusType == GNUNET_OS_PROCESS_STOPPED) || + (statusType == GNUNET_OS_PROCESS_UNKNOWN) || + (statusType == GNUNET_OS_PROCESS_RUNNING) ) continue; if (statusType == GNUNET_OS_PROCESS_EXITED) @@ -806,120 +1782,312 @@ maint_child_death (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) statstr = _( /* process termination method */ "unknown"); statcode = 0; } - GNUNET_OS_process_close (pos->proc); + if (0 != pos->killed_at.abs_value_us) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _("Service `%s' took %s to terminate\n"), + pos->name, + GNUNET_STRINGS_relative_time_to_string (GNUNET_TIME_absolute_get_duration (pos->killed_at), + GNUNET_YES)); + } + GNUNET_OS_process_destroy (pos->proc); pos->proc = NULL; + broadcast_status (pos->name, + GNUNET_ARM_SERVICE_STOPPED, + NULL); if (NULL != pos->killing_client) { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Service `%s' stopped\n"), - pos->name); signal_result (pos->killing_client, pos->name, - GNUNET_MESSAGE_TYPE_ARM_IS_DOWN); - GNUNET_SERVER_receive_done (pos->killing_client, GNUNET_OK); - GNUNET_SERVER_client_drop (pos->killing_client); - free_service (pos); - continue; + pos->killing_client_request_id, + GNUNET_ARM_RESULT_STOPPED); + pos->killing_client = NULL; + pos->killing_client_request_id = 0; } if (GNUNET_YES != in_shutdown) { - if (0 == (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN)) - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - _ - ("Service `%s' terminated with status %s/%d, will try to restart it!\n"), - pos->name, statstr, statcode); - /* schedule restart */ - pos->restartAt = GNUNET_TIME_relative_to_absolute (pos->backoff); - if (pos->backoff.rel_value < EXPONENTIAL_BACKOFF_THRESHOLD) - pos->backoff = GNUNET_TIME_relative_multiply (pos->backoff, 2); - if (GNUNET_SCHEDULER_NO_TASK != child_restart_task) - GNUNET_SCHEDULER_cancel (child_restart_task); - child_restart_task = - GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_IDLE, - &delayed_restart_task, NULL); + if ( (statusType == GNUNET_OS_PROCESS_EXITED) && + (statcode == 0) ) + { + /* process terminated normally, allow restart at any time */ + pos->restart_at.abs_value_us = 0; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _("Service `%s' terminated normally, will restart at any time\n"), + pos->name); + /* process can still be re-started on-demand, ensure it is re-started if there is demand */ + for (sli = pos->listen_head; NULL != sli; sli = sli->next) + { + GNUNET_break (NULL == sli->accept_task); + sli->accept_task = + GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, + sli->listen_socket, + &accept_connection, + sli); + } + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _("Service `%s' terminated with status %s/%d, will restart in %s\n"), + pos->name, + statstr, + statcode, + GNUNET_STRINGS_relative_time_to_string (pos->backoff, + GNUNET_YES)); + /* schedule restart */ + pos->restart_at = GNUNET_TIME_relative_to_absolute (pos->backoff); + pos->backoff = GNUNET_TIME_STD_BACKOFF (pos->backoff); + if (NULL != child_restart_task) + GNUNET_SCHEDULER_cancel (child_restart_task); + child_restart_task + = GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_IDLE, + &delayed_restart_task, + NULL); + } } -#if DEBUG_ARM else - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Service `%s' terminated with status %s/%d\n", pos->name, - statstr, statcode); -#endif + { + free_service (pos); + } } - child_death_task = - GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, pr, - &maint_child_death, NULL); - if (GNUNET_YES == in_shutdown) - clean_up_running (); + child_death_task = GNUNET_SCHEDULER_add_read_file ( + GNUNET_TIME_UNIT_FOREVER_REL, + pr, + &maint_child_death, NULL); if ((NULL == running_head) && (GNUNET_YES == in_shutdown)) do_shutdown (); + else if (GNUNET_YES == in_shutdown) + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Delaying shutdown after child's death, still have %u children\n", + list_count (running_head)); + +} + + +/** + * Signal handler called for SIGCHLD. Triggers the + * respective handler by writing to the trigger pipe. + */ +static void +sighandler_child_death () +{ + static char c; + int old_errno = errno; /* back-up errno */ + + GNUNET_break (1 == + GNUNET_DISK_file_write (GNUNET_DISK_pipe_handle (sigpipe, + GNUNET_DISK_PIPE_END_WRITE), + &c, + sizeof (c))); + errno = old_errno; /* restore errno */ } -static size_t -transmit_shutdown_ack (void *cls, size_t size, void *buf) +/** + * Setup our service record for the given section in the configuration file + * (assuming the section is for a service). + * + * @param cls unused + * @param section a section in the configuration file + * @return #GNUNET_OK (continue) + */ +static void +setup_service (void *cls, + const char *section) { - struct GNUNET_SERVER_Client *client = cls; - struct GNUNET_MessageHeader *msg; + struct ServiceList *sl; + char *binary; + char *config; + struct stat sbuf; + struct sockaddr **addrs; + socklen_t *addr_lens; + int ret; + unsigned int i; - if (size < sizeof (struct GNUNET_MessageHeader)) + if (0 == strcasecmp (section, + "arm")) + return; + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "BINARY", + &binary)) { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _("Failed to transmit shutdown ACK.\n")); - GNUNET_SERVER_receive_done (client, GNUNET_SYSERR); - return 0; /* client disconnected */ + /* not a service section */ + return; + } + if ((GNUNET_YES == + GNUNET_CONFIGURATION_have_value (cfg, + section, + "USER_SERVICE")) && + (GNUNET_YES == + GNUNET_CONFIGURATION_get_value_yesno (cfg, + section, + "USER_SERVICE"))) + { + if (GNUNET_NO == start_user) + { + GNUNET_free (binary); + return; /* user service, and we don't deal with those */ + } + } + else + { + if (GNUNET_NO == start_system) + { + GNUNET_free (binary); + return; /* system service, and we don't deal with those */ + } + } + sl = find_service (section); + if (NULL != sl) + { + /* got the same section twice!? */ + GNUNET_break (0); + GNUNET_free (binary); + return; + } + config = NULL; + if (( (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (cfg, + section, + "CONFIG", + &config)) && + (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (cfg, + "PATHS", + "DEFAULTCONFIG", + &config)) ) || + (0 != STAT (config, &sbuf))) + { + if (NULL != config) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_WARNING, + section, "CONFIG", + STRERROR (errno)); + GNUNET_free (config); + config = NULL; + } + } + sl = GNUNET_new (struct ServiceList); + sl->name = GNUNET_strdup (section); + sl->binary = binary; + sl->config = config; + sl->backoff = GNUNET_TIME_UNIT_MILLISECONDS; + sl->restart_at = GNUNET_TIME_UNIT_FOREVER_ABS; +#if WINDOWS + sl->pipe_control = GNUNET_YES; +#else + if (GNUNET_CONFIGURATION_have_value (cfg, + section, + "PIPECONTROL")) + sl->pipe_control = GNUNET_CONFIGURATION_get_value_yesno (cfg, + section, + "PIPECONTROL"); +#endif + GNUNET_CONTAINER_DLL_insert (running_head, + running_tail, + sl); + if (GNUNET_YES == + GNUNET_CONFIGURATION_get_value_yesno (cfg, + section, + "FORCESTART")) + { + sl->force_start = GNUNET_YES; + if (GNUNET_YES == + GNUNET_CONFIGURATION_get_value_yesno (cfg, + section, + "NOARMBIND")) + return; + } + else + { + if (GNUNET_YES != + GNUNET_CONFIGURATION_get_value_yesno (cfg, + section, + "AUTOSTART")) + return; } + if (0 >= (ret = get_server_addresses (section, + cfg, + &addrs, + &addr_lens))) + return; + /* this will free (or capture) addrs[i] */ + for (i = 0; i < ret; i++) + create_listen_socket (addrs[i], + addr_lens[i], + sl); + GNUNET_free (addrs); + GNUNET_free (addr_lens); +} + - GNUNET_log (GNUNET_ERROR_TYPE_INFO, _("Transmitting shutdown ACK.\n")); - - /* Make the connection flushing for the purpose of ACK transmitting, - * needed on W32 to ensure that the message is even received, harmless - * on other platforms... */ - GNUNET_break (GNUNET_OK == GNUNET_SERVER_client_disable_corking (client)); - msg = (struct GNUNET_MessageHeader *) buf; - msg->type = htons (GNUNET_MESSAGE_TYPE_ARM_SHUTDOWN_ACK); - msg->size = htons (sizeof (struct GNUNET_MessageHeader)); - GNUNET_SERVER_receive_done (client, GNUNET_OK); - GNUNET_SERVER_client_drop (client); - return sizeof (struct GNUNET_MessageHeader); +/** + * A client connected, mark as a monitoring client. + * + * @param cls closure + * @param client identification of the client + * @param mq queue to talk to @a client + * @return @a client + */ +static void * +client_connect_cb (void *cls, + struct GNUNET_SERVICE_Client *client, + struct GNUNET_MQ_Handle *mq) +{ + /* All clients are considered to be of the "monitor" kind + * (that is, they don't affect ARM shutdown). + */ + GNUNET_SERVICE_client_mark_monitor (client); + return client; } /** - * Handler for SHUTDOWN message. + * A client disconnected, clean up associated state. * - * @param cls closure (refers to service) + * @param cls closure * @param client identification of the client - * @param message the actual message + * @param app_ctx must match @a client */ static void -handle_shutdown (void *cls, struct GNUNET_SERVER_Client *client, - const struct GNUNET_MessageHeader *message) +client_disconnect_cb (void *cls, + struct GNUNET_SERVICE_Client *client, + void *app_ctx) { - GNUNET_SERVER_client_keep (client); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _("Initiating shutdown as requested by client.\n")); - GNUNET_SERVER_notify_transmit_ready (client, - sizeof (struct GNUNET_MessageHeader), - GNUNET_TIME_UNIT_FOREVER_REL, - &transmit_shutdown_ack, client); - GNUNET_SERVER_client_persist_ (client); - GNUNET_SCHEDULER_shutdown (); + struct ServiceList *sl; + + GNUNET_assert (client == app_ctx); + + for (sl = running_head; NULL != sl; sl = sl->next) + if (sl->killing_client == client) + sl->killing_client = NULL; } /** - * Signal handler called for SIGCHLD. Triggers the - * respective handler by writing to the trigger pipe. + * Handle MONITOR-message. + * + * @param cls identification of the client + * @param message the actual message + * @return #GNUNET_OK to keep the connection open, + * #GNUNET_SYSERR to close it (signal serious error) */ static void -sighandler_child_death () +handle_monitor (void *cls, + const struct GNUNET_MessageHeader *message) { - static char c; - int old_errno = errno; /* back-up errno */ - - GNUNET_break (1 == - GNUNET_DISK_file_write (GNUNET_DISK_pipe_handle - (sigpipe, GNUNET_DISK_PIPE_END_WRITE), - &c, sizeof (c))); - errno = old_errno; /* restore errno */ + struct GNUNET_SERVICE_Client *client = cls; + + /* FIXME: might want to start by letting monitor know about + services that are already running */ + /* Removal is handled by the server implementation, internally. */ + GNUNET_notification_context_add (notifier, + GNUNET_SERVICE_client_get_mq (client)); + broadcast_status ("arm", + GNUNET_ARM_SERVICE_MONITORING_STARTED, + client); + GNUNET_SERVICE_client_continue (client); } @@ -927,79 +2095,88 @@ sighandler_child_death () * Process arm requests. * * @param cls closure - * @param serv the initialized server + * @param serv the initialized service * @param c configuration to use */ static void -run (void *cls, struct GNUNET_SERVER_Handle *serv, - const struct GNUNET_CONFIGURATION_Handle *c) +run (void *cls, + const struct GNUNET_CONFIGURATION_Handle *c, + struct GNUNET_SERVICE_Handle *serv) { - static const struct GNUNET_SERVER_MessageHandler handlers[] = { - {&handle_start, NULL, GNUNET_MESSAGE_TYPE_ARM_START, 0}, - {&handle_stop, NULL, GNUNET_MESSAGE_TYPE_ARM_STOP, 0}, - {&handle_shutdown, NULL, GNUNET_MESSAGE_TYPE_ARM_SHUTDOWN, - sizeof (struct GNUNET_MessageHeader)}, - {NULL, NULL, 0, 0} - }; - char *defaultservices; - char *pos; + struct ServiceList *sl; cfg = c; - server = serv; - GNUNET_assert (serv != NULL); - pr = GNUNET_DISK_pipe_handle (sigpipe, GNUNET_DISK_PIPE_END_READ); - GNUNET_assert (pr != NULL); - GNUNET_SERVER_ignore_shutdown (serv, GNUNET_YES); - GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &shutdown_task, - NULL); + service = serv; + GNUNET_SCHEDULER_add_shutdown (&shutdown_task, + NULL); child_death_task = - GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, pr, - &maint_child_death, NULL); - + GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, + GNUNET_DISK_pipe_handle (sigpipe, + GNUNET_DISK_PIPE_END_READ), + &maint_child_death, + NULL); +#if HAVE_WAIT4 + if (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_filename (cfg, + "ARM", + "RESOURCE_DIAGNOSTICS", + &wait_filename)) + { + wait_file = fopen (wait_filename, + "w"); + if (NULL == wait_file) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "fopen", + wait_filename); + } + } +#endif if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, "ARM", "GLOBAL_PREFIX", + GNUNET_CONFIGURATION_get_value_string (cfg, + "ARM", + "GLOBAL_PREFIX", &prefix_command)) prefix_command = GNUNET_strdup (""); + else + prefix_command = GNUNET_CONFIGURATION_expand_dollar (cfg, + prefix_command); if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, "ARM", "GLOBAL_POSTFIX", + GNUNET_CONFIGURATION_get_value_string (cfg, + "ARM", + "GLOBAL_POSTFIX", &final_option)) final_option = GNUNET_strdup (""); - /* start default services... */ - if (GNUNET_OK == - GNUNET_CONFIGURATION_get_value_string (cfg, "ARM", "DEFAULTSERVICES", - &defaultservices)) + else + final_option = GNUNET_CONFIGURATION_expand_dollar (cfg, + final_option); + if (GNUNET_YES == + GNUNET_CONFIGURATION_get_value_yesno (cfg, + "ARM", + "USER_ONLY")) { -#if DEBUG_ARM - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Starting default services `%s'\n", - defaultservices); -#endif - if (0 < strlen (defaultservices)) - { - pos = strtok (defaultservices, " "); - while (pos != NULL) - { - start_service (NULL, pos, NULL); - pos = strtok (NULL, " "); - } - } - GNUNET_free (defaultservices); + GNUNET_break (GNUNET_YES == start_user); + start_system = GNUNET_NO; } - else + if (GNUNET_YES == + GNUNET_CONFIGURATION_get_value_yesno (cfg, + "ARM", + "SYSTEM_ONLY")) { -#if DEBUG_ARM - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "No default services configured.\n"); -#endif + GNUNET_break (GNUNET_YES == start_system); + start_user = GNUNET_NO; } + GNUNET_CONFIGURATION_iterate_sections (cfg, + &setup_service, + NULL); - /* create listening sockets for future services */ - prepareServices (cfg); - - /* process client requests */ - GNUNET_SERVER_add_handlers (server, handlers); - - /* manage services */ - GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_IDLE, - &config_change_task, NULL); + /* start default services... */ + for (sl = running_head; NULL != sl; sl = sl->next) + if (GNUNET_YES == sl->force_start) + start_process (sl, + NULL, + 0); + notifier = GNUNET_notification_context_create (MAX_NOTIFY_QUEUE); } @@ -1011,18 +2188,64 @@ run (void *cls, struct GNUNET_SERVER_Handle *serv, * @return 0 ok, 1 on error */ int -main (int argc, char *const *argv) +main (int argc, + char *const *argv) { int ret; struct GNUNET_SIGNAL_Context *shc_chld; + struct GNUNET_MQ_MessageHandler handlers[] = { + GNUNET_MQ_hd_var_size (start, + GNUNET_MESSAGE_TYPE_ARM_START, + struct GNUNET_ARM_Message, + NULL), + GNUNET_MQ_hd_var_size (stop, + GNUNET_MESSAGE_TYPE_ARM_STOP, + struct GNUNET_ARM_Message, + NULL), + GNUNET_MQ_hd_fixed_size (monitor, + GNUNET_MESSAGE_TYPE_ARM_MONITOR, + struct GNUNET_MessageHeader, + NULL), + GNUNET_MQ_hd_fixed_size (list, + GNUNET_MESSAGE_TYPE_ARM_LIST, + struct GNUNET_ARM_Message, + NULL), + GNUNET_MQ_hd_fixed_size (test, + GNUNET_MESSAGE_TYPE_ARM_TEST, + struct GNUNET_MessageHeader, + NULL), + GNUNET_MQ_handler_end () + }; - sigpipe = GNUNET_DISK_pipe (GNUNET_NO, GNUNET_NO, GNUNET_NO); - GNUNET_assert (sigpipe != NULL); + sigpipe = GNUNET_DISK_pipe (GNUNET_NO, + GNUNET_NO, + GNUNET_NO, + GNUNET_NO); + GNUNET_assert (NULL != sigpipe); shc_chld = - GNUNET_SIGNAL_handler_install (GNUNET_SIGCHLD, &sighandler_child_death); - ret = - (GNUNET_OK == - GNUNET_SERVICE_run (argc, argv, "arm", GNUNET_YES, &run, NULL)) ? 0 : 1; + GNUNET_SIGNAL_handler_install (GNUNET_SIGCHLD, + &sighandler_child_death); + ret = GNUNET_SERVICE_run_ (argc, + argv, + "arm", + GNUNET_SERVICE_OPTION_MANUAL_SHUTDOWN, + &run, + &client_connect_cb, + &client_disconnect_cb, + NULL, + handlers); +#if HAVE_WAIT4 + if (NULL != wait_file) + { + fclose (wait_file); + wait_file = NULL; + } + if (NULL != wait_filename) + { + GNUNET_free (wait_filename); + wait_filename = NULL; + } +#endif GNUNET_SIGNAL_handler_uninstall (shc_chld); shc_chld = NULL; GNUNET_DISK_pipe_close (sigpipe); @@ -1030,7 +2253,8 @@ main (int argc, char *const *argv) return ret; } -#ifdef LINUX + +#if defined(LINUX) && defined(__GLIBC__) #include /**