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_OS_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 473 : read_stdout (void *cls)
137 : {
138 473 : struct SystemState *as = cls;
139 : const struct GNUNET_DISK_FileHandle *fh;
140 : char buf[1024 * 10];
141 : ssize_t ret;
142 473 : size_t off = 0;
143 :
144 473 : as->reader = NULL;
145 473 : strcpy (buf,
146 473 : as->ibuf);
147 473 : off = strlen (buf);
148 473 : fh = GNUNET_DISK_pipe_handle (as->pipe_out,
149 : GNUNET_DISK_PIPE_END_READ);
150 473 : ret = GNUNET_DISK_file_read (fh,
151 473 : &buf[off],
152 : sizeof (buf) - off);
153 473 : 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 473 : 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 473 : if ( (1 != ret) ||
168 110 : ('.' != buf[off]) )
169 411 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
170 : "TUS: %.*s\n",
171 : (int) ret,
172 : &buf[off]);
173 473 : start_reader (as);
174 473 : off += ret;
175 473 : if (as->ready)
176 : {
177 : /* already done */
178 0 : return;
179 : }
180 473 : if (NULL !=
181 473 : 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 454 : mcpy = GNUNET_MIN (off,
197 : sizeof (as->ibuf) - 1);
198 454 : memcpy (as->ibuf,
199 454 : &buf[off - mcpy],
200 : mcpy);
201 454 : as->ibuf[mcpy] = '\0';
202 : }
203 : }
204 :
205 :
206 : static void
207 492 : start_reader (struct SystemState *as)
208 : {
209 : const struct GNUNET_DISK_FileHandle *fh;
210 :
211 492 : GNUNET_assert (NULL == as->reader);
212 492 : fh = GNUNET_DISK_pipe_handle (as->pipe_out,
213 : GNUNET_DISK_PIPE_END_READ);
214 492 : as->reader = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
215 : fh,
216 : &read_stdout,
217 : as);
218 492 : }
219 :
220 :
221 : /**
222 : * Run the command. Use the `taler-exchange-system' 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 : as->system_proc
242 38 : = GNUNET_OS_start_process_vap (
243 : GNUNET_OS_INHERIT_STD_ERR,
244 : as->pipe_in, as->pipe_out, NULL,
245 : "taler-unified-setup.sh",
246 19 : as->args);
247 19 : if (NULL == as->system_proc)
248 : {
249 0 : GNUNET_break (0);
250 0 : TALER_TESTING_interpreter_fail (is);
251 0 : return;
252 : }
253 19 : as->active = true;
254 19 : start_reader (as);
255 19 : as->cwh = GNUNET_wait_child (as->system_proc,
256 : &setup_terminated,
257 : as);
258 : }
259 :
260 :
261 : /**
262 : * Free the state of a "system" CMD, and possibly kill its
263 : * process if it did not terminate correctly.
264 : *
265 : * @param cls closure.
266 : * @param cmd the command being freed.
267 : */
268 : static void
269 19 : system_cleanup (void *cls,
270 : const struct TALER_TESTING_Command *cmd)
271 : {
272 19 : struct SystemState *as = cls;
273 :
274 : (void) cmd;
275 19 : if (NULL != as->cwh)
276 : {
277 19 : GNUNET_wait_child_cancel (as->cwh);
278 19 : as->cwh = NULL;
279 : }
280 19 : if (NULL != as->reader)
281 : {
282 19 : GNUNET_SCHEDULER_cancel (as->reader);
283 19 : as->reader = NULL;
284 : }
285 19 : if (NULL != as->system_proc)
286 : {
287 19 : if (as->active)
288 : {
289 19 : GNUNET_break (0 ==
290 : GNUNET_OS_process_kill (as->system_proc,
291 : SIGTERM));
292 19 : GNUNET_OS_process_wait (as->system_proc);
293 : }
294 19 : GNUNET_OS_process_destroy (as->system_proc);
295 19 : as->system_proc = NULL;
296 : }
297 19 : if (NULL != as->pipe_in)
298 : {
299 19 : GNUNET_break (GNUNET_OK ==
300 : GNUNET_DISK_pipe_close (as->pipe_in));
301 19 : as->pipe_in = NULL;
302 : }
303 19 : if (NULL != as->pipe_out)
304 : {
305 19 : GNUNET_break (GNUNET_OK ==
306 : GNUNET_DISK_pipe_close (as->pipe_out));
307 19 : as->pipe_out = NULL;
308 : }
309 :
310 105 : for (unsigned int i = 0; NULL != as->args[i]; i++)
311 86 : GNUNET_free (as->args[i]);
312 19 : GNUNET_free (as->args);
313 19 : GNUNET_free (as);
314 19 : }
315 :
316 :
317 : /**
318 : * Offer "system" CMD internal data to other commands.
319 : *
320 : * @param cls closure.
321 : * @param[out] ret result.
322 : * @param trait name of the trait.
323 : * @param index index number of the object to offer.
324 : * @return #GNUNET_OK on success
325 : */
326 : static enum GNUNET_GenericReturnValue
327 20 : system_traits (void *cls,
328 : const void **ret,
329 : const char *trait,
330 : unsigned int index)
331 : {
332 20 : struct SystemState *as = cls;
333 : struct TALER_TESTING_Trait traits[] = {
334 20 : TALER_TESTING_make_trait_process (&as->system_proc),
335 20 : TALER_TESTING_trait_end ()
336 : };
337 :
338 20 : return TALER_TESTING_get_trait (traits,
339 : ret,
340 : trait,
341 : index);
342 : }
343 :
344 :
345 : struct TALER_TESTING_Command
346 19 : TALER_TESTING_cmd_system_start (
347 : const char *label,
348 : const char *config_file,
349 : ...)
350 : {
351 : struct SystemState *as;
352 : va_list ap;
353 : const char *arg;
354 : unsigned int cnt;
355 :
356 19 : as = GNUNET_new (struct SystemState);
357 19 : cnt = 4; /* 0-2 reserved, +1 for NULL termination */
358 19 : va_start (ap,
359 : config_file);
360 48 : while (NULL != (arg = va_arg (ap,
361 : const char *)))
362 : {
363 29 : cnt++;
364 : }
365 19 : va_end (ap);
366 19 : as->args = GNUNET_new_array (cnt,
367 : char *);
368 19 : as->args[0] = GNUNET_strdup ("taler-unified-setup");
369 19 : as->args[1] = GNUNET_strdup ("-c");
370 19 : as->args[2] = GNUNET_strdup (config_file);
371 19 : cnt = 3;
372 19 : va_start (ap,
373 : config_file);
374 48 : while (NULL != (arg = va_arg (ap,
375 : const char *)))
376 : {
377 29 : as->args[cnt++] = GNUNET_strdup (arg);
378 : }
379 19 : va_end (ap);
380 :
381 : {
382 19 : struct TALER_TESTING_Command cmd = {
383 : .cls = as,
384 : .label = label,
385 : .run = &system_run,
386 : .cleanup = &system_cleanup,
387 : .traits = &system_traits
388 : };
389 :
390 19 : return cmd;
391 : }
392 : }
393 :
394 :
395 : /* end of testing_api_cmd_system_start.c */
|