/* This file is part of GNUnet. 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 Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . SPDX-License-Identifier: AGPL3.0-or-later */ /** * @file arm/gnunet-service-arm.c * @brief the automated restart manager service * @author Christian Grothoff */ #include "platform.h" #include "gnunet_util_lib.h" #include "gnunet_arm_service.h" #include "gnunet_protocols.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 /** * Name of the file for writing resource utilization summaries to. */ static char *wait_filename; /** * Handle for the file for writing resource summaries. */ 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. */ 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. */ struct ServiceList { /** * This is a doubly-linked list. */ struct ServiceList *next; /** * This is a doubly-linked list. */ 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. */ char *name; /** * Name of the binary used. */ char *binary; /** * Name of the configuration file used. */ char *config; /** * Client to notify upon kill completion (waitpid), NULL * if we should simply restart the process. */ struct GNUNET_SERVICE_Client *killing_client; /** * ID of the request that killed the service (for reporting back). */ uint64_t killing_client_request_id; /** * Process structure pointer of the child. */ struct GNUNET_OS_Process *proc; /** * Process exponential backoff time */ struct GNUNET_TIME_Relative backoff; /** * Absolute time at which the process was (re-)started last. */ struct GNUNET_TIME_Absolute last_started_at; /** * Absolute time at which the process is scheduled to restart in case of death */ 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; /** * Last exit status of the process. */ int last_exit_status; }; /** * List of running services. */ static struct ServiceList *running_head; /** * List of running services. */ static struct ServiceList *running_tail; /** * Our configuration */ static const struct GNUNET_CONFIGURATION_Handle *cfg; /** * Command to prepend to each actual command. */ static char *prefix_command; /** * Option to append to each actual command. */ static char *final_option; /** * ID of task called whenever we get a SIGCHILD. */ static struct GNUNET_SCHEDULER_Task *child_death_task; /** * ID of task called whenever the timeout for restarting a child * expires. */ static struct GNUNET_SCHEDULER_Task *child_restart_task; /** * Pipe used to communicate shutdown via signal. */ static struct GNUNET_DISK_PipeHandle *sigpipe; /** * Are we in shutdown mode? */ static int in_shutdown; /** * Return value from main */ static int global_ret; /** * Are we starting user services? */ static int start_user = GNUNET_YES; /** * Are we starting system services? */ static int start_system = GNUNET_YES; /** * 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 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_SERVICE_Handle *service; /** * Context for notifications we need to send to our clients. */ static struct GNUNET_NotificationContext *notifier; /** * Add the given UNIX domain path as an address to the * list (as the first entry). * * @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 add_unixpath (struct sockaddr **saddrs, socklen_t *saddrlens, const char *unixpath, int abstract) { #ifdef AF_UNIX struct sockaddr_un *un; un = GNUNET_new (struct sockaddr_un); un->sun_family = AF_UNIX; GNUNET_strlcpy (un->sun_path, unixpath, sizeof(un->sun_path)); #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 } /** * Get the list of addresses that a server for the given service * should bind to. * * @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 int get_server_addresses (const char *service_name, const struct GNUNET_CONFIGURATION_Handle *cfg, struct sockaddr ***addrs, socklen_t **addr_lens) { 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 (! disablev6) { /* 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 (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; } } #endif if ((0 == port) && (NULL == unixpath)) { if (GNUNET_YES == GNUNET_CONFIGURATION_get_value_yesno (cfg, service_name, "START_ON_DEMAND")) LOG (GNUNET_ERROR_TYPE_ERROR, _ ( "Have neither PORT nor UNIXPATH for service `%s', but one is required\n"), service_name); GNUNET_free (hostname); return GNUNET_SYSERR; } if (0 == port) { saddrs = GNUNET_new_array (2, struct sockaddr *); saddrlens = GNUNET_new_array (2, socklen_t); add_unixpath (saddrs, saddrlens, unixpath, abstract); GNUNET_free (unixpath); GNUNET_free (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 (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 (unixpath); return GNUNET_SYSERR; } resi = i; if (NULL != unixpath) resi++; saddrs = GNUNET_new_array (resi + 1, struct sockaddr *); saddrlens = GNUNET_new_array (resi + 1, 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_new_array (resi + 1, struct sockaddr *); saddrlens = GNUNET_new_array (resi + 1, 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_new_array (resi + 1, struct sockaddr *); saddrlens = GNUNET_new_array (resi + 1, 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 (unixpath); *addrs = saddrs; *addr_lens = saddrlens; return resi; } /** * Signal our client that we will start or stop the * service. * * @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_SERVICE_Client *client, const char *name, uint64_t request_id, enum GNUNET_ARM_Result result) { struct GNUNET_MQ_Envelope *env; struct GNUNET_ARM_ResultMessage *msg; (void) name; 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); } /** * 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_ServiceMonitorStatus 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) { if (NULL != notifier) GNUNET_notification_context_broadcast (notifier, &msg->header, GNUNET_YES); GNUNET_MQ_discard (env); } 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; int *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; } } GNUNET_array_append (lsocks, ls, -1); /* 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 | GNUNET_OS_USE_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 | GNUNET_OS_USE_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 | GNUNET_OS_USE_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 | GNUNET_OS_USE_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 | GNUNET_OS_USE_PIPE_CONTROL : GNUNET_OS_INHERIT_STD_OUT_AND_ERR, lsocks, loprefix, quotedbinary, "-c", sl->config, options, NULL); } } GNUNET_free (binary); GNUNET_free (quotedbinary); sl->last_started_at = GNUNET_TIME_absolute_get (); 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); } /** * Find the process with the given service * name in the given list and return it. * * @param name which service entry to look up * @return NULL if it was not found */ static struct ServiceList * find_service (const char *name) { struct ServiceList *sl; sl = running_head; while (sl != NULL) { if (0 == strcasecmp (sl->name, name)) return sl; sl = sl->next; } return NULL; } /** * 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 cls callback data, `struct ServiceListeningInfo` describing a listen socket */ static void accept_connection (void *cls) { struct ServiceListeningInfo *sli = cls; struct ServiceList *sl = sli->sl; sli->accept_task = NULL; GNUNET_assert (GNUNET_NO == in_shutdown); start_process (sl, NULL, 0); } /** * Creating a listening socket for each of the service's addresses and * wait for the first incoming connection to it * * @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 create_listen_socket (struct sockaddr *sa, socklen_t addr_len, struct ServiceList *sl) { static int on = 1; struct GNUNET_NETWORK_Handle *sock; struct ServiceListeningInfo *sli; int match_uid; int match_gid; 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_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 if (AF_UNIX == sa->sa_family) GNUNET_NETWORK_unix_precheck ((struct sockaddr_un *) sa); if (GNUNET_OK != GNUNET_NETWORK_socket_bind (sock, (const struct sockaddr *) sa, addr_len)) { 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; } 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 (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); } /** * Remove and free an entry in the service list. Listen sockets * must have already been cleaned up. Only to be called during shutdown. * * @param sl entry to free */ 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 (sl->config); GNUNET_free (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) { (void) cls; GNUNET_MQ_check_zero_termination (amsg); 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; 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) { signal_result (client, servicename, request_id, GNUNET_ARM_RESULT_IN_SHUTDOWN); return; } sl = find_service (servicename); if (NULL == sl) { signal_result (client, servicename, request_id, GNUNET_ARM_RESULT_IS_NOT_KNOWN); return; } sl->force_start = GNUNET_YES; if (NULL != sl->proc) { signal_result (client, servicename, request_id, GNUNET_ARM_RESULT_IS_STARTED_ALREADY); return; } start_process (sl, client, request_id); } /** * Start a shutdown sequence. * * @param cls closure (refers to service) */ static void trigger_shutdown (void *cls) { (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) { (void) cls; GNUNET_MQ_check_zero_termination (amsg); return GNUNET_OK; } /** * Handle STOP-message. * * @param cls identification of the client * @param amsg the actual message */ static void handle_stop (void *cls, const struct GNUNET_ARM_Message *amsg) { struct GNUNET_SERVICE_Client *client = cls; struct ServiceList *sl; const char *servicename; uint64_t request_id; 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")) { 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; } sl = find_service (servicename); if (NULL == sl) { signal_result (client, servicename, request_id, GNUNET_ARM_RESULT_IS_NOT_KNOWN); return; } sl->force_start = GNUNET_NO; if (GNUNET_YES == in_shutdown) { /* 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 (NULL == sl->proc) { /* process is down */ signal_result (client, servicename, request_id, GNUNET_ARM_RESULT_IS_STOPPED_ALREADY); return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "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; } /** * Write a string to a string pool. * * @param pool_start pointer to the start of the string pool * @param pool_size size of the string pool * @param[in,out] pool_pos current position index in the string pool, * will be updated * @param str string to write to the string pool * @returns GNUNET_OK if the string fits into the pool, * GNUNET_SYSERR otherwise */ static int pool_write (char *pool_start, size_t pool_size, size_t *pool_pos, char *str) { size_t next_pos = (*pool_pos) + strlen (str) + 1; if (next_pos > pool_size) return GNUNET_SYSERR; memcpy (pool_start + *pool_pos, str, strlen (str) + 1); *pool_pos = next_pos; return GNUNET_OK; } /** * Handle LIST-message. * * @param cls identification of the client * @param message the actual message */ static void handle_list (void *cls, const struct GNUNET_ARM_Message *request) { struct GNUNET_SERVICE_Client *client = cls; struct GNUNET_MQ_Envelope *env; struct GNUNET_ARM_ListResultMessage *msg; size_t extra_size; struct ServiceList *sl; uint16_t count; size_t pool_size; size_t pool_pos; char *pool_start; struct GNUNET_ARM_ServiceInfoMessage *ssm; GNUNET_break_op (0 == ntohl (request->reserved)); count = 0; pool_size = 0; /* Do one pass over the list to compute the number of services * and the string pool size */ for (sl = running_head; NULL != sl; sl = sl->next) { pool_size += strlen (sl->name) + 1; pool_size += strlen (sl->binary) + 1; count++; } extra_size = pool_size + (count * sizeof (struct GNUNET_ARM_ServiceInfoMessage)); env = GNUNET_MQ_msg_extra (msg, extra_size, GNUNET_MESSAGE_TYPE_ARM_LIST_RESULT); msg->arm_msg.request_id = request->request_id; msg->count = htons (count); ssm = (struct GNUNET_ARM_ServiceInfoMessage *) &msg[1]; pool_start = (char *) (ssm + count); pool_pos = 0; for (sl = running_head; NULL != sl; sl = sl->next) { ssm->name_index = htons ((uint16_t) pool_pos); GNUNET_assert (GNUNET_OK == pool_write (pool_start, pool_size, &pool_pos, sl->name)); ssm->binary_index = htons ((uint16_t) pool_pos); GNUNET_assert (GNUNET_OK == pool_write (pool_start, pool_size, &pool_pos, sl->binary)); if (NULL == sl->proc) { if (0 == sl->last_started_at.abs_value_us) { /* Process never started */ ssm->status = htonl (GNUNET_ARM_SERVICE_STATUS_STOPPED); } else if (0 == sl->last_exit_status) { ssm->status = htonl (GNUNET_ARM_SERVICE_STATUS_FINISHED); } else { ssm->status = htonl (GNUNET_ARM_SERVICE_STATUS_FAILED); ssm->last_exit_status = htons (sl->last_exit_status); } } else if ((NULL != sl->killing_client) || (GNUNET_YES == in_shutdown)) { ssm->status = htonl (GNUNET_ARM_SERVICE_STATUS_STOPPING); } else { ssm->status = htonl (GNUNET_ARM_SERVICE_STATUS_STARTED); } ssm->last_started_at = GNUNET_TIME_absolute_hton (sl->last_started_at); ssm->restart_at = GNUNET_TIME_absolute_hton (sl->restart_at); ssm++; } GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), env); GNUNET_SERVICE_client_continue (client); } /** * Handle TEST-message by sending back TEST. * * @param cls identification of the client * @param message the actual message */ static void handle_test (void *cls, const struct GNUNET_MessageHeader *message) { struct GNUNET_SERVICE_Client *client = cls; struct GNUNET_MQ_Envelope *env; struct GNUNET_MessageHeader *msg; (void) message; 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); } /** * We are done with everything. Stop remaining * tasks, signal handler and the server. */ static void do_shutdown () { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Last shutdown phase\n"); if (NULL != notifier) { GNUNET_notification_context_destroy (notifier); notifier = NULL; } if (NULL != service) { GNUNET_SERVICE_shutdown (service); service = NULL; } if (NULL != child_death_task) { GNUNET_SCHEDULER_cancel (child_death_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; NULL != 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 */ static void shutdown_task (void *cls) { struct ServiceList *pos; struct ServiceList *nxt; struct ServiceListeningInfo *sli; (void) cls; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "First shutdown phase\n"); if (NULL != child_restart_task) { GNUNET_SCHEDULER_cancel (child_restart_task); child_restart_task = NULL; } in_shutdown = GNUNET_YES; /* 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 (NULL != pos->proc) { 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); } } /* 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)); } /** * Task run whenever it is time to restart a child that died. * * @param cls closure, always NULL */ static void delayed_restart_task (void *cls) { struct ServiceList *sl; struct GNUNET_TIME_Relative lowestRestartDelay; struct ServiceListeningInfo *sli; (void) cls; 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 */ for (sl = running_head; NULL != sl; sl = sl->next) { if (NULL != sl->proc) continue; /* service is currently not running */ if (0 == GNUNET_TIME_absolute_get_remaining (sl->restart_at).rel_value_us) { /* restart is now allowed */ if (sl->force_start) { /* 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 { /* 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); } } } 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_us != GNUNET_TIME_UNIT_FOREVER_REL.rel_value_us) { 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_with_priority (lowestRestartDelay, GNUNET_SCHEDULER_PRIORITY_IDLE, &delayed_restart_task, NULL); } } /** * Task triggered whenever we receive a SIGCHLD (child * process died). * * @param cls closure, NULL */ static void 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; (void) cls; 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))); /* check for services that died (WAITPID) */ next = running_head; while (NULL != (pos = next)) { next = pos->next; if (NULL == pos->proc) { if (GNUNET_YES == in_shutdown) free_service (pos); continue; } #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) { statstr = _ (/* process termination method */ "exit"); statcode = statusCode; } else if (statusType == GNUNET_OS_PROCESS_SIGNALED) { statstr = _ (/* process termination method */ "signal"); statcode = statusCode; } else { statstr = _ (/* process termination method */ "unknown"); statcode = 0; } 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) { signal_result (pos->killing_client, pos->name, pos->killing_client_request_id, GNUNET_ARM_RESULT_STOPPED); pos->killing_client = NULL; pos->killing_client_request_id = 0; } if (GNUNET_YES != in_shutdown) { pos->last_exit_status = statcode; 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_WARNING, _ ("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)); { /* Reduce backoff based on runtime of the process, so that there is a cool-down if a process actually runs for a while. */ struct GNUNET_TIME_Relative runtime; unsigned int minutes; runtime = GNUNET_TIME_absolute_get_duration (pos->restart_at); minutes = runtime.rel_value_us / GNUNET_TIME_UNIT_MINUTES.rel_value_us; if (minutes > 31) pos->backoff = GNUNET_TIME_UNIT_ZERO; else pos->backoff.rel_value_us <<= minutes; } /* 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); } } else { free_service (pos); } } 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 */ } /** * 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 ServiceList *sl; char *binary; char *config; struct stat sbuf; struct sockaddr **addrs; socklen_t *addr_lens; int ret; (void) cls; if (0 == strcasecmp (section, "arm")) return; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, section, "BINARY", &binary)) { /* not a service section */ return; } if ((GNUNET_YES == GNUNET_CONFIGURATION_have_value (cfg, section, "RUN_PER_USER")) && (GNUNET_YES == GNUNET_CONFIGURATION_get_value_yesno (cfg, section, "RUN_PER_USER"))) { 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 (GNUNET_CONFIGURATION_have_value (cfg, section, "PIPECONTROL")) sl->pipe_control = GNUNET_CONFIGURATION_get_value_yesno (cfg, section, "PIPECONTROL"); GNUNET_CONTAINER_DLL_insert (running_head, running_tail, sl); if (GNUNET_YES == GNUNET_CONFIGURATION_get_value_yesno (cfg, section, "IMMEDIATE_START")) { 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, "START_ON_DEMAND")) return; } if (0 >= (ret = get_server_addresses (section, cfg, &addrs, &addr_lens))) return; /* this will free (or capture) addrs[i] */ for (unsigned int i = 0; i < (unsigned int) ret; i++) create_listen_socket (addrs[i], addr_lens[i], sl); GNUNET_free (addrs); GNUNET_free (addr_lens); } /** * 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). */ (void) cls; (void) mq; GNUNET_SERVICE_client_mark_monitor (client); return client; } /** * A client disconnected, clean up associated state. * * @param cls closure * @param client identification of the client * @param app_ctx must match @a client */ static void client_disconnect_cb (void *cls, struct GNUNET_SERVICE_Client *client, void *app_ctx) { (void) cls; GNUNET_assert (client == app_ctx); for (struct ServiceList *sl = running_head; NULL != sl; sl = sl->next) if (sl->killing_client == client) sl->killing_client = NULL; } /** * 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 handle_monitor (void *cls, const struct GNUNET_MessageHeader *message) { struct GNUNET_SERVICE_Client *client = cls; (void) message; /* 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); } /** * Process arm requests. * * @param cls closure, NULL * @param serv the initialized service * @param c configuration to use */ static void run (void *cls, const struct GNUNET_CONFIGURATION_Handle *c, struct GNUNET_SERVICE_Handle *serv) { struct ServiceList *sl; (void) cls; cfg = c; service = serv; GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL); child_death_task = 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", &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", &final_option)) final_option = GNUNET_strdup (""); else final_option = GNUNET_CONFIGURATION_expand_dollar (cfg, final_option); start_user = GNUNET_CONFIGURATION_get_value_yesno (cfg, "ARM", "START_USER_SERVICES"); start_system = GNUNET_CONFIGURATION_get_value_yesno (cfg, "ARM", "START_SYSTEM_SERVICES"); if ((GNUNET_NO == start_user) && (GNUNET_NO == start_system)) { GNUNET_log ( GNUNET_ERROR_TYPE_ERROR, "Please configure either START_USER_SERVICES or START_SYSTEM_SERVICES or both.\n"); GNUNET_SCHEDULER_shutdown (); global_ret = 1; return; } GNUNET_CONFIGURATION_iterate_sections (cfg, &setup_service, 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); } /** * The main function for the arm service. * * @param argc number of arguments from the command line * @param argv command line arguments * @return 0 ok, 1 on error */ int main (int argc, char *const *argv) { 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_DISK_PF_NONE); GNUNET_assert (NULL != sigpipe); shc_chld = GNUNET_SIGNAL_handler_install (GNUNET_SIGCHLD, &sighandler_child_death); if (0 != GNUNET_SERVICE_run_ (argc, argv, "arm", GNUNET_SERVICE_OPTION_MANUAL_SHUTDOWN | GNUNET_SERVICE_OPTION_CLOSE_LSOCKS, &run, &client_connect_cb, &client_disconnect_cb, NULL, handlers)) global_ret = 2; #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); sigpipe = NULL; return global_ret; } #if defined(__linux__) && defined(__GLIBC__) #include /** * MINIMIZE heap size (way below 128k) since this process doesn't need much. */ void __attribute__ ((constructor)) GNUNET_ARM_memory_init () { mallopt (M_TRIM_THRESHOLD, 4 * 1024); mallopt (M_TOP_PAD, 1 * 1024); malloc_trim (0); } #endif /* end of gnunet-service-arm.c */