X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=src%2Ftestbed%2Fgnunet-helper-testbed.c;h=f3c56eb9db7806a904bb636b7c47f62a3f261848;hb=5742938289524f4c5fba7883742e4dd69cccf11d;hp=97409c4511351107ca09dc8ab4a0ceb13245070b;hpb=c408abb9b9069c9eee62f1ca78ee3a815b491f14;p=oweals%2Fgnunet.git diff --git a/src/testbed/gnunet-helper-testbed.c b/src/testbed/gnunet-helper-testbed.c index 97409c451..f3c56eb9d 100644 --- a/src/testbed/gnunet-helper-testbed.c +++ b/src/testbed/gnunet-helper-testbed.c @@ -1,6 +1,6 @@ /* This file is part of GNUnet - (C) 2012 Christian Grothoff (and other contributing authors) + Copyright (C) 2008--2013 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,8 +14,8 @@ 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. */ /** @@ -24,15 +24,23 @@ * gnunet-service-testbed. This binary also receives configuration * from the remove controller which is put in a temporary location * with ports and paths fixed so that gnunet-service-testbed runs - * without any hurdles. This binary also kills the testbed service - * should the connection from the remote controller is dropped + * without any hurdles. + * + * This helper monitors for three termination events. They are: (1)The + * stdin of the helper is closed for reading; (2)the helper received + * SIGTERM/SIGINT; (3)the testbed crashed. In case of events 1 and 2 + * the helper kills the testbed service. When testbed crashed (event + * 3), the helper should send a SIGTERM to its own process group; this + * behaviour will help terminate any child processes (peers) testbed + * has started and prevents them from leaking and running forever. + * * @author Sree Harsha Totakura */ #include "platform.h" #include "gnunet_util_lib.h" -#include "gnunet_testing_lib-new.h" +#include "gnunet_testing_lib.h" #include "gnunet_testbed_service.h" #include "testbed_helper.h" #include "testbed_api.h" @@ -108,15 +116,25 @@ static struct GNUNET_DISK_FileHandle *stdout_fd; */ static struct GNUNET_OS_Process *testbed; +/** + * Pipe used to communicate shutdown via signal. + */ +static struct GNUNET_DISK_PipeHandle *sigpipe; + /** * Task identifier for the read task */ -static GNUNET_SCHEDULER_TaskIdentifier read_task_id; +static struct GNUNET_SCHEDULER_Task *read_task_id; /** * Task identifier for the write task */ -static GNUNET_SCHEDULER_TaskIdentifier write_task_id; +static struct GNUNET_SCHEDULER_Task *write_task_id; + +/** + * Task to kill the child + */ +static struct GNUNET_SCHEDULER_Task * child_death_task_id; /** * Are we done reading messages from stdin? @@ -130,31 +148,37 @@ static int status; /** - * Are we shutting down - */ -static int in_shutdown; - - -/** - * Task to shutting down nicely + * Task to shut down cleanly * * @param cls NULL - * @param tc the task context */ static void -shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +shutdown_task (void *cls) { LOG_DEBUG ("Shutting down\n"); - in_shutdown = GNUNET_YES; - if (GNUNET_SCHEDULER_NO_TASK != read_task_id) + if (NULL != testbed) + { + LOG_DEBUG ("Killing testbed\n"); + GNUNET_break (0 == GNUNET_OS_process_kill (testbed, GNUNET_TERM_SIG)); + } + if (NULL != read_task_id) { GNUNET_SCHEDULER_cancel (read_task_id); - read_task_id = GNUNET_SCHEDULER_NO_TASK; + read_task_id = NULL; + } + if (NULL != write_task_id) + { + struct WriteContext *wc; + + wc = GNUNET_SCHEDULER_cancel (write_task_id); + write_task_id = NULL; + GNUNET_free (wc->data); + GNUNET_free (wc); } - if (GNUNET_SCHEDULER_NO_TASK != write_task_id) + if (NULL != child_death_task_id) { - GNUNET_SCHEDULER_cancel (write_task_id); - write_task_id = GNUNET_SCHEDULER_NO_TASK; + GNUNET_SCHEDULER_cancel (child_death_task_id); + child_death_task_id = NULL; } if (NULL != stdin_fd) (void) GNUNET_DISK_file_close (stdin_fd); @@ -164,9 +188,7 @@ shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) tokenizer = NULL; if (NULL != testbed) { - LOG_DEBUG ("Killing testbed\n"); - GNUNET_break (0 == GNUNET_OS_process_kill (testbed, SIGTERM)); - GNUNET_assert (GNUNET_OK == GNUNET_OS_process_wait (testbed)); + GNUNET_break (GNUNET_OK == GNUNET_OS_process_wait (testbed)); GNUNET_OS_process_destroy (testbed); testbed = NULL; } @@ -182,28 +204,22 @@ shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) * Task to write to the standard out * * @param cls the WriteContext - * @param tc the TaskContext */ static void -write_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +write_task (void *cls) { struct WriteContext *wc = cls; ssize_t bytes_wrote; GNUNET_assert (NULL != wc); - write_task_id = GNUNET_SCHEDULER_NO_TASK; - if (0 != (GNUNET_SCHEDULER_REASON_SHUTDOWN & tc->reason)) - { - GNUNET_free (wc->data); - GNUNET_free (wc); - return; - } + write_task_id = NULL; bytes_wrote = GNUNET_DISK_file_write (stdout_fd, wc->data + wc->pos, wc->length - wc->pos); if (GNUNET_SYSERR == bytes_wrote) { - LOG (GNUNET_ERROR_TYPE_WARNING, "Cannot reply back configuration\n"); + LOG (GNUNET_ERROR_TYPE_WARNING, + "Cannot reply back configuration\n"); GNUNET_free (wc->data); GNUNET_free (wc); return; @@ -216,22 +232,69 @@ write_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) return; } write_task_id = - GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL, stdout_fd, + GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL, + stdout_fd, &write_task, wc); } +/** + * Task triggered whenever we receive a SIGCHLD (child + * process died). + * + * @param cls closure, NULL if we need to self-restart + */ +static void +child_death_task (void *cls) +{ + const struct GNUNET_DISK_FileHandle *pr; + char c[16]; + enum GNUNET_OS_ProcessStatusType type; + unsigned long code; + int ret; + + pr = GNUNET_DISK_pipe_handle (sigpipe, GNUNET_DISK_PIPE_END_READ); + child_death_task_id = NULL; + /* consume the signal */ + GNUNET_break (0 < GNUNET_DISK_file_read (pr, &c, sizeof (c))); + LOG_DEBUG ("Got SIGCHLD\n"); + if (NULL == testbed) + { + GNUNET_break (0); + return; + } + GNUNET_break (GNUNET_SYSERR != + (ret = GNUNET_OS_process_status (testbed, &type, &code))); + if (GNUNET_NO != ret) + { + GNUNET_OS_process_destroy (testbed); + testbed = NULL; + /* Send SIGTERM to our process group */ + if (0 != PLIBC_KILL (0, GNUNET_TERM_SIG)) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "signal"); + GNUNET_SCHEDULER_shutdown (); /* Couldn't send the signal, we shutdown frowning */ + } + return; + } + LOG_DEBUG ("Child hasn't died. Resuming to monitor its status\n"); + child_death_task_id = + GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, + pr, &child_death_task, NULL); +} + + /** * Functions with this signature are called whenever a * complete message is received by the tokenizer. * - * Do not call GNUNET_SERVER_mst_destroy in callback + * Do not call #GNUNET_SERVER_mst_destroy() in this callback * * @param cls closure * @param client identification of the client * @param message the actual message * - * @return GNUNET_OK on success, GNUNET_SYSERR to stop further processing + * @return #GNUNET_OK on success, #GNUNET_SYSERR to stop further processing */ static int tokenizer_cb (void *cls, void *client, @@ -246,13 +309,15 @@ tokenizer_cb (void *cls, void *client, char *hostname; char *config; char *xconfig; + char *evstr; + //char *str; size_t config_size; uLongf ul_config_size; size_t xconfig_size; uint16_t trusted_ip_size; uint16_t hostname_size; uint16_t msize; - + msize = ntohs (message->size); if ((sizeof (struct GNUNET_TESTBED_HelperInit) >= msize) || (GNUNET_MESSAGE_TYPE_TESTBED_HELPER_INIT != ntohs (message->type))) @@ -265,8 +330,7 @@ tokenizer_cb (void *cls, void *client, trusted_ip = (char *) &msg[1]; if ('\0' != trusted_ip[trusted_ip_size]) { - LOG (GNUNET_ERROR_TYPE_WARNING, - "Trusted IP cannot be empty -- exiting\n"); + LOG (GNUNET_ERROR_TYPE_WARNING, "Trusted IP cannot be empty -- exiting\n"); goto error; } hostname_size = ntohs (msg->hostname_size); @@ -276,7 +340,7 @@ tokenizer_cb (void *cls, void *client, GNUNET_break (0); LOG (GNUNET_ERROR_TYPE_WARNING, "Received unexpected message -- exiting\n"); goto error; - } + } ul_config_size = (uLongf) ntohs (msg->config_size); config = GNUNET_malloc (ul_config_size); xconfig_size = @@ -284,8 +348,8 @@ tokenizer_cb (void *cls, void *client, sizeof (struct GNUNET_TESTBED_HelperInit)); if (Z_OK != uncompress ((Bytef *) config, &ul_config_size, - (const Bytef *) (trusted_ip + trusted_ip_size + 1 + hostname_size), - (uLongf) xconfig_size)) + (const Bytef *) (trusted_ip + trusted_ip_size + 1 + + hostname_size), (uLongf) xconfig_size)) { LOG (GNUNET_ERROR_TYPE_WARNING, "Error while uncompressing config -- exiting\n"); @@ -306,18 +370,49 @@ tokenizer_cb (void *cls, void *client, if (0 != hostname_size) { hostname = GNUNET_malloc (hostname_size + 1); - (void) strncpy (hostname, ((char *) &msg[1]) + trusted_ip_size + 1, hostname_size); + (void) strncpy (hostname, ((char *) &msg[1]) + trusted_ip_size + 1, + hostname_size); hostname[hostname_size] = '\0'; } - test_system = GNUNET_TESTING_system_create ("testbed-helper", trusted_ip, - hostname); + /* unset GNUNET_TESTING_PREFIX if present as it is more relevant for testbed */ + evstr = getenv (GNUNET_TESTING_PREFIX); + if (NULL != evstr) + { + /* unsetting the variable will invalidate the pointer! */ + evstr = GNUNET_strdup (evstr); +#ifdef WINDOWS + GNUNET_break (0 != SetEnvironmentVariable (GNUNET_TESTING_PREFIX, NULL)); +#else + GNUNET_break (0 == unsetenv (GNUNET_TESTING_PREFIX)); +#endif + } + test_system = + GNUNET_TESTING_system_create ("testbed-helper", trusted_ip, hostname, + NULL); + if (NULL != evstr) + { +#ifdef WINDOWS + GNUNET_assert (0 != SetEnvironmentVariable (GNUNET_TESTING_PREFIX, + evstr)); +#else + char *evar; + + GNUNET_asprintf (&evar, + GNUNET_TESTING_PREFIX "=%s", + evstr); + GNUNET_assert (0 == putenv (evar)); /* consumes 'evar', + see putenv(): becomes part of envrionment! */ +#endif + GNUNET_free (evstr); + evstr = NULL; + } GNUNET_free_non_null (hostname); hostname = NULL; GNUNET_assert (NULL != test_system); GNUNET_assert (GNUNET_OK == GNUNET_TESTING_configuration_create (test_system, cfg)); GNUNET_assert (GNUNET_OK == - GNUNET_CONFIGURATION_get_value_string (cfg, "PATHS", + GNUNET_CONFIGURATION_get_value_filename (cfg, "PATHS", "DEFAULTCONFIG", &config)); if (GNUNET_OK != GNUNET_CONFIGURATION_write (cfg, config)) @@ -330,12 +425,26 @@ tokenizer_cb (void *cls, void *client, } LOG_DEBUG ("Staring testbed with config: %s\n", config); binary = GNUNET_OS_get_libexec_binary_path ("gnunet-service-testbed"); + { + char *evar; + + /* expose testbed configuration through env variable */ + GNUNET_asprintf (&evar, + "%s=%s", + ENV_TESTBED_CONFIG, + config); + GNUNET_assert (0 == putenv (evar)); /* consumes 'evar', + see putenv(): becomes part of envrionment! */ + evstr = NULL; + } testbed = GNUNET_OS_start_process (PIPE_CONTROL, - GNUNET_OS_INHERIT_STD_ERR /*verbose? */ , NULL, - NULL, - binary, - "gnunet-service-testbed", "-c", config, NULL); + GNUNET_OS_INHERIT_STD_ERR /*verbose? */ , + NULL, NULL, NULL, + binary, + "gnunet-service-testbed", + "-c", config, + NULL); GNUNET_free (binary); GNUNET_free (config); if (NULL == testbed) @@ -352,7 +461,7 @@ tokenizer_cb (void *cls, void *client, xconfig_size = GNUNET_TESTBED_compress_config_ (config, config_size, &xconfig); GNUNET_free (config); - wc = GNUNET_malloc (sizeof (struct WriteContext)); + wc = GNUNET_new (struct WriteContext); wc->length = xconfig_size + sizeof (struct GNUNET_TESTBED_HelperReply); reply = GNUNET_realloc (xconfig, wc->length); memmove (&reply[1], reply, xconfig_size); @@ -361,8 +470,14 @@ tokenizer_cb (void *cls, void *client, reply->config_size = htons ((uint16_t) config_size); wc->data = reply; write_task_id = - GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL, stdout_fd, + GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL, + stdout_fd, &write_task, wc); + child_death_task_id = + GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, + GNUNET_DISK_pipe_handle (sigpipe, + GNUNET_DISK_PIPE_END_READ), + &child_death_task, NULL); return GNUNET_OK; error: @@ -376,30 +491,30 @@ error: * Task to read from stdin * * @param cls NULL - * @param tc the task context */ static void -read_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +read_task (void *cls) { char buf[GNUNET_SERVER_MAX_MESSAGE_SIZE]; ssize_t sread; - read_task_id = GNUNET_SCHEDULER_NO_TASK; - if (0 != (GNUNET_SCHEDULER_REASON_SHUTDOWN & tc->reason)) - return; + read_task_id = NULL; sread = GNUNET_DISK_file_read (stdin_fd, buf, sizeof (buf)); if ((GNUNET_SYSERR == sread) || (0 == sread)) { + LOG_DEBUG ("STDIN closed\n"); GNUNET_SCHEDULER_shutdown (); return; } if (GNUNET_YES == done_reading) { /* didn't expect any more data! */ + GNUNET_break_op (0); GNUNET_SCHEDULER_shutdown (); return; } - LOG_DEBUG ("Read %u bytes\n", sread); + LOG_DEBUG ("Read %u bytes\n", + (unsigned int) sread); if (GNUNET_OK != GNUNET_SERVER_mst_receive (tokenizer, NULL, buf, sread, GNUNET_NO, GNUNET_NO)) @@ -409,7 +524,8 @@ read_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) return; } read_task_id = /* No timeout while reading */ - GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, stdin_fd, + GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, + stdin_fd, &read_task, NULL); } @@ -423,7 +539,9 @@ read_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) * @param cfg configuration */ static void -run (void *cls, char *const *args, const char *cfgfile, +run (void *cls, + char *const *args, + const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *cfg) { LOG_DEBUG ("Starting testbed helper...\n"); @@ -431,10 +549,11 @@ run (void *cls, char *const *args, const char *cfgfile, stdin_fd = GNUNET_DISK_get_handle_from_native (stdin); stdout_fd = GNUNET_DISK_get_handle_from_native (stdout); read_task_id = - GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, stdin_fd, + GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, + stdin_fd, &read_task, NULL); - GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &shutdown_task, - NULL); + GNUNET_SCHEDULER_add_shutdown (&shutdown_task, + NULL); } @@ -444,14 +563,15 @@ run (void *cls, char *const *args, const char *cfgfile, static void sighandler_child_death () { - if ((NULL != testbed) && (GNUNET_NO == in_shutdown)) - { - LOG_DEBUG ("Child died\n"); - GNUNET_assert (GNUNET_OK == GNUNET_OS_process_wait (testbed)); - GNUNET_OS_process_destroy (testbed); - testbed = NULL; - GNUNET_SCHEDULER_shutdown (); /* We are done too! */ - } + static char c; + int old_errno; /* back-up errno */ + + old_errno = errno; + GNUNET_break (1 == + GNUNET_DISK_file_write (GNUNET_DISK_pipe_handle + (sigpipe, GNUNET_DISK_PIPE_END_WRITE), + &c, sizeof (c))); + errno = old_errno; } @@ -473,7 +593,12 @@ main (int argc, char **argv) int ret; status = GNUNET_OK; - in_shutdown = GNUNET_NO; + if (NULL == (sigpipe = GNUNET_DISK_pipe (GNUNET_NO, GNUNET_NO, + GNUNET_NO, GNUNET_NO))) + { + GNUNET_break (0); + return 1; + } shc_chld = GNUNET_SIGNAL_handler_install (GNUNET_SIGCHLD, &sighandler_child_death); ret = @@ -482,6 +607,7 @@ main (int argc, char **argv) &run, NULL); GNUNET_SIGNAL_handler_uninstall (shc_chld); shc_chld = NULL; + GNUNET_DISK_pipe_close (sigpipe); if (GNUNET_OK != ret) return 1; return (GNUNET_OK == status) ? 0 : 1;