aboutsummaryrefslogtreecommitdiff
path: root/src/testbed/gnunet-helper-cmds.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/testbed/gnunet-helper-cmds.c')
-rw-r--r--src/testbed/gnunet-helper-cmds.c581
1 files changed, 581 insertions, 0 deletions
diff --git a/src/testbed/gnunet-helper-cmds.c b/src/testbed/gnunet-helper-cmds.c
new file mode 100644
index 000000000..3073ebdfb
--- /dev/null
+++ b/src/testbed/gnunet-helper-cmds.c
@@ -0,0 +1,581 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2008--2013, 2016 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file testbed/gnunet-helper-cmds.c
23 * @brief Helper binary that is started from a remote interpreter loop to start
24 * a local interpreter loop.
25 *
26 * This helper monitors for three termination events. They are: (1)The
27 * stdin of the helper is closed for reading; (2)the helper received
28 * SIGTERM/SIGINT; (3)the local loop crashed. In case of events 1 and 2
29 * the helper kills the interpreter loop. When the interpreter loop
30 * crashed (event 3), the helper should send a SIGTERM to its own process
31 * group; this behaviour will help terminate any child processes the loop
32 * has started and prevents them from leaking and running forever.
33 *
34 * @author t3sserakt
35 * @author Sree Harsha Totakura <sreeharsha@totakura.in>
36 */
37
38
39#include "platform.h"
40#include "gnunet_util_lib.h"
41#include "gnunet_testing_lib.h"
42#include "gnunet_testbed_service.h"
43#include "testbed_helper.h"
44#include "testbed_api.h"
45#include <zlib.h>
46
47/**
48 * Generic logging shortcut
49 */
50#define LOG(kind, ...) GNUNET_log (kind, __VA_ARGS__)
51
52/**
53 * Debug logging shorthand
54 */
55#define LOG_DEBUG(...) LOG (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__)
56
57
58/**
59 * Context for a single write on a chunk of memory
60 */
61struct WriteContext
62{
63 /**
64 * The data to write
65 */
66 void *data;
67
68 /**
69 * The length of the data
70 */
71 size_t length;
72
73 /**
74 * The current position from where the write operation should begin
75 */
76 size_t pos;
77};
78
79/**
80 * The process handle to the testbed service
81 */
82static struct GNUNET_OS_Process *cmd_binary_process;
83
84/**
85 * Handle to the testing system
86 */
87static struct GNUNET_TESTING_System *test_system;
88
89/**
90 * Our message stream tokenizer
91 */
92struct GNUNET_MessageStreamTokenizer *tokenizer;
93
94/**
95 * Disk handle from stdin
96 */
97static struct GNUNET_DISK_FileHandle *stdin_fd;
98
99/**
100 * Disk handle for stdout
101 */
102static struct GNUNET_DISK_FileHandle *stdout_fd;
103
104/**
105 * Pipe used to communicate shutdown via signal.
106 */
107static struct GNUNET_DISK_PipeHandle *sigpipe;
108
109/**
110 * Task identifier for the read task
111 */
112static struct GNUNET_SCHEDULER_Task *read_task_id;
113
114/**
115 * Task identifier for the write task
116 */
117static struct GNUNET_SCHEDULER_Task *write_task_id;
118
119/**
120 * Task to kill the child
121 */
122static struct GNUNET_SCHEDULER_Task *child_death_task_id;
123
124/**
125 * Are we done reading messages from stdin?
126 */
127static int done_reading;
128
129/**
130 * Result to return in case we fail
131 */
132static int status;
133
134
135/**
136 * Task to shut down cleanly
137 *
138 * @param cls NULL
139 */
140static void
141shutdown_task (void *cls)
142{
143 LOG_DEBUG ("Shutting down\n");
144
145 if (NULL != read_task_id)
146 {
147 GNUNET_SCHEDULER_cancel (read_task_id);
148 read_task_id = NULL;
149 }
150 if (NULL != write_task_id)
151 {
152 struct WriteContext *wc;
153
154 wc = GNUNET_SCHEDULER_cancel (write_task_id);
155 write_task_id = NULL;
156 GNUNET_free (wc->data);
157 GNUNET_free (wc);
158 }
159 if (NULL != child_death_task_id)
160 {
161 GNUNET_SCHEDULER_cancel (child_death_task_id);
162 child_death_task_id = NULL;
163 }
164 if (NULL != stdin_fd)
165 (void) GNUNET_DISK_file_close (stdin_fd);
166 if (NULL != stdout_fd)
167 (void) GNUNET_DISK_file_close (stdout_fd);
168 GNUNET_MST_destroy (tokenizer);
169 tokenizer = NULL;
170
171 if (NULL != test_system)
172 {
173 GNUNET_TESTING_system_destroy (test_system, GNUNET_YES);
174 test_system = NULL;
175 }
176}
177
178
179/**
180 * Task to write to the standard out
181 *
182 * @param cls the WriteContext
183 */
184static void
185write_task (void *cls)
186{
187 struct WriteContext *wc = cls;
188 ssize_t bytes_wrote;
189
190 GNUNET_assert (NULL != wc);
191 write_task_id = NULL;
192 bytes_wrote = GNUNET_DISK_file_write (stdout_fd,
193 wc->data + wc->pos,
194 wc->length - wc->pos);
195 if (GNUNET_SYSERR == bytes_wrote)
196 {
197 LOG (GNUNET_ERROR_TYPE_WARNING, "Cannot reply back configuration\n");
198 GNUNET_free (wc->data);
199 GNUNET_free (wc);
200 return;
201 }
202 wc->pos += bytes_wrote;
203 if (wc->pos == wc->length)
204 {
205 GNUNET_free (wc->data);
206 GNUNET_free (wc);
207 return;
208 }
209 write_task_id = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
210 stdout_fd,
211 &write_task,
212 wc);
213}
214
215
216/**
217 * Task triggered whenever we receive a SIGCHLD (child
218 * process died).
219 *
220 * @param cls closure, NULL if we need to self-restart
221 */
222static void
223child_death_task (void *cls)
224{
225 const struct GNUNET_DISK_FileHandle *pr;
226 char c[16];
227
228 pr = GNUNET_DISK_pipe_handle (sigpipe, GNUNET_DISK_PIPE_END_READ);
229 child_death_task_id = NULL;
230 /* consume the signal */
231 GNUNET_break (0 < GNUNET_DISK_file_read (pr, &c, sizeof(c)));
232 LOG_DEBUG ("Got SIGCHLD\n");
233
234 LOG_DEBUG ("Child hasn't died. Resuming to monitor its status\n");
235 child_death_task_id =
236 GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
237 pr,
238 &child_death_task,
239 NULL);
240}
241
242
243/**
244 * Functions with this signature are called whenever a
245 * complete message is received by the tokenizer.
246 *
247 * Do not call #GNUNET_mst_destroy() in this callback
248 *
249 * @param cls identification of the client
250 * @param message the actual message
251 * @return #GNUNET_OK on success,
252 * #GNUNET_NO to stop further processing (no error)
253 * #GNUNET_SYSERR to stop further processing with error
254 */
255static int
256tokenizer_cb (void *cls, const struct GNUNET_MessageHeader *message)
257{
258 const struct GNUNET_TESTBED_HelperInit *msg;
259 struct GNUNET_TESTBED_HelperReply *reply;
260 struct GNUNET_CONFIGURATION_Handle *cfg;
261 struct WriteContext *wc;
262 char *binary;
263 char *trusted_ip;
264 char *hostname;
265 char *config;
266 char *xconfig;
267 char *evstr;
268 // char *str;
269 size_t config_size;
270 uLongf ul_config_size;
271 size_t xconfig_size;
272 uint16_t trusted_ip_size;
273 uint16_t hostname_size;
274 uint16_t msize;
275
276 msize = ntohs (message->size);
277 if ((sizeof(struct GNUNET_TESTBED_HelperInit) >= msize) ||
278 (GNUNET_MESSAGE_TYPE_TESTBED_HELPER_INIT != ntohs (message->type)))
279 {
280 LOG (GNUNET_ERROR_TYPE_WARNING, "Received unexpected message -- exiting\n");
281 goto error;
282 }
283 msg = (const struct GNUNET_TESTBED_HelperInit *) message;
284 trusted_ip_size = ntohs (msg->trusted_ip_size);
285 trusted_ip = (char *) &msg[1];
286 if ('\0' != trusted_ip[trusted_ip_size])
287 {
288 LOG (GNUNET_ERROR_TYPE_WARNING, "Trusted IP cannot be empty -- exiting\n");
289 goto error;
290 }
291 hostname_size = ntohs (msg->hostname_size);
292 if ((sizeof(struct GNUNET_TESTBED_HelperInit) + trusted_ip_size + 1
293 + hostname_size) >= msize)
294 {
295 GNUNET_break (0);
296 LOG (GNUNET_ERROR_TYPE_WARNING, "Received unexpected message -- exiting\n");
297 goto error;
298 }
299 ul_config_size = (uLongf) ntohs (msg->config_size);
300 config = GNUNET_malloc (ul_config_size);
301 xconfig_size = msize - (trusted_ip_size + 1 + hostname_size
302 + sizeof(struct GNUNET_TESTBED_HelperInit));
303 int ret = uncompress ((Bytef *) config,
304 &ul_config_size,
305 (const Bytef *) (trusted_ip + trusted_ip_size + 1
306 + hostname_size),
307 (uLongf) xconfig_size);
308 if (Z_OK != ret)
309 {
310 switch (ret)
311 {
312 case Z_MEM_ERROR:
313 LOG (GNUNET_ERROR_TYPE_ERROR, "Not enough memory for decompression\n");
314 break;
315
316 case Z_BUF_ERROR:
317 LOG (GNUNET_ERROR_TYPE_ERROR, "Output buffer too small\n");
318 break;
319
320 case Z_DATA_ERROR:
321 LOG (GNUNET_ERROR_TYPE_ERROR, "Data corrupted/incomplete\n");
322 break;
323
324 default:
325 GNUNET_break (0);
326 }
327 LOG (GNUNET_ERROR_TYPE_ERROR,
328 "Error while uncompressing config -- exiting\n");
329 GNUNET_free (config);
330 goto error;
331 }
332 cfg = GNUNET_CONFIGURATION_create ();
333 if (GNUNET_OK !=
334 GNUNET_CONFIGURATION_deserialize (cfg, config, ul_config_size, NULL))
335 {
336 LOG (GNUNET_ERROR_TYPE_ERROR, "Unable to deserialize config -- exiting\n");
337 GNUNET_free (config);
338 goto error;
339 }
340 GNUNET_free (config);
341 hostname = NULL;
342 if (0 != hostname_size)
343 {
344 hostname = GNUNET_malloc (hostname_size + 1);
345 GNUNET_strlcpy (hostname,
346 ((char *) &msg[1]) + trusted_ip_size + 1,
347 hostname_size + 1);
348 }
349 /* unset GNUNET_TESTING_PREFIX if present as it is more relevant for testbed */
350 evstr = getenv (GNUNET_TESTING_PREFIX);
351 if (NULL != evstr)
352 {
353 /* unsetting the variable will invalidate the pointer! */
354 evstr = GNUNET_strdup (evstr);
355 GNUNET_break (0 == unsetenv (GNUNET_TESTING_PREFIX));
356 }
357 test_system =
358 GNUNET_TESTING_system_create ("testbed-helper", trusted_ip, hostname, NULL);
359 if (NULL != evstr)
360 {
361 char *evar;
362
363 GNUNET_asprintf (&evar, GNUNET_TESTING_PREFIX "=%s", evstr);
364 GNUNET_assert (0 == putenv (evar)); /* consumes 'evar',
365 see putenv(): becomes part of environment! */
366 GNUNET_free (evstr);
367 evstr = NULL;
368 }
369 GNUNET_free (hostname);
370 hostname = NULL;
371 GNUNET_assert (NULL != test_system);
372 GNUNET_assert (GNUNET_OK ==
373 GNUNET_TESTING_configuration_create (test_system, cfg));
374 GNUNET_assert (GNUNET_OK ==
375 GNUNET_CONFIGURATION_get_value_filename (cfg,
376 "PATHS",
377 "DEFAULTCONFIG",
378 &config));
379 if (GNUNET_OK != GNUNET_CONFIGURATION_write (cfg, config))
380 {
381 LOG (GNUNET_ERROR_TYPE_WARNING,
382 "Unable to write config file: %s -- exiting\n",
383 config);
384 GNUNET_CONFIGURATION_destroy (cfg);
385 GNUNET_free (config);
386 goto error;
387 }
388 LOG_DEBUG ("Staring testbed with config: %s\n", config);
389 binary = GNUNET_OS_get_libexec_binary_path ("gnunet-cmd");
390 {
391 char *evar;
392
393 /* expose testbed configuration through env variable */
394 GNUNET_asprintf (&evar, "%s=%s", ENV_TESTBED_CONFIG, config);
395 GNUNET_assert (0 == putenv (evar)); /* consumes 'evar',
396 see putenv(): becomes part of environment! */
397 evstr = NULL;
398 }
399
400 cmd_binary_process = GNUNET_OS_start_process (
401 GNUNET_OS_INHERIT_STD_ERR /*verbose? */,
402 NULL,
403 NULL,
404 NULL,
405 binary);
406
407 if (NULL == cmd_binary_process)
408 {
409 return GNUNET_SYSERR;
410 }
411
412 GNUNET_free (binary);
413 GNUNET_free (config);
414
415 done_reading = GNUNET_YES;
416 config = GNUNET_CONFIGURATION_serialize (cfg, &config_size);
417 GNUNET_CONFIGURATION_destroy (cfg);
418 cfg = NULL;
419 xconfig_size =
420 GNUNET_TESTBED_compress_config_ (config, config_size, &xconfig);
421 GNUNET_free (config);
422 wc = GNUNET_new (struct WriteContext);
423 wc->length = xconfig_size + sizeof(struct GNUNET_TESTBED_HelperReply);
424 reply = GNUNET_realloc (xconfig, wc->length);
425 memmove (&reply[1], reply, xconfig_size);
426 reply->header.type = htons (GNUNET_MESSAGE_TYPE_TESTBED_HELPER_REPLY);
427 reply->header.size = htons ((uint16_t) wc->length);
428 reply->config_size = htons ((uint16_t) config_size);
429 wc->data = reply;
430 write_task_id = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
431 stdout_fd,
432 &write_task,
433 wc);
434 child_death_task_id = GNUNET_SCHEDULER_add_read_file (
435 GNUNET_TIME_UNIT_FOREVER_REL,
436 GNUNET_DISK_pipe_handle (sigpipe, GNUNET_DISK_PIPE_END_READ),
437 &child_death_task,
438 NULL);
439 return GNUNET_OK;
440
441 error:
442 status = GNUNET_SYSERR;
443 GNUNET_SCHEDULER_shutdown ();
444 return GNUNET_SYSERR;
445}
446
447
448/**
449 * Task to read from stdin
450 *
451 * @param cls NULL
452 */
453static void
454read_task (void *cls)
455{
456 char buf[GNUNET_MAX_MESSAGE_SIZE];
457 ssize_t sread;
458
459 read_task_id = NULL;
460 sread = GNUNET_DISK_file_read (stdin_fd, buf, sizeof(buf));
461 if ((GNUNET_SYSERR == sread) || (0 == sread))
462 {
463 LOG_DEBUG ("STDIN closed\n");
464 GNUNET_SCHEDULER_shutdown ();
465 return;
466 }
467 if (GNUNET_YES == done_reading)
468 {
469 /* didn't expect any more data! */
470 GNUNET_break_op (0);
471 GNUNET_SCHEDULER_shutdown ();
472 return;
473 }
474 LOG_DEBUG ("Read %u bytes\n", (unsigned int) sread);
475 /* FIXME: could introduce a GNUNET_MST_read2 to read
476 directly from 'stdin_fd' and save a memcpy() here */
477 if (GNUNET_OK !=
478 GNUNET_MST_from_buffer (tokenizer, buf, sread, GNUNET_NO, GNUNET_NO))
479 {
480 GNUNET_break (0);
481 GNUNET_SCHEDULER_shutdown ();
482 return;
483 }
484 read_task_id /* No timeout while reading */
485 = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
486 stdin_fd,
487 &read_task,
488 NULL);
489}
490
491
492/**
493 * Main function that will be run.
494 *
495 * @param cls closure
496 * @param args remaining command-line arguments
497 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
498 * @param cfg configuration
499 */
500static void
501run (void *cls,
502 char *const *args,
503 const char *cfgfile,
504 const struct GNUNET_CONFIGURATION_Handle *cfg)
505{
506 LOG_DEBUG ("Starting interpreter loop helper...\n");
507 tokenizer = GNUNET_MST_create (&tokenizer_cb, NULL);
508 stdin_fd = GNUNET_DISK_get_handle_from_native (stdin);
509 stdout_fd = GNUNET_DISK_get_handle_from_native (stdout);
510 read_task_id = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
511 stdin_fd,
512 &read_task,
513 NULL);
514 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL);
515}
516
517
518/**
519 * Signal handler called for SIGCHLD.
520 */
521static void
522sighandler_child_death ()
523{
524 static char c;
525 int old_errno; /* back-up errno */
526
527 old_errno = errno;
528 GNUNET_break (
529 1 ==
530 GNUNET_DISK_file_write (GNUNET_DISK_pipe_handle (sigpipe,
531 GNUNET_DISK_PIPE_END_WRITE),
532 &c,
533 sizeof(c)));
534 errno = old_errno;
535}
536
537
538/**
539 * Main function
540 *
541 * @param argc the number of command line arguments
542 * @param argv command line arg array
543 * @return return code
544 */
545int
546main (int argc, char **argv)
547{
548 struct GNUNET_SIGNAL_Context *shc_chld;
549 struct GNUNET_GETOPT_CommandLineOption options[] =
550 { GNUNET_GETOPT_OPTION_END };
551 int ret;
552
553 GNUNET_log_setup ("gnunet-helper-cmds",
554 "DEBUG",
555 NULL);
556 status = GNUNET_OK;
557 if (NULL ==
558 (sigpipe = GNUNET_DISK_pipe (GNUNET_DISK_PF_NONE)))
559 {
560 GNUNET_break (0);
561 return 1;
562 }
563 shc_chld =
564 GNUNET_SIGNAL_handler_install (GNUNET_SIGCHLD, &sighandler_child_death);
565 ret = GNUNET_PROGRAM_run (argc,
566 argv,
567 "gnunet-helper-cmds",
568 "Helper for starting a local interpreter loop",
569 options,
570 &run,
571 NULL);
572 GNUNET_SIGNAL_handler_uninstall (shc_chld);
573 shc_chld = NULL;
574 GNUNET_DISK_pipe_close (sigpipe);
575 if (GNUNET_OK != ret)
576 return 1;
577 return (GNUNET_OK == status) ? 0 : 1;
578}
579
580
581/* end of gnunet-helper-testbed.c */