helper shutdown on stdin close
[oweals/gnunet.git] / src / testbed / gnunet-testbed-helper.c
index 4c202193bb76a0ea35efc0d20a563c82b245d1a5..215f680b3c2ad3997e47cbb4577c082a85db989e 100644 (file)
@@ -33,7 +33,9 @@
 #include "platform.h"
 #include "gnunet_util_lib.h"
 #include "gnunet_testing_lib-new.h"
+#include "gnunet_testbed_service.h"
 #include "testbed_helper.h"
+#include "testbed_api.h"
 #include <zlib.h>
 
 /**
 #define LOG_DEBUG(...)                          \
   LOG (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__)
 
+
+/**
+ * Context for a single write on a chunk of memory
+ */
+struct WriteContext
+{
+  /**
+   * The data to write
+   */
+  void *data;
+
+  /**
+   * The length of the data
+   */
+  size_t length;
+
+  /**
+   * The current position from where the write operation should begin
+   */
+  size_t pos;
+};
+
+
 /**
  * Handle to the testing system
  */
@@ -64,24 +89,24 @@ struct GNUNET_SERVER_MessageStreamTokenizer *tokenizer;
 static struct GNUNET_DISK_FileHandle *stdin_fd;
 
 /**
- * The process handle to the testbed service
+ * Disk handle for stdout
  */
-static struct GNUNET_OS_Process *testbed;
+static struct GNUNET_DISK_FileHandle *stdout_fd;
 
 /**
- * Pipe handle to child's stdin
+ * The process handle to the testbed service
  */
-static struct GNUNET_DISK_PipeHandle *pipe_in;
+static struct GNUNET_OS_Process *testbed;
 
 /**
- * Pipe handle to child's stdout
+ * Task identifier for the read task
  */
-static struct GNUNET_DISK_PipeHandle *pipe_out;
+static GNUNET_SCHEDULER_TaskIdentifier read_task_id;
 
 /**
- * Task identifier for the read task
+ * Task identifier for the write task
  */
-static GNUNET_SCHEDULER_TaskIdentifier read_task_id;
+static GNUNET_SCHEDULER_TaskIdentifier write_task_id;
 
 /**
  * Are we done reading messages from stdin?
@@ -91,49 +116,90 @@ static int done_reading;
 /**
  * Result to return in case we fail
  */
-static int ret;
+static int status;
+
+
+/**
+ * Are we shutting down
+ */
+static int in_shutdown;
 
 
 /**
  * Task to shutting down nicely
  *
  * @param cls NULL
- * @return tc the task context
+ * @param tc the task context
  */
 static void
 shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
 {
   LOG_DEBUG ("Shutting down\n");
+  in_shutdown = GNUNET_YES;
   if (GNUNET_SCHEDULER_NO_TASK != read_task_id)
   {
     GNUNET_SCHEDULER_cancel (read_task_id);
     read_task_id = GNUNET_SCHEDULER_NO_TASK;
   }
-  (void) GNUNET_DISK_file_close (stdin_fd);
+  if (GNUNET_SCHEDULER_NO_TASK != write_task_id)
+  {
+    GNUNET_SCHEDULER_cancel (write_task_id);
+    write_task_id = GNUNET_SCHEDULER_NO_TASK;
+  }
+  if (NULL != stdin_fd)
+    (void) GNUNET_DISK_file_close (stdin_fd);
+  if (NULL != stdout_fd)
+    (void) GNUNET_DISK_file_close (stdout_fd);
   GNUNET_SERVER_mst_destroy (tokenizer);  
   tokenizer = NULL;
   if (NULL != testbed)
   {
-    (void) GNUNET_OS_process_kill (testbed, SIGTERM);
+    LOG_DEBUG ("Killing testbed\n");
+    GNUNET_break (0 == GNUNET_OS_process_kill (testbed, SIGTERM));
     GNUNET_assert (GNUNET_OK == GNUNET_OS_process_wait (testbed));
     GNUNET_OS_process_destroy (testbed);
     testbed = NULL;
   }
-  if (NULL != pipe_in)
+  if (NULL != test_system)
   {
-    (void) GNUNET_DISK_pipe_close (pipe_in);
-    pipe_in = NULL;
+    GNUNET_TESTING_system_destroy (test_system, GNUNET_YES);
+    test_system = NULL;
   }
-  if (NULL != pipe_out)
+}
+
+
+/**
+ * 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)
+{
+  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))
   {
-    (void) GNUNET_DISK_pipe_close (pipe_out);
-    pipe_out = NULL;
+    GNUNET_free (wc->data);
+    GNUNET_free (wc);
+    return;
   }
-  if (NULL != test_system)
+  bytes_wrote = GNUNET_DISK_file_write (stdout_fd, wc->data + wc->pos,
+                                        wc->length - wc->pos);
+  GNUNET_assert (GNUNET_SYSERR != bytes_wrote);
+  wc->pos += bytes_wrote;
+  if (wc->pos == wc->length)
   {
-    GNUNET_TESTING_system_destroy (test_system, GNUNET_YES);
-    test_system = NULL;
+    GNUNET_free (wc->data);
+    GNUNET_free (wc);
+    return;
   }
+  write_task_id = GNUNET_SCHEDULER_add_write_file
+    (GNUNET_TIME_UNIT_FOREVER_REL, stdout_fd, &write_task, wc);
 }
 
 
@@ -154,11 +220,15 @@ tokenizer_cb (void *cls, void *client,
               const struct GNUNET_MessageHeader *message)
 {
   const struct GNUNET_TESTBED_HelperInit *msg;
+  struct GNUNET_TESTBED_HelperReply *reply;
   struct GNUNET_CONFIGURATION_Handle *cfg;
+  struct WriteContext *wc;
   char *controller;
   char *config;
-  uLongf config_size;
-  uint16_t xconfig_size;
+  char *xconfig;
+  size_t config_size;
+  uLongf ul_config_size;
+  size_t xconfig_size;
   uint16_t cname_size;
 
   if ((sizeof (struct GNUNET_TESTBED_HelperInit) >= ntohs (message->size)) ||
@@ -177,10 +247,11 @@ tokenizer_cb (void *cls, void *client,
          "Controller name cannot be empty -- exiting\n");
     goto error;
   }
-  config_size = (uLongf) ntohs (msg->config_size);
-  config = GNUNET_malloc (config_size);
-  xconfig_size = ntohs (message->size) - (cname_size + 1);
-  if (Z_OK != uncompress ((Bytef *) config, &config_size,
+  ul_config_size = (uLongf) ntohs (msg->config_size);
+  config = GNUNET_malloc (ul_config_size);
+  xconfig_size = ntohs (message->size) - 
+    (cname_size + 1 + sizeof (struct GNUNET_TESTBED_HelperInit));
+  if (Z_OK != uncompress ((Bytef *) config, &ul_config_size,
                           (const Bytef *) (controller + cname_size + 1),
                           (uLongf) xconfig_size))
   {
@@ -190,8 +261,8 @@ tokenizer_cb (void *cls, void *client,
     goto error;
   }
   cfg = GNUNET_CONFIGURATION_create ();  
-  if (GNUNET_OK != GNUNET_CONFIGURATION_deserialize (cfg, config, config_size,
-                                                     GNUNET_NO))
+  if (GNUNET_OK != GNUNET_CONFIGURATION_deserialize (cfg, config, 
+                                                     ul_config_size, GNUNET_NO))
   {
     LOG (GNUNET_ERROR_TYPE_WARNING, 
          "Unable to deserialize config -- exiting\n");
@@ -205,39 +276,47 @@ tokenizer_cb (void *cls, void *client,
                  (test_system, cfg));
   GNUNET_assert (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string 
                  (cfg, "PATHS", "DEFAULTCONFIG", &config));
-  pipe_in = GNUNET_DISK_pipe (GNUNET_NO, GNUNET_NO, GNUNET_YES, GNUNET_NO);
-  pipe_out = GNUNET_DISK_pipe (GNUNET_NO, GNUNET_NO, GNUNET_NO, GNUNET_YES);
-  if ((NULL == pipe_in) || (NULL == pipe_out))
-  {
-    GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "pipe");
-    GNUNET_free (config);
-    goto error;
-  }
   if (GNUNET_OK != GNUNET_CONFIGURATION_write (cfg, config))
   {
     LOG (GNUNET_ERROR_TYPE_WARNING, 
          "Unable to write config file: %s -- exiting\n", config);
+    GNUNET_CONFIGURATION_destroy (cfg);
     GNUNET_free (config);
     goto error;
   }
   LOG_DEBUG ("Staring testbed with config: %s\n", config);
   testbed = GNUNET_OS_start_process 
-    (GNUNET_YES, GNUNET_OS_INHERIT_STD_ERR /*verbose? */, pipe_in, pipe_out,
+    (GNUNET_YES, GNUNET_OS_INHERIT_STD_ERR /*verbose? */, NULL, NULL,
      "gnunet-service-testbed", "gnunet-service-testbed", "-c", config, NULL);
   GNUNET_free (config);
   if (NULL == testbed)
   {
     LOG (GNUNET_ERROR_TYPE_WARNING, 
          "Error staring gnunet-service-testbed -- exiting\n");
+    GNUNET_CONFIGURATION_destroy (cfg);
     goto error;
   }
-  GNUNET_DISK_pipe_close_end (pipe_out, GNUNET_DISK_PIPE_END_WRITE);
-  GNUNET_DISK_pipe_close_end (pipe_in, GNUNET_DISK_PIPE_END_READ);
   done_reading = GNUNET_YES;
+  config = GNUNET_CONFIGURATION_serialize (cfg, &config_size);
+  GNUNET_CONFIGURATION_destroy (cfg);
+  cfg = NULL;
+  xconfig_size = GNUNET_TESTBED_compress_config_ (config, config_size,
+                                                 &xconfig);
+  GNUNET_free (config);
+  wc = GNUNET_malloc (sizeof (struct WriteContext));
+  wc->length = xconfig_size + sizeof (struct GNUNET_TESTBED_HelperReply);
+  reply = GNUNET_realloc (xconfig, wc->length);
+  memmove (&reply[1], reply, xconfig_size);
+  reply->header.type = htons (GNUNET_MESSAGE_TYPE_TESTBED_HELPER_REPLY);
+  reply->header.size = htons ((uint16_t) wc->length);
+  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, &write_task, wc);       
   return GNUNET_OK;
   
  error:
-  ret = GNUNET_SYSERR;
+  status = GNUNET_SYSERR;
   GNUNET_SCHEDULER_shutdown ();
   return GNUNET_SYSERR;
 }
@@ -247,7 +326,7 @@ tokenizer_cb (void *cls, void *client,
  * Task to read from stdin
  *
  * @param cls NULL
- * @return tc the task context
+ * @param tc the task context
  */
 static void
 read_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
@@ -259,16 +338,14 @@ read_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
   if (0 != (GNUNET_SCHEDULER_REASON_SHUTDOWN & tc->reason))
     return;  
   sread = GNUNET_DISK_file_read (stdin_fd, buf, sizeof (buf));
-  if (GNUNET_SYSERR == sread)
+  if ((GNUNET_SYSERR == sread) || (0 == sread))
   {
-    GNUNET_break (0);           /* FIXME: stdin closed - kill child */
     GNUNET_SCHEDULER_shutdown ();
     return;
   }
   if (GNUNET_YES == done_reading)
   {
     /* didn't expect any more data! */
-    GNUNET_break (0);
     GNUNET_SCHEDULER_shutdown ();
     return;
   }
@@ -302,6 +379,7 @@ run (void *cls, char *const *args, const char *cfgfile,
   LOG_DEBUG ("Starting testbed helper...\n");
   tokenizer = GNUNET_SERVER_mst_create (&tokenizer_cb, NULL);
   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, &read_task, NULL);
@@ -310,6 +388,23 @@ run (void *cls, char *const *args, const char *cfgfile,
 }
 
 
+/**
+ * Signal handler called for SIGCHLD.
+ */
+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! */
+  }  
+}
+
+
 /**
  * Main function
  *
@@ -319,17 +414,25 @@ run (void *cls, char *const *args, const char *cfgfile,
  */
 int main (int argc, char **argv)
 {
+  struct GNUNET_SIGNAL_Context *shc_chld;
   struct GNUNET_GETOPT_CommandLineOption options[] = {
     GNUNET_GETOPT_OPTION_END
   };
+  int ret;
 
-  ret = GNUNET_OK;
-  if (GNUNET_OK != 
-      GNUNET_PROGRAM_run (argc, argv, "gnunet-testbed-helper",
-                         "Helper for starting gnunet-service-testbed",
-                         options, &run, NULL))
+  status = GNUNET_OK;
+  in_shutdown = GNUNET_NO;
+  //sleep (60);
+  shc_chld =
+    GNUNET_SIGNAL_handler_install (GNUNET_SIGCHLD, &sighandler_child_death);
+  ret = GNUNET_PROGRAM_run (argc, argv, "gnunet-testbed-helper",
+                                "Helper for starting gnunet-service-testbed",
+                           options, &run, NULL);
+  GNUNET_SIGNAL_handler_uninstall (shc_chld);
+  shc_chld = NULL;
+  if (GNUNET_OK != ret)
     return 1;
-  return (GNUNET_OK == ret) ? 0 : 1;
+  return (GNUNET_OK == status) ? 0 : 1;
 }
 
 /* end of gnunet-testbed-helper.c */