+/**
+ * Handle for controller process
+ */
+struct GNUNET_TESTBED_ControllerProc
+{
+ /**
+ * The process handle
+ */
+ struct GNUNET_HELPER_Handle *helper;
+
+ /**
+ * The arguments used to start the helper
+ */
+ char **helper_argv;
+
+ /**
+ * The host where the helper is run
+ */
+ struct GNUNET_TESTBED_Host *host;
+
+ /**
+ * The controller error callback
+ */
+ GNUNET_TESTBED_ControllerStatusCallback cb;
+
+ /**
+ * The closure for the above callback
+ */
+ void *cls;
+
+ /**
+ * The send handle for the helper
+ */
+ struct GNUNET_HELPER_SendHandle *shandle;
+
+ /**
+ * The message corresponding to send handle
+ */
+ struct GNUNET_MessageHeader *msg;
+
+};
+
+
+/**
+ * Function to copy NULL terminated list of arguments
+ *
+ * @param argv the NULL terminated list of arguments. Cannot be NULL.
+ * @return the copied NULL terminated arguments
+ */
+static char **
+copy_argv (const char *const *argv)
+{
+ char **argv_dup;
+ unsigned int argp;
+
+ GNUNET_assert (NULL != argv);
+ for (argp = 0; NULL != argv[argp]; argp++) ;
+ argv_dup = GNUNET_malloc (sizeof (char *) * (argp + 1));
+ for (argp = 0; NULL != argv[argp]; argp++)
+ argv_dup[argp] = strdup (argv[argp]);
+ return argv_dup;
+}
+
+
+/**
+ * Function to join NULL terminated list of arguments
+ *
+ * @param argv1 the NULL terminated list of arguments. Cannot be NULL.
+ * @param argv2 the NULL terminated list of arguments. Cannot be NULL.
+ * @return the joined NULL terminated arguments
+ */
+static char **
+join_argv (const char *const *argv1, const char *const *argv2)
+{
+ char **argvj;
+ char *argv;
+ unsigned int carg;
+ unsigned int cnt;
+
+ carg = 0;
+ argvj = NULL;
+ for (cnt = 0; NULL != argv1[cnt]; cnt++)
+ {
+ argv = GNUNET_strdup (argv1[cnt]);
+ GNUNET_array_append (argvj, carg, argv);
+ }
+ for (cnt = 0; NULL != argv2[cnt]; cnt++)
+ {
+ argv = GNUNET_strdup (argv2[cnt]);
+ GNUNET_array_append (argvj, carg, argv);
+ }
+ GNUNET_array_append (argvj, carg, NULL);
+ return argvj;
+}
+
+
+/**
+ * Frees the given NULL terminated arguments
+ *
+ * @param argv the NULL terminated list of arguments
+ */
+static void
+free_argv (char **argv)
+{
+ unsigned int argp;
+
+ for (argp = 0; NULL != argv[argp]; argp++)
+ GNUNET_free (argv[argp]);
+ GNUNET_free (argv);
+}
+
+
+/**
+ * Generates arguments for opening a remote shell. Builds up the arguments
+ * from the environment variable GNUNET_TESTBED_RSH_CMD. The variable
+ * should not mention `-p' (port) option and destination address as these will
+ * be set locally in the function from its parameteres. If the environmental
+ * variable is not found then it defaults to `ssh -o BatchMode=yes -o
+ * NoHostAuthenticationForLocalhost=yes'
+ *
+ * @param port the destination port number
+ * @param hostname the hostname of the target host
+ * @param username the username to use while connecting to target host
+ * @return NULL terminated list of arguments
+ */
+static char **
+gen_rsh_args (const char *port, const char *hostname, const char *username)
+{
+ static const char *default_ssh_args[] = {
+ "ssh",
+ "-o",
+ "BatchMode=yes",
+ "-o",
+ "NoHostAuthenticationForLocalhost=yes",
+ "%h",
+ NULL
+ };
+ char **ssh_args;
+ char *ssh_cmd;
+ char *ssh_cmd_cp;
+ char *arg;
+ const char *new_arg;
+ unsigned int size;
+ unsigned int cnt;
+
+ ssh_args = NULL;
+ if (NULL != (ssh_cmd = getenv ("GNUNET_TESTBED_RSH_CMD")))
+ {
+ ssh_cmd = GNUNET_strdup (ssh_cmd);
+ ssh_cmd_cp = ssh_cmd;
+ for (size = 0; NULL != (arg = strtok (ssh_cmd, " ")); ssh_cmd = NULL)
+ GNUNET_array_append (ssh_args, size, GNUNET_strdup (arg));
+ GNUNET_free (ssh_cmd_cp);
+ }
+ else
+ {
+ ssh_args = copy_argv (default_ssh_args);
+ size = (sizeof (default_ssh_args)) / (sizeof (const char *));
+ GNUNET_array_grow (ssh_args, size, size - 1);
+ }
+ for (cnt = 0; cnt < size; cnt++)
+ {
+ arg = ssh_args[cnt];
+ if ('%' != arg[0])
+ continue;
+ switch (arg[1])
+ {
+ case 'p':
+ new_arg = port;
+ break;
+
+ case 'u':
+ new_arg = username;
+ break;
+
+ case 'h':
+ new_arg = hostname;
+ break;
+
+ default:
+ continue;
+ }
+ if (NULL == new_arg)
+ continue;
+ GNUNET_free (arg);
+ ssh_args[cnt] = GNUNET_strdup (new_arg);
+ }
+ GNUNET_array_append (ssh_args, size, NULL);
+ return ssh_args;
+}
+
+
+/**
+ * Generates the arguments needed for executing the given binary in a remote
+ * shell. Builds the arguments from the environmental variable
+ * GNUNET_TETSBED_RSH_CMD_SUFFIX. If the environmental variable is not found,
+ * only the given binary name will be present in the returned arguments
+ *
+ * @param append_args the arguments to append after generating the suffix
+ * arguments. Can be NULL; if not must be NULL terminated 'char *' array
+ * @return NULL-terminated args
+ */
+static char **
+gen_rsh_suffix_args (const char * const *append_args)
+{
+ char **rshell_args;
+ char *rshell_cmd;
+ char *rshell_cmd_cp;
+ char *arg;
+ unsigned int cnt;
+ unsigned int append_cnt;
+
+ rshell_args = NULL;
+ cnt = 0;
+ if (NULL != (rshell_cmd = getenv ("GNUNET_TESTBED_RSH_CMD_SUFFIX")))
+ {
+ rshell_cmd = GNUNET_strdup (rshell_cmd);
+ rshell_cmd_cp = rshell_cmd;
+ for (; NULL != (arg = strtok (rshell_cmd, " ")); rshell_cmd = NULL)
+ GNUNET_array_append (rshell_args, cnt, GNUNET_strdup (arg));
+ GNUNET_free (rshell_cmd_cp);
+ }
+ if (NULL != append_args)
+ {
+ for (append_cnt = 0; NULL != append_args[append_cnt]; append_cnt++)
+ GNUNET_array_append (rshell_args, cnt, GNUNET_strdup (append_args[append_cnt]));
+ }
+ GNUNET_array_append (rshell_args, cnt, NULL);
+ return rshell_args;
+}
+
+
+/**
+ * Functions with this signature are called whenever a
+ * complete message is received by the tokenizer.
+ *
+ * Do not call GNUNET_SERVER_mst_destroy in 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
+ */
+static int
+helper_mst (void *cls, void *client, const struct GNUNET_MessageHeader *message)
+{
+ struct GNUNET_TESTBED_ControllerProc *cp = cls;
+ const struct GNUNET_TESTBED_HelperReply *msg;
+ const char *hostname;
+ char *config;
+ uLongf config_size;
+ uLongf xconfig_size;
+
+ msg = (const struct GNUNET_TESTBED_HelperReply *) message;
+ GNUNET_assert (sizeof (struct GNUNET_TESTBED_HelperReply) <
+ ntohs (msg->header.size));
+ GNUNET_assert (GNUNET_MESSAGE_TYPE_TESTBED_HELPER_REPLY ==
+ ntohs (msg->header.type));
+ config_size = (uLongf) ntohs (msg->config_size);
+ xconfig_size =
+ (uLongf) (ntohs (msg->header.size) -
+ sizeof (struct GNUNET_TESTBED_HelperReply));
+ config = GNUNET_malloc (config_size);
+ GNUNET_assert (Z_OK ==
+ uncompress ((Bytef *) config, &config_size,
+ (const Bytef *) &msg[1], xconfig_size));
+ /* Replace the configuration template present in the host with the
+ controller's running configuration */
+ GNUNET_CONFIGURATION_destroy (cp->host->cfg);
+ cp->host->cfg = GNUNET_CONFIGURATION_create ();
+ GNUNET_assert (GNUNET_CONFIGURATION_deserialize
+ (cp->host->cfg, config, config_size, GNUNET_NO));
+ GNUNET_free (config);
+ if (NULL == (hostname = GNUNET_TESTBED_host_get_hostname (cp->host)))
+ hostname = "localhost";
+ /* Change the hostname so that we can connect to it */
+ GNUNET_CONFIGURATION_set_value_string (cp->host->cfg, "testbed", "hostname",
+ hostname);
+ cp->host->locked = GNUNET_NO;
+ cp->host->controller_started = GNUNET_YES;
+ cp->cb (cp->cls, cp->host->cfg, GNUNET_OK);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Continuation function from GNUNET_HELPER_send()
+ *
+ * @param cls closure
+ * @param result GNUNET_OK on success,
+ * GNUNET_NO if helper process died
+ * GNUNET_SYSERR during GNUNET_HELPER_stop
+ */
+static void
+clear_msg (void *cls, int result)
+{
+ struct GNUNET_TESTBED_ControllerProc *cp = cls;
+
+ GNUNET_assert (NULL != cp->shandle);
+ cp->shandle = NULL;
+ GNUNET_free (cp->msg);
+ cp->msg = NULL;
+}
+
+
+/**
+ * Callback that will be called when the helper process dies. This is not called
+ * when the helper process is stoped using GNUNET_HELPER_stop()
+ *
+ * @param cls the closure from GNUNET_HELPER_start()
+ */
+static void
+helper_exp_cb (void *cls)
+{
+ struct GNUNET_TESTBED_ControllerProc *cp = cls;
+ GNUNET_TESTBED_ControllerStatusCallback cb;
+ void *cb_cls;
+
+ cb = cp->cb;
+ cb_cls = cp->cls;
+ cp->helper = NULL;
+ GNUNET_TESTBED_controller_stop (cp);
+ if (NULL != cb)
+ cb (cb_cls, NULL, GNUNET_SYSERR);
+}
+
+
+/**
+ * Starts a controller process at the given host. The given host's configration
+ * is used as a Template configuration to use for the remote controller; the
+ * remote controller will be started with a slightly modified configuration
+ * (port numbers, unix domain sockets and service home values are changed as per
+ * TESTING library on the remote host). The modified configuration replaces the
+ * host's existing configuration before signalling success through the
+ * GNUNET_TESTBED_ControllerStatusCallback()
+ *
+ * @param trusted_ip the ip address of the controller which will be set as TRUSTED
+ * HOST(all connections form this ip are permitted by the testbed) when
+ * starting testbed controller at host. This can either be a single ip
+ * address or a network address in CIDR notation.
+ * @param host the host where the controller has to be started. CANNOT be NULL.
+ * @param cb function called when the controller is successfully started or
+ * dies unexpectedly; GNUNET_TESTBED_controller_stop shouldn't be
+ * called if cb is called with GNUNET_SYSERR as status. Will never be
+ * called in the same task as 'GNUNET_TESTBED_controller_start'
+ * (synchronous errors will be signalled by returning NULL). This
+ * parameter cannot be NULL.
+ * @param cls closure for above callbacks
+ * @return the controller process handle, NULL on errors
+ */
+struct GNUNET_TESTBED_ControllerProc *
+GNUNET_TESTBED_controller_start (const char *trusted_ip,
+ struct GNUNET_TESTBED_Host *host,
+ GNUNET_TESTBED_ControllerStatusCallback cb,
+ void *cls)
+{
+ struct GNUNET_TESTBED_ControllerProc *cp;
+ struct GNUNET_TESTBED_HelperInit *msg;
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+ const char *hostname;
+ static char *const binary_argv[] = {
+ HELPER_TESTBED_BINARY, NULL
+ };
+
+ GNUNET_assert (NULL != host);
+ GNUNET_assert (NULL != (cfg = GNUNET_TESTBED_host_get_cfg_ (host)));
+ hostname = NULL;
+ API_VIOLATION (GNUNET_NO == host->locked,
+ "Host is already locked by a previous call to GNUNET_TESTBED_controller_start()");
+ host->locked = GNUNET_YES;
+ API_VIOLATION (GNUNET_NO == host->controller_started,
+ "Attempting to start a controller on a host which is already started a controller");
+ cp = GNUNET_new (struct GNUNET_TESTBED_ControllerProc);
+ if (0 == GNUNET_TESTBED_host_get_id_ (host))
+ {
+ cp->helper =
+ GNUNET_HELPER_start (GNUNET_YES, HELPER_TESTBED_BINARY, binary_argv,
+ &helper_mst, &helper_exp_cb, cp);
+ }
+ else
+ {
+ char *helper_binary_path_args[2];
+ char **rsh_args;
+ char **rsh_suffix_args;
+ const char *username;
+ char *port;
+ char *argstr;
+ char *aux;
+ unsigned int cnt;
+
+ username = host->username;
+ hostname = host->hostname;
+ GNUNET_asprintf (&port, "%u", host->port);
+ LOG_DEBUG ("Starting remote connection to destination %s\n", hostname);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_filename (cfg, "testbed",
+ "HELPER_BINARY_PATH",
+ &helper_binary_path_args[0]))
+ helper_binary_path_args[0] =
+ GNUNET_OS_get_libexec_binary_path (HELPER_TESTBED_BINARY);
+ helper_binary_path_args[1] = NULL;
+ rsh_args = gen_rsh_args (port, hostname, username);
+ rsh_suffix_args = gen_rsh_suffix_args ((const char **) helper_binary_path_args);
+ cp->helper_argv =
+ join_argv ((const char **) rsh_args, (const char **) rsh_suffix_args);
+ free_argv (rsh_args);
+ free_argv (rsh_suffix_args);
+ GNUNET_free (port);
+ argstr = GNUNET_strdup ("");
+ for (cnt = 0; NULL != cp->helper_argv[cnt]; cnt++)
+ {
+ aux = argstr;
+ GNUNET_assert (0 < GNUNET_asprintf (&argstr, "%s %s", aux, cp->helper_argv[cnt]));
+ GNUNET_free (aux);
+ }
+ LOG_DEBUG ("Helper cmd str: %s\n", argstr);
+ GNUNET_free (argstr);
+ cp->helper =
+ GNUNET_HELPER_start (GNUNET_NO, cp->helper_argv[0], cp->helper_argv, &helper_mst,
+ &helper_exp_cb, cp);
+ GNUNET_free (helper_binary_path_args[0]);
+ }
+ if (NULL == cp->helper)
+ {
+ if (NULL != cp->helper_argv)
+ free_argv (cp->helper_argv);
+ GNUNET_free (cp);
+ return NULL;
+ }
+ cp->host = host;
+ cp->cb = cb;
+ cp->cls = cls;
+ msg = GNUNET_TESTBED_create_helper_init_msg_ (trusted_ip, hostname, cfg);
+ cp->msg = &msg->header;
+ cp->shandle =
+ GNUNET_HELPER_send (cp->helper, &msg->header, GNUNET_NO, &clear_msg, cp);
+ if (NULL == cp->shandle)
+ {
+ GNUNET_free (msg);
+ GNUNET_TESTBED_controller_stop (cp);
+ return NULL;
+ }
+ return cp;
+}
+
+
+/**
+ * Sends termination signal to the controller's helper process
+ *
+ * @param cproc the handle to the controller's helper process
+ */
+void
+GNUNET_TESTBED_controller_kill_ (struct GNUNET_TESTBED_ControllerProc *cproc)
+{
+ if (NULL != cproc->shandle)
+ GNUNET_HELPER_send_cancel (cproc->shandle);
+ if (NULL != cproc->helper)
+ GNUNET_HELPER_kill (cproc->helper, GNUNET_YES);
+}
+
+
+/**
+ * Cleans-up the controller's helper process handle
+ *
+ * @param cproc the handle to the controller's helper process
+ */
+void
+GNUNET_TESTBED_controller_destroy_ (struct GNUNET_TESTBED_ControllerProc *cproc)
+{
+ if (NULL != cproc->helper)
+ {
+ GNUNET_break (GNUNET_OK == GNUNET_HELPER_wait (cproc->helper));
+ GNUNET_HELPER_destroy (cproc->helper);
+ }
+ if (NULL != cproc->helper_argv)
+ free_argv (cproc->helper_argv);
+ cproc->host->controller_started = GNUNET_NO;
+ cproc->host->locked = GNUNET_NO;
+ GNUNET_free_non_null (cproc->msg);
+ GNUNET_free (cproc);
+}
+
+
+/**
+ * Stop the controller process (also will terminate all peers and controllers
+ * dependent on this controller). This function blocks until the testbed has
+ * been fully terminated (!). The controller status cb from
+ * GNUNET_TESTBED_controller_start() will not be called.
+ *
+ * @param cproc the controller process handle
+ */
+void
+GNUNET_TESTBED_controller_stop (struct GNUNET_TESTBED_ControllerProc *cproc)
+{
+ GNUNET_TESTBED_controller_kill_ (cproc);
+ GNUNET_TESTBED_controller_destroy_ (cproc);
+}
+
+