aboutsummaryrefslogtreecommitdiff
path: root/src/lib/testing/gnunet-cmds-helper.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/testing/gnunet-cmds-helper.c')
-rw-r--r--src/lib/testing/gnunet-cmds-helper.c549
1 files changed, 549 insertions, 0 deletions
diff --git a/src/lib/testing/gnunet-cmds-helper.c b/src/lib/testing/gnunet-cmds-helper.c
new file mode 100644
index 000000000..93d4d96de
--- /dev/null
+++ b/src/lib/testing/gnunet-cmds-helper.c
@@ -0,0 +1,549 @@
1/*
2 This file is part of GNUnet
3 Copyright (C) 2021 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-cmds-helper.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#include "platform.h"
38#include "gnunet_util_lib.h"
39#include "gnunet_testing_lib.h"
40#include "testing_api_loop.h"
41#include "testing_cmds.h"
42#include "testing_api_topology.h"
43
44/**
45 * Generic logging shortcut
46 */
47#define LOG(kind, ...) GNUNET_log (kind, __VA_ARGS__)
48
49/**
50 * Debug logging shorthand
51 */
52#define LOG_DEBUG(...) LOG (GNUNET_ERROR_TYPE_DEBUG, __VA_ARGS__)
53
54/**
55 * Context for a single write on a chunk of memory
56 */
57struct WriteContext
58{
59
60 struct WriteContext *next;
61
62 struct WriteContext *prev;
63
64 /**
65 * The data to write
66 */
67 void *data;
68
69 /**
70 * The length of the data
71 */
72 size_t length;
73
74 /**
75 * The current position from where the write operation should begin
76 */
77 size_t pos;
78};
79
80
81static struct WriteContext *wc_head;
82
83static struct WriteContext *wc_tail;
84
85static struct GNUNET_TESTING_Interpreter *is;
86
87static const char *my_node_id;
88
89/**
90 * Plugin to dynamically load a test case.
91 */
92static struct GNUNET_TESTING_PluginFunctions *plugin;
93
94/**
95 * Name of our plugin.
96 */
97static char *plugin_name;
98
99/**
100 * The loaded topology.
101 */
102 struct GNUNET_TESTING_NetjailTopology *njt;
103
104/**
105 * Our message stream tokenizer
106 */
107static struct GNUNET_MessageStreamTokenizer *tokenizer;
108
109/**
110 * Disk handle from stdin
111 */
112static struct GNUNET_DISK_FileHandle *stdin_fd;
113
114/**
115 * Disk handle for stdout
116 */
117static struct GNUNET_DISK_FileHandle *stdout_fd;
118
119/**
120 * Task identifier for the read task
121 */
122static struct GNUNET_SCHEDULER_Task *read_task_id;
123
124/**
125 * Task identifier for the write task
126 */
127static struct GNUNET_SCHEDULER_Task *write_task_id;
128
129/**
130 * Result to return in case we fail
131 */
132static int global_ret;
133
134/**
135 * Set to true once we are finished and should exit
136 * after sending our final message to the parent.
137 */
138static bool finished;
139
140
141/**
142 * Task to shut down cleanly
143 *
144 * @param cls NULL
145 */
146static void
147do_shutdown (void *cls)
148{
149 struct WriteContext *wc;
150
151 if (NULL != read_task_id)
152 {
153 GNUNET_SCHEDULER_cancel (read_task_id);
154 read_task_id = NULL;
155 }
156 if (NULL != write_task_id)
157 {
158 GNUNET_SCHEDULER_cancel (write_task_id);
159 write_task_id = NULL;
160 }
161 while (NULL != (wc = wc_head))
162 {
163 GNUNET_CONTAINER_DLL_remove (wc_head,
164 wc_tail,
165 wc);
166 GNUNET_free (wc->data);
167 GNUNET_free (wc);
168 }
169 if (NULL != tokenizer)
170 {
171 GNUNET_MST_destroy (tokenizer);
172 tokenizer = NULL;
173 }
174 if (NULL != plugin)
175 {
176 GNUNET_PLUGIN_unload (plugin_name,
177 plugin);
178 }
179 if (NULL != njt)
180 {
181 GNUNET_TESTING_free_topology (njt);
182 njt = NULL;
183 }
184}
185
186
187/**
188 * Task to write to the standard out
189 *
190 * @param cls the WriteContext
191 */
192static void
193write_task (void *cls)
194{
195 struct WriteContext *wc = wc_head;
196 ssize_t bytes_wrote;
197
198 write_task_id = NULL;
199 if (NULL == wc)
200 {
201 if (finished)
202 GNUNET_SCHEDULER_shutdown ();
203 return;
204 }
205 bytes_wrote
206 = GNUNET_DISK_file_write (stdout_fd,
207 wc->data + wc->pos,
208 wc->length - wc->pos);
209 if (GNUNET_SYSERR == bytes_wrote)
210 {
211 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
212 "write");
213 GNUNET_free (wc->data);
214 GNUNET_free (wc);
215 global_ret = EXIT_FAILURE;
216 GNUNET_SCHEDULER_shutdown ();
217 return;
218 }
219 wc->pos += bytes_wrote;
220 if (wc->pos == wc->length)
221 {
222 GNUNET_CONTAINER_DLL_remove (wc_head,
223 wc_tail,
224 wc);
225 GNUNET_free (wc->data);
226 GNUNET_free (wc);
227 }
228 write_task_id
229 = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
230 stdout_fd,
231 &write_task,
232 NULL);
233}
234
235
236/**
237 * Callback to write a message to the parent process.
238 *
239 */
240static void
241write_message (const struct GNUNET_MessageHeader *message)
242{
243 struct WriteContext *wc;
244 size_t msg_length = ntohs (message->size);
245
246 wc = GNUNET_new (struct WriteContext);
247 wc->length = msg_length;
248 wc->data = GNUNET_memdup (message,
249 msg_length);
250 GNUNET_CONTAINER_DLL_insert_tail (wc_head,
251 wc_tail,
252 wc);
253 if (NULL == write_task_id)
254 {
255 GNUNET_assert (wc_head == wc);
256 write_task_id
257 = GNUNET_SCHEDULER_add_write_file (
258 GNUNET_TIME_UNIT_FOREVER_REL,
259 stdout_fd,
260 &write_task,
261 NULL);
262 }
263}
264
265
266static void
267finished_cb (void *cls,
268 enum GNUNET_GenericReturnValue rv)
269{
270 struct GNUNET_TESTING_CommandLocalFinished reply = {
271 .header.type = htons (GNUNET_MESSAGE_TYPE_CMDS_HELPER_LOCAL_FINISHED),
272 .header.size = htons (sizeof (reply)),
273 .rv = htonl ((uint32_t) rv)
274 };
275
276 (void) cls;
277 finished = true;
278 write_message (&reply.header);
279}
280
281
282static enum GNUNET_GenericReturnValue
283check_helper_init (
284 void *cls,
285 const struct GNUNET_TESTING_CommandHelperInit *msg)
286{
287 uint16_t msize = htons (msg->header.size);
288 uint32_t barrier_count = htonl (msg->barrier_count);
289 size_t bs = barrier_count * sizeof (struct GNUNET_ShortHashCode);
290 size_t left = msize - bs - sizeof (*msg);
291 const struct GNUNET_ShortHashCode *bd
292 = (const struct GNUNET_ShortHashCode *) &msg[1];
293 const char *topo = (const char *) &bd[barrier_count];
294
295 if (msize < bs + sizeof (*msg))
296 {
297 GNUNET_break_op (0);
298 return GNUNET_SYSERR;
299 }
300 if ('\0' != topo[left - 1])
301 {
302 GNUNET_break_op (0);
303 return GNUNET_SYSERR;
304 }
305 return GNUNET_OK;
306}
307
308
309static void
310handle_helper_init (
311 void *cls,
312 const struct GNUNET_TESTING_CommandHelperInit *msg)
313{
314 uint16_t msize = htons (msg->header.size);
315 uint32_t barrier_count = htonl (msg->barrier_count);
316 size_t bs = barrier_count * sizeof (struct GNUNET_ShortHashCode);
317 size_t left = msize - bs - sizeof (*msg);
318 const struct GNUNET_ShortHashCode *bd
319 = (const struct GNUNET_ShortHashCode *) &msg[1];
320 const char *topo = (const char *) &bd[barrier_count];
321
322
323 GNUNET_assert ('\0' == topo[left - 1]);
324 njt = GNUNET_TESTING_get_topo_from_string_ (topo);
325 if (NULL == njt)
326 {
327 GNUNET_break_op (0);
328 global_ret = EXIT_FAILURE;
329 GNUNET_SCHEDULER_shutdown ();
330 return;
331 }
332 plugin_name = GNUNET_TESTING_get_plugin_from_topo (njt,
333 my_node_id);
334 plugin = GNUNET_PLUGIN_load (plugin_name,
335 (void *) my_node_id);
336 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
337 "Starting plugin `%s' for node %s\n",
338 plugin_name,
339 my_node_id);
340 if (NULL == plugin)
341 {
342 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
343 "Plugin `%s' not found!\n",
344 plugin_name);
345 global_ret = EXIT_FAILURE;
346 GNUNET_SCHEDULER_shutdown ();
347 return;
348 }
349 struct GNUNET_TESTING_Command *commands = plugin->cls;
350 unsigned int i;
351
352 for (i = 0; NULL != commands[i].run; i++)
353 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
354 "helper %s\n",
355 commands[i].label.value);
356 is = plugin->start_testcase (plugin->cls,
357 topo,
358 barrier_count,
359 bd,
360 &write_message,
361 &finished_cb,
362 NULL);
363}
364
365
366static void
367handle_helper_barrier_crossable (
368 void *cls,
369 const struct GNUNET_TESTING_CommandBarrierSatisfied *cbs)
370{
371 struct GNUNET_TESTING_Barrier *barrier;
372
373 if (NULL == is)
374 {
375 /* Barrier satisfied *before* helper_init?! */
376 GNUNET_break_op (0);
377 global_ret = EXIT_FAILURE;
378 GNUNET_SCHEDULER_shutdown ();
379 return;
380 }
381 barrier = GNUNET_TESTING_get_barrier2_ (is,
382 &cbs->barrier_key);
383 if (barrier->satisfied)
384 {
385 /* Barrier satisfied *twice* is strange... */
386 GNUNET_break_op (0);
387 global_ret = EXIT_FAILURE;
388 GNUNET_SCHEDULER_shutdown ();
389 return;
390 }
391 barrier->satisfied = true;
392 GNUNET_TESTING_loop_notify_children_ (is,
393 &cbs->header);
394 for (unsigned int i = 0; i<barrier->cnt_waiting; i++)
395 GNUNET_TESTING_async_finish (barrier->waiting[i]);
396 GNUNET_array_grow (barrier->waiting,
397 barrier->cnt_waiting,
398 0);
399}
400
401
402/**
403 * Functions with this signature are called whenever a
404 * complete message is received by the tokenizer.
405 *
406 * Do not call #GNUNET_mst_destroy() in this callback
407 *
408 * @param cls identification of the client
409 * @param message the actual message
410 * @return #GNUNET_OK on success,
411 * #GNUNET_NO to stop further processing (no error)
412 * #GNUNET_SYSERR to stop further processing with error
413 */
414static enum GNUNET_GenericReturnValue
415tokenizer_cb (void *cls,
416 const struct GNUNET_MessageHeader *message)
417{
418 struct GNUNET_MQ_MessageHandler handlers[] = {
419 GNUNET_MQ_hd_var_size (
420 helper_init,
421 GNUNET_MESSAGE_TYPE_CMDS_HELPER_INIT,
422 struct GNUNET_TESTING_CommandHelperInit,
423 NULL),
424 GNUNET_MQ_hd_fixed_size (
425 helper_barrier_crossable,
426 GNUNET_MESSAGE_TYPE_CMDS_HELPER_BARRIER_CROSSABLE,
427 struct GNUNET_TESTING_CommandBarrierSatisfied,
428 NULL),
429 GNUNET_MQ_handler_end ()
430 };
431
432 return GNUNET_MQ_handle_message (handlers,
433 message);
434}
435
436
437/**
438 * Task to read from stdin
439 *
440 * @param cls NULL
441 */
442static void
443read_task (void *cls)
444{
445 char buf[GNUNET_MAX_MESSAGE_SIZE];
446 ssize_t sread;
447
448 read_task_id = NULL;
449 sread = GNUNET_DISK_file_read (stdin_fd,
450 buf,
451 sizeof(buf));
452 if (GNUNET_SYSERR == sread)
453 {
454 GNUNET_break (0);
455 global_ret = EXIT_FAILURE;
456 GNUNET_SCHEDULER_shutdown ();
457 return;
458 }
459 if (0 == sread)
460 {
461 LOG_DEBUG ("STDIN eof\n");
462 GNUNET_SCHEDULER_shutdown ();
463 return;
464 }
465 if (GNUNET_OK !=
466 GNUNET_MST_from_buffer (tokenizer,
467 buf,
468 sread,
469 GNUNET_NO,
470 GNUNET_NO))
471 {
472 GNUNET_break (0);
473 global_ret = EXIT_FAILURE;
474 GNUNET_SCHEDULER_shutdown ();
475 return;
476 }
477 read_task_id
478 = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
479 stdin_fd,
480 &read_task,
481 NULL);
482}
483
484
485/**
486 * Main function that will be run.
487 *
488 * @param cls closure
489 * @param args remaining command-line arguments
490 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
491 * @param cfg configuration
492 */
493static void
494run (void *cls,
495 char *const *args,
496 const char *cfgfile,
497 const struct GNUNET_CONFIGURATION_Handle *cfg)
498{
499 if (NULL == args[0])
500 {
501 /* must be called with our node ID as 1st argument */
502 GNUNET_break_op (0);
503 global_ret = EXIT_INVALIDARGUMENT;
504 return;
505 }
506 my_node_id = args[0];
507 tokenizer = GNUNET_MST_create (&tokenizer_cb,
508 NULL);
509 stdin_fd = GNUNET_DISK_get_handle_from_native (stdin);
510 stdout_fd = GNUNET_DISK_get_handle_from_native (stdout);
511 read_task_id = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
512 stdin_fd,
513 &read_task,
514 NULL);
515 GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
516 NULL);
517}
518
519
520/**
521 * Main function
522 *
523 * @param argc the number of command line arguments
524 * @param argv command line arg array
525 * @return return code
526 */
527int
528main (int argc,
529 char **argv)
530{
531 struct GNUNET_GETOPT_CommandLineOption options[] = {
532 GNUNET_GETOPT_OPTION_END
533 };
534 enum GNUNET_GenericReturnValue ret;
535
536 ret = GNUNET_PROGRAM_run (argc,
537 argv,
538 "gnunet-cmds-helper",
539 "Helper for starting a local interpreter loop",
540 options,
541 &run,
542 NULL);
543 if (GNUNET_OK != ret)
544 return 1;
545 return global_ret;
546}
547
548
549/* end of gnunet-cmds-helper.c */