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 under the
6 : terms of the GNU General Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file conversion.c
18 : * @brief helper routines to run some external JSON-to-JSON converter
19 : * @author Christian Grothoff
20 : */
21 : #include "platform.h" /* UNNECESSARY? */
22 : #include "taler/taler_util.h"
23 : #include "taler/taler_json_lib.h"
24 : #include <gnunet/gnunet_util_lib.h>
25 :
26 :
27 : struct TALER_JSON_ExternalConversion
28 : {
29 : /**
30 : * Callback to call with the result.
31 : */
32 : TALER_JSON_JsonCallback cb;
33 :
34 : /**
35 : * Closure for @e cb.
36 : */
37 : void *cb_cls;
38 :
39 : /**
40 : * Handle to the helper process.
41 : */
42 : struct GNUNET_Process *helper;
43 :
44 : /**
45 : * Pipe for the stdin of the @e helper.
46 : */
47 : struct GNUNET_DISK_FileHandle *chld_stdin;
48 :
49 : /**
50 : * Pipe for the stdout of the @e helper.
51 : */
52 : struct GNUNET_DISK_FileHandle *chld_stdout;
53 :
54 : /**
55 : * Handle to wait on the child to terminate.
56 : */
57 : struct GNUNET_ChildWaitHandle *cwh;
58 :
59 : /**
60 : * Task to read JSON output from the child.
61 : */
62 : struct GNUNET_SCHEDULER_Task *read_task;
63 :
64 : /**
65 : * Task to send JSON input to the child.
66 : */
67 : struct GNUNET_SCHEDULER_Task *write_task;
68 :
69 : /**
70 : * Buffer with data we need to send to the helper.
71 : */
72 : void *write_buf;
73 :
74 : /**
75 : * Buffer for reading data from the helper.
76 : */
77 : void *read_buf;
78 :
79 : /**
80 : * Total length of @e write_buf.
81 : */
82 : size_t write_size;
83 :
84 : /**
85 : * Current write position in @e write_buf.
86 : */
87 : size_t write_pos;
88 :
89 : /**
90 : * Current size of @a read_buf.
91 : */
92 : size_t read_size;
93 :
94 : /**
95 : * Current offset in @a read_buf.
96 : */
97 : size_t read_pos;
98 :
99 : };
100 :
101 :
102 : /**
103 : * Function called when we can read more data from
104 : * the child process.
105 : *
106 : * @param cls our `struct TALER_JSON_ExternalConversion *`
107 : */
108 : static void
109 40 : read_cb (void *cls)
110 : {
111 40 : struct TALER_JSON_ExternalConversion *ec = cls;
112 :
113 40 : ec->read_task = NULL;
114 : while (1)
115 30 : {
116 : ssize_t ret;
117 :
118 70 : if (ec->read_size == ec->read_pos)
119 : {
120 : /* Grow input buffer */
121 : size_t ns;
122 : void *tmp;
123 :
124 30 : ns = GNUNET_MAX (2 * ec->read_size,
125 : 1024);
126 30 : if (ns > GNUNET_MAX_MALLOC_CHECKED)
127 0 : ns = GNUNET_MAX_MALLOC_CHECKED;
128 30 : if (ec->read_size == ns)
129 : {
130 : /* Helper returned more than 40 MB of data! Stop reading! */
131 0 : GNUNET_break (0);
132 0 : GNUNET_break (GNUNET_OK ==
133 : GNUNET_DISK_file_close (ec->chld_stdout));
134 0 : ec->chld_stdout = NULL;
135 20 : return;
136 : }
137 30 : tmp = GNUNET_malloc_large (ns);
138 30 : if (NULL == tmp)
139 : {
140 : /* out of memory, also stop reading */
141 0 : GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
142 : "malloc");
143 0 : GNUNET_break (GNUNET_OK ==
144 : GNUNET_DISK_file_close (ec->chld_stdout));
145 0 : ec->chld_stdout = NULL;
146 0 : return;
147 : }
148 30 : GNUNET_memcpy (tmp,
149 : ec->read_buf,
150 : ec->read_pos);
151 30 : GNUNET_free (ec->read_buf);
152 30 : ec->read_buf = tmp;
153 30 : ec->read_size = ns;
154 : }
155 70 : ret = GNUNET_DISK_file_read (ec->chld_stdout,
156 70 : ec->read_buf + ec->read_pos,
157 70 : ec->read_size - ec->read_pos);
158 70 : if (ret < 0)
159 : {
160 20 : if ( (EAGAIN != errno) &&
161 0 : (EWOULDBLOCK != errno) &&
162 0 : (EINTR != errno) )
163 : {
164 0 : GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
165 : "read");
166 0 : return;
167 : }
168 20 : break;
169 : }
170 50 : if (0 == ret)
171 : {
172 : /* regular end of stream, good! */
173 20 : return;
174 : }
175 30 : GNUNET_assert (ec->read_size >= ec->read_pos + ret);
176 30 : ec->read_pos += ret;
177 : }
178 : ec->read_task
179 20 : = GNUNET_SCHEDULER_add_read_file (
180 20 : GNUNET_TIME_UNIT_FOREVER_REL,
181 20 : ec->chld_stdout,
182 : &read_cb,
183 : ec);
184 : }
185 :
186 :
187 : /**
188 : * Function called when we can write more data to
189 : * the child process.
190 : *
191 : * @param cls our `struct TALER_JSON_ExternalConversion *`
192 : */
193 : static void
194 20 : write_cb (void *cls)
195 : {
196 20 : struct TALER_JSON_ExternalConversion *ec = cls;
197 : ssize_t ret;
198 :
199 20 : ec->write_task = NULL;
200 40 : while (ec->write_size > ec->write_pos)
201 : {
202 20 : ret = GNUNET_DISK_file_write (ec->chld_stdin,
203 20 : ec->write_buf + ec->write_pos,
204 20 : ec->write_size - ec->write_pos);
205 20 : if (ret < 0)
206 : {
207 0 : if ( (EAGAIN != errno) &&
208 0 : (EINTR != errno) )
209 0 : GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
210 : "write");
211 0 : break;
212 : }
213 20 : if (0 == ret)
214 : {
215 0 : GNUNET_break (0);
216 0 : break;
217 : }
218 20 : GNUNET_assert (ec->write_size >= ec->write_pos + ret);
219 20 : ec->write_pos += ret;
220 : }
221 20 : if ( (ec->write_size > ec->write_pos) &&
222 0 : ( (EAGAIN == errno) ||
223 0 : (EWOULDBLOCK == errno) ||
224 0 : (EINTR == errno) ) )
225 0 : {
226 : ec->write_task
227 0 : = GNUNET_SCHEDULER_add_write_file (
228 0 : GNUNET_TIME_UNIT_FOREVER_REL,
229 0 : ec->chld_stdin,
230 : &write_cb,
231 : ec);
232 : }
233 : else
234 : {
235 20 : GNUNET_break (GNUNET_OK ==
236 : GNUNET_DISK_file_close (ec->chld_stdin));
237 20 : ec->chld_stdin = NULL;
238 : }
239 20 : }
240 :
241 :
242 : /**
243 : * Defines a GNUNET_ChildCompletedCallback which is sent back
244 : * upon death or completion of a child process.
245 : *
246 : * @param cls handle for the callback
247 : * @param type type of the process
248 : * @param exit_code status code of the process
249 : *
250 : */
251 : static void
252 20 : child_done_cb (void *cls,
253 : enum GNUNET_OS_ProcessStatusType type,
254 : long unsigned int exit_code)
255 : {
256 20 : struct TALER_JSON_ExternalConversion *ec = cls;
257 20 : json_t *j = NULL;
258 : json_error_t err;
259 :
260 20 : ec->cwh = NULL;
261 20 : if (NULL != ec->read_task)
262 : {
263 0 : GNUNET_SCHEDULER_cancel (ec->read_task);
264 : /* We could get the process termination notification before having drained
265 : the read buffer. So drain it now, just in case. */
266 0 : read_cb (ec);
267 : }
268 20 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
269 : "Conversion helper exited with status %d and code %llu after outputting %llu bytes of data\n",
270 : (int) type,
271 : (unsigned long long) exit_code,
272 : (unsigned long long) ec->read_pos);
273 20 : GNUNET_process_destroy (ec->helper);
274 20 : ec->helper = NULL;
275 20 : if (0 != ec->read_pos)
276 : {
277 20 : j = json_loadb (ec->read_buf,
278 : ec->read_pos,
279 : JSON_REJECT_DUPLICATES,
280 : &err);
281 20 : if (NULL == j)
282 : {
283 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
284 : "Failed to parse JSON from helper at %d: %s\n",
285 : err.position,
286 : err.text);
287 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
288 : "Helper output was: `%.*s'\n",
289 : (int) GNUNET_MIN (1024,
290 : ec->read_pos),
291 : (const char *) ec->read_buf);
292 : }
293 : }
294 20 : ec->cb (ec->cb_cls,
295 : type,
296 : exit_code,
297 : j);
298 20 : json_decref (j);
299 20 : TALER_JSON_external_conversion_stop (ec);
300 20 : }
301 :
302 :
303 : struct TALER_JSON_ExternalConversion *
304 20 : TALER_JSON_external_conversion_start (const json_t *input,
305 : TALER_JSON_JsonCallback cb,
306 : void *cb_cls,
307 : const char *binary,
308 : const char **argv)
309 : {
310 : struct TALER_JSON_ExternalConversion *ec;
311 : struct GNUNET_DISK_PipeHandle *pipe_stdin;
312 : struct GNUNET_DISK_PipeHandle *pipe_stdout;
313 :
314 20 : ec = GNUNET_new (struct TALER_JSON_ExternalConversion);
315 20 : ec->cb = cb;
316 20 : ec->cb_cls = cb_cls;
317 20 : pipe_stdin = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ);
318 20 : GNUNET_assert (NULL != pipe_stdin);
319 20 : pipe_stdout = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_WRITE);
320 20 : GNUNET_assert (NULL != pipe_stdout);
321 20 : ec->helper = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
322 20 : GNUNET_assert (GNUNET_OK ==
323 : GNUNET_process_set_options (
324 : ec->helper,
325 : GNUNET_process_option_inherit_rpipe (pipe_stdin,
326 : STDIN_FILENO),
327 : GNUNET_process_option_inherit_wpipe (pipe_stdout,
328 : STDOUT_FILENO)));
329 20 : if (GNUNET_OK !=
330 20 : GNUNET_process_run_command_argv (ec->helper,
331 : binary,
332 : argv))
333 : {
334 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
335 : "Failed to run conversion helper `%s'\n",
336 : binary);
337 0 : GNUNET_break (GNUNET_OK ==
338 : GNUNET_DISK_pipe_close (pipe_stdin));
339 0 : GNUNET_break (GNUNET_OK ==
340 : GNUNET_DISK_pipe_close (pipe_stdout));
341 0 : GNUNET_process_destroy (ec->helper);
342 0 : GNUNET_free (ec);
343 0 : return NULL;
344 : }
345 20 : ec->chld_stdin =
346 20 : GNUNET_DISK_pipe_detach_end (pipe_stdin,
347 : GNUNET_DISK_PIPE_END_WRITE);
348 20 : ec->chld_stdout =
349 20 : GNUNET_DISK_pipe_detach_end (pipe_stdout,
350 : GNUNET_DISK_PIPE_END_READ);
351 20 : GNUNET_break (GNUNET_OK ==
352 : GNUNET_DISK_pipe_close (pipe_stdin));
353 20 : GNUNET_break (GNUNET_OK ==
354 : GNUNET_DISK_pipe_close (pipe_stdout));
355 20 : ec->write_buf = json_dumps (input,
356 : JSON_COMPACT);
357 20 : GNUNET_assert (NULL != ec->write_buf);
358 20 : ec->write_size = strlen (ec->write_buf);
359 20 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
360 : "Passing %llu bytes to JSON conversion tool\n",
361 : (unsigned long long) ec->write_size);
362 : ec->read_task
363 40 : = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
364 20 : ec->chld_stdout,
365 : &read_cb,
366 : ec);
367 : ec->write_task
368 40 : = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
369 20 : ec->chld_stdin,
370 : &write_cb,
371 : ec);
372 20 : ec->cwh = GNUNET_wait_child (ec->helper,
373 : &child_done_cb,
374 : ec);
375 20 : return ec;
376 : }
377 :
378 :
379 : void
380 20 : TALER_JSON_external_conversion_stop (
381 : struct TALER_JSON_ExternalConversion *ec)
382 : {
383 20 : if (NULL != ec->cwh)
384 : {
385 0 : GNUNET_wait_child_cancel (ec->cwh);
386 0 : ec->cwh = NULL;
387 : }
388 20 : if (NULL != ec->helper)
389 : {
390 0 : GNUNET_break (GNUNET_OK ==
391 : GNUNET_process_kill (ec->helper,
392 : SIGKILL));
393 0 : GNUNET_process_destroy (ec->helper);
394 0 : ec->helper = NULL;
395 : }
396 20 : if (NULL != ec->read_task)
397 : {
398 0 : GNUNET_SCHEDULER_cancel (ec->read_task);
399 0 : ec->read_task = NULL;
400 : }
401 20 : if (NULL != ec->write_task)
402 : {
403 0 : GNUNET_SCHEDULER_cancel (ec->write_task);
404 0 : ec->write_task = NULL;
405 : }
406 20 : if (NULL != ec->chld_stdin)
407 : {
408 0 : GNUNET_break (GNUNET_OK ==
409 : GNUNET_DISK_file_close (ec->chld_stdin));
410 0 : ec->chld_stdin = NULL;
411 : }
412 20 : if (NULL != ec->chld_stdout)
413 : {
414 20 : GNUNET_break (GNUNET_OK ==
415 : GNUNET_DISK_file_close (ec->chld_stdout));
416 20 : ec->chld_stdout = NULL;
417 : }
418 20 : GNUNET_free (ec->read_buf);
419 20 : free (ec->write_buf);
420 20 : GNUNET_free (ec);
421 20 : }
|