Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2023 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it
6 : under the terms of the GNU General Public License as published
7 : by the Free Software Foundation; either version 3, or (at your
8 : option) any later version.
9 :
10 : TALER 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
13 : GNU General Public License for more details.
14 :
15 : You should have received a copy of the GNU General Public
16 : License along with TALER; see the file COPYING. If not,
17 : see <http://www.gnu.org/licenses/>
18 : */
19 : /**
20 : * @file testing/testing_api_cmd_system_start.c
21 : * @brief run taler-benchmark-setup.sh command
22 : * @author Christian Grothoff
23 : */
24 : #include "taler/platform.h"
25 : #include "taler/taler_json_lib.h"
26 : #include <gnunet/gnunet_curl_lib.h>
27 : #include "taler/taler_signatures.h"
28 : #include "taler/taler_testing_lib.h"
29 :
30 :
31 : /**
32 : * State for a "system" CMD.
33 : */
34 : struct SystemState
35 : {
36 :
37 : /**
38 : * System process.
39 : */
40 : struct GNUNET_Process *system_proc;
41 :
42 : /**
43 : * Input pipe to @e system_proc, used to keep the
44 : * process alive until we are done.
45 : */
46 : struct GNUNET_DISK_PipeHandle *pipe_in;
47 :
48 : /**
49 : * Output pipe to @e system_proc, used to find out
50 : * when the services are ready.
51 : */
52 : struct GNUNET_DISK_PipeHandle *pipe_out;
53 :
54 : /**
55 : * Task reading from @e pipe_in.
56 : */
57 : struct GNUNET_SCHEDULER_Task *reader;
58 :
59 : /**
60 : * Waiting for child to die.
61 : */
62 : struct GNUNET_ChildWaitHandle *cwh;
63 :
64 : /**
65 : * Our interpreter state.
66 : */
67 : struct TALER_TESTING_Interpreter *is;
68 :
69 : /**
70 : * NULL-terminated array of command-line arguments.
71 : */
72 : char **args;
73 :
74 : /**
75 : * Current input buffer, 0-terminated. Contains the last 15 bytes of input
76 : * so we can search them again for the "<<READY>>" tag.
77 : */
78 : char ibuf[16];
79 :
80 : /**
81 : * Did we find the ready tag?
82 : */
83 : bool ready;
84 :
85 : /**
86 : * Is the child process still running?
87 : */
88 : bool active;
89 : };
90 :
91 :
92 : /**
93 : * Defines a GNUNET_ChildCompletedCallback which is sent back
94 : * upon death or completion of a child process.
95 : *
96 : * @param cls our `struct SystemState *`
97 : * @param type type of the process
98 : * @param exit_code status code of the process
99 : */
100 : static void
101 0 : setup_terminated (void *cls,
102 : enum GNUNET_OS_ProcessStatusType type,
103 : long unsigned int exit_code)
104 : {
105 0 : struct SystemState *as = cls;
106 :
107 0 : as->cwh = NULL;
108 0 : as->active = false;
109 0 : if (NULL != as->reader)
110 : {
111 0 : GNUNET_SCHEDULER_cancel (as->reader);
112 0 : as->reader = NULL;
113 : }
114 0 : if (! as->ready)
115 : {
116 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
117 : "Launching Taler system failed: %d/%llu\n",
118 : (int) type,
119 : (unsigned long long) exit_code);
120 0 : TALER_TESTING_interpreter_fail (as->is);
121 0 : return;
122 : }
123 : }
124 :
125 :
126 : /**
127 : * Start helper to read from stdout of child.
128 : *
129 : * @param as our system state
130 : */
131 : static void
132 : start_reader (struct SystemState *as);
133 :
134 :
135 : static void
136 434 : read_stdout (void *cls)
137 : {
138 434 : struct SystemState *as = cls;
139 : const struct GNUNET_DISK_FileHandle *fh;
140 : char buf[1024 * 10];
141 : ssize_t ret;
142 434 : size_t off = 0;
143 :
144 434 : as->reader = NULL;
145 434 : strcpy (buf,
146 434 : as->ibuf);
147 434 : off = strlen (buf);
148 434 : fh = GNUNET_DISK_pipe_handle (as->pipe_out,
149 : GNUNET_DISK_PIPE_END_READ);
150 434 : ret = GNUNET_DISK_file_read (fh,
151 434 : &buf[off],
152 : sizeof (buf) - off);
153 434 : if (-1 == ret)
154 : {
155 0 : GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
156 : "read");
157 0 : TALER_TESTING_interpreter_fail (as->is);
158 19 : return;
159 : }
160 434 : if (0 == ret)
161 : {
162 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
163 : "Child closed stdout\n");
164 0 : return;
165 : }
166 : /* forward log, except single '.' outputs */
167 434 : if ( (1 != ret) ||
168 83 : ('.' != buf[off]) )
169 376 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
170 : "TUS: %.*s\n",
171 : (int) ret,
172 : &buf[off]);
173 434 : start_reader (as);
174 434 : off += ret;
175 434 : if (as->ready)
176 : {
177 : /* already done */
178 0 : return;
179 : }
180 434 : if (NULL !=
181 434 : memmem (buf,
182 : off,
183 : "\n<<READY>>\n",
184 : strlen ("\n<<READY>>\n")))
185 : {
186 19 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
187 : "Taler system UP\n");
188 19 : as->ready = true;
189 19 : TALER_TESTING_interpreter_next (as->is);
190 19 : return;
191 : }
192 :
193 : {
194 : size_t mcpy;
195 :
196 415 : mcpy = GNUNET_MIN (off,
197 : sizeof (as->ibuf) - 1);
198 415 : memcpy (as->ibuf,
199 415 : &buf[off - mcpy],
200 : mcpy);
201 415 : as->ibuf[mcpy] = '\0';
202 : }
203 : }
204 :
205 :
206 : static void
207 453 : start_reader (struct SystemState *as)
208 : {
209 : const struct GNUNET_DISK_FileHandle *fh;
210 :
211 453 : GNUNET_assert (NULL == as->reader);
212 453 : fh = GNUNET_DISK_pipe_handle (as->pipe_out,
213 : GNUNET_DISK_PIPE_END_READ);
214 453 : as->reader = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
215 : fh,
216 : &read_stdout,
217 : as);
218 453 : }
219 :
220 :
221 : /**
222 : * Run the command. Use the `taler-unified-setup.sh` program.
223 : *
224 : * @param cls closure.
225 : * @param cmd command being run.
226 : * @param is interpreter state.
227 : */
228 : static void
229 19 : system_run (void *cls,
230 : const struct TALER_TESTING_Command *cmd,
231 : struct TALER_TESTING_Interpreter *is)
232 : {
233 19 : struct SystemState *as = cls;
234 :
235 : (void) cmd;
236 19 : as->is = is;
237 19 : as->pipe_in = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ);
238 19 : GNUNET_assert (NULL != as->pipe_in);
239 19 : as->pipe_out = GNUNET_DISK_pipe (GNUNET_DISK_PF_NONE);
240 19 : GNUNET_assert (NULL != as->pipe_out);
241 19 : as->system_proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
242 19 : GNUNET_assert (GNUNET_OK ==
243 : GNUNET_process_set_options (
244 : as->system_proc,
245 : GNUNET_process_option_inherit_rpipe (as->pipe_in,
246 : STDIN_FILENO),
247 : GNUNET_process_option_inherit_wpipe (as->pipe_out,
248 : STDOUT_FILENO)));
249 19 : if (GNUNET_OK !=
250 19 : GNUNET_process_run_command_argv (as->system_proc,
251 : "taler-unified-setup.sh",
252 19 : (const char **) as->args))
253 : {
254 0 : GNUNET_break (0);
255 0 : GNUNET_process_destroy (as->system_proc);
256 0 : as->system_proc = NULL;
257 0 : TALER_TESTING_interpreter_fail (is);
258 0 : return;
259 : }
260 19 : as->active = true;
261 19 : start_reader (as);
262 19 : as->cwh = GNUNET_wait_child (as->system_proc,
263 : &setup_terminated,
264 : as);
265 : }
266 :
267 :
268 : /**
269 : * Free the state of a "system" CMD, and possibly kill its
270 : * process if it did not terminate correctly.
271 : *
272 : * @param cls closure.
273 : * @param cmd the command being freed.
274 : */
275 : static void
276 19 : system_cleanup (void *cls,
277 : const struct TALER_TESTING_Command *cmd)
278 : {
279 19 : struct SystemState *as = cls;
280 :
281 : (void) cmd;
282 19 : if (NULL != as->cwh)
283 : {
284 19 : GNUNET_wait_child_cancel (as->cwh);
285 19 : as->cwh = NULL;
286 : }
287 19 : if (NULL != as->reader)
288 : {
289 19 : GNUNET_SCHEDULER_cancel (as->reader);
290 19 : as->reader = NULL;
291 : }
292 19 : if (NULL != as->system_proc)
293 : {
294 19 : if (as->active)
295 : {
296 19 : GNUNET_break (GNUNET_OK ==
297 : GNUNET_process_kill (as->system_proc,
298 : SIGTERM));
299 19 : GNUNET_process_wait (as->system_proc,
300 : true,
301 : NULL,
302 : NULL);
303 : }
304 19 : GNUNET_process_destroy (as->system_proc);
305 19 : as->system_proc = NULL;
306 : }
307 19 : if (NULL != as->pipe_in)
308 : {
309 19 : GNUNET_break (GNUNET_OK ==
310 : GNUNET_DISK_pipe_close (as->pipe_in));
311 19 : as->pipe_in = NULL;
312 : }
313 19 : if (NULL != as->pipe_out)
314 : {
315 19 : GNUNET_break (GNUNET_OK ==
316 : GNUNET_DISK_pipe_close (as->pipe_out));
317 19 : as->pipe_out = NULL;
318 : }
319 :
320 105 : for (unsigned int i = 0; NULL != as->args[i]; i++)
321 86 : GNUNET_free (as->args[i]);
322 19 : GNUNET_free (as->args);
323 19 : GNUNET_free (as);
324 19 : }
325 :
326 :
327 : /**
328 : * Offer "system" CMD internal data to other commands.
329 : *
330 : * @param cls closure.
331 : * @param[out] ret result.
332 : * @param trait name of the trait.
333 : * @param index index number of the object to offer.
334 : * @return #GNUNET_OK on success
335 : */
336 : static enum GNUNET_GenericReturnValue
337 20 : system_traits (void *cls,
338 : const void **ret,
339 : const char *trait,
340 : unsigned int index)
341 : {
342 20 : struct SystemState *as = cls;
343 : struct TALER_TESTING_Trait traits[] = {
344 20 : TALER_TESTING_make_trait_process (&as->system_proc),
345 20 : TALER_TESTING_trait_end ()
346 : };
347 :
348 20 : return TALER_TESTING_get_trait (traits,
349 : ret,
350 : trait,
351 : index);
352 : }
353 :
354 :
355 : struct TALER_TESTING_Command
356 19 : TALER_TESTING_cmd_system_start (
357 : const char *label,
358 : const char *config_file,
359 : ...)
360 : {
361 : struct SystemState *as;
362 : va_list ap;
363 : const char *arg;
364 : unsigned int cnt;
365 :
366 19 : as = GNUNET_new (struct SystemState);
367 19 : cnt = 4; /* 0-2 reserved, +1 for NULL termination */
368 19 : va_start (ap,
369 : config_file);
370 48 : while (NULL != (arg = va_arg (ap,
371 : const char *)))
372 : {
373 29 : cnt++;
374 : }
375 19 : va_end (ap);
376 19 : as->args = GNUNET_new_array (cnt,
377 : char *);
378 19 : as->args[0] = GNUNET_strdup ("taler-unified-setup");
379 19 : as->args[1] = GNUNET_strdup ("-c");
380 19 : as->args[2] = GNUNET_strdup (config_file);
381 19 : cnt = 3;
382 19 : va_start (ap,
383 : config_file);
384 48 : while (NULL != (arg = va_arg (ap,
385 : const char *)))
386 : {
387 29 : as->args[cnt++] = GNUNET_strdup (arg);
388 : }
389 19 : va_end (ap);
390 :
391 : {
392 19 : struct TALER_TESTING_Command cmd = {
393 : .cls = as,
394 : .label = label,
395 : .run = &system_run,
396 : .cleanup = &system_cleanup,
397 : .traits = &system_traits
398 : };
399 :
400 19 : return cmd;
401 : }
402 : }
403 :
404 :
405 : /* end of testing_api_cmd_system_start.c */
|