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"
22 : #include "taler_util.h"
23 : #include "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_OS_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_stdin));
134 20 : return;
135 : }
136 30 : tmp = GNUNET_malloc_large (ns);
137 30 : if (NULL == tmp)
138 : {
139 : /* out of memory, also stop reading */
140 0 : GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
141 : "malloc");
142 0 : GNUNET_break (GNUNET_OK ==
143 : GNUNET_DISK_file_close (ec->chld_stdin));
144 0 : return;
145 : }
146 30 : GNUNET_memcpy (tmp,
147 : ec->read_buf,
148 : ec->read_pos);
149 30 : GNUNET_free (ec->read_buf);
150 30 : ec->read_buf = tmp;
151 30 : ec->read_size = ns;
152 : }
153 70 : ret = GNUNET_DISK_file_read (ec->chld_stdout,
154 70 : ec->read_buf + ec->read_pos,
155 70 : ec->read_size - ec->read_pos);
156 70 : if (ret < 0)
157 : {
158 20 : if ( (EAGAIN != errno) &&
159 0 : (EWOULDBLOCK != errno) &&
160 0 : (EINTR != errno) )
161 : {
162 0 : GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
163 : "read");
164 0 : return;
165 : }
166 20 : break;
167 : }
168 50 : if (0 == ret)
169 : {
170 : /* regular end of stream, good! */
171 20 : return;
172 : }
173 30 : GNUNET_assert (ec->read_size >= ec->read_pos + ret);
174 30 : ec->read_pos += ret;
175 : }
176 : ec->read_task
177 20 : = GNUNET_SCHEDULER_add_read_file (
178 20 : GNUNET_TIME_UNIT_FOREVER_REL,
179 20 : ec->chld_stdout,
180 : &read_cb,
181 : ec);
182 : }
183 :
184 :
185 : /**
186 : * Function called when we can write more data to
187 : * the child process.
188 : *
189 : * @param cls our `struct TALER_JSON_ExternalConversion *`
190 : */
191 : static void
192 20 : write_cb (void *cls)
193 : {
194 20 : struct TALER_JSON_ExternalConversion *ec = cls;
195 : ssize_t ret;
196 :
197 20 : ec->write_task = NULL;
198 40 : while (ec->write_size > ec->write_pos)
199 : {
200 20 : ret = GNUNET_DISK_file_write (ec->chld_stdin,
201 20 : ec->write_buf + ec->write_pos,
202 20 : ec->write_size - ec->write_pos);
203 20 : if (ret < 0)
204 : {
205 0 : if ( (EAGAIN != errno) &&
206 0 : (EINTR != errno) )
207 0 : GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
208 : "write");
209 0 : break;
210 : }
211 20 : if (0 == ret)
212 : {
213 0 : GNUNET_break (0);
214 0 : break;
215 : }
216 20 : GNUNET_assert (ec->write_size >= ec->write_pos + ret);
217 20 : ec->write_pos += ret;
218 : }
219 20 : if ( (ec->write_size > ec->write_pos) &&
220 0 : ( (EAGAIN == errno) ||
221 0 : (EWOULDBLOCK == errno) ||
222 0 : (EINTR == errno) ) )
223 0 : {
224 : ec->write_task
225 0 : = GNUNET_SCHEDULER_add_write_file (
226 0 : GNUNET_TIME_UNIT_FOREVER_REL,
227 0 : ec->chld_stdin,
228 : &write_cb,
229 : ec);
230 : }
231 : else
232 : {
233 20 : GNUNET_break (GNUNET_OK ==
234 : GNUNET_DISK_file_close (ec->chld_stdin));
235 20 : ec->chld_stdin = NULL;
236 : }
237 20 : }
238 :
239 :
240 : /**
241 : * Defines a GNUNET_ChildCompletedCallback which is sent back
242 : * upon death or completion of a child process.
243 : *
244 : * @param cls handle for the callback
245 : * @param type type of the process
246 : * @param exit_code status code of the process
247 : *
248 : */
249 : static void
250 20 : child_done_cb (void *cls,
251 : enum GNUNET_OS_ProcessStatusType type,
252 : long unsigned int exit_code)
253 : {
254 20 : struct TALER_JSON_ExternalConversion *ec = cls;
255 20 : json_t *j = NULL;
256 : json_error_t err;
257 :
258 20 : ec->cwh = NULL;
259 20 : if (NULL != ec->read_task)
260 : {
261 0 : GNUNET_SCHEDULER_cancel (ec->read_task);
262 : /* We could get the process termination notification before having drained
263 : the read buffer. So drain it now, just in case. */
264 0 : read_cb (ec);
265 : }
266 20 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
267 : "Conversion helper exited with status %d and code %llu after outputting %llu bytes of data\n",
268 : (int) type,
269 : (unsigned long long) exit_code,
270 : (unsigned long long) ec->read_pos);
271 20 : GNUNET_OS_process_destroy (ec->helper);
272 20 : ec->helper = NULL;
273 20 : if (0 != ec->read_pos)
274 : {
275 20 : j = json_loadb (ec->read_buf,
276 : ec->read_pos,
277 : JSON_REJECT_DUPLICATES,
278 : &err);
279 20 : if (NULL == j)
280 : {
281 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
282 : "Failed to parse JSON from helper at %d: %s\n",
283 : err.position,
284 : err.text);
285 0 : fprintf (stderr,
286 : "%.*s\n",
287 0 : (int) ec->read_pos,
288 0 : (const char *) ec->read_buf);
289 : }
290 : }
291 20 : ec->cb (ec->cb_cls,
292 : type,
293 : exit_code,
294 : j);
295 20 : json_decref (j);
296 20 : TALER_JSON_external_conversion_stop (ec);
297 20 : }
298 :
299 :
300 : struct TALER_JSON_ExternalConversion *
301 20 : TALER_JSON_external_conversion_start (const json_t *input,
302 : TALER_JSON_JsonCallback cb,
303 : void *cb_cls,
304 : const char *binary,
305 : const char **argv)
306 : {
307 : struct TALER_JSON_ExternalConversion *ec;
308 : struct GNUNET_DISK_PipeHandle *pipe_stdin;
309 : struct GNUNET_DISK_PipeHandle *pipe_stdout;
310 :
311 20 : ec = GNUNET_new (struct TALER_JSON_ExternalConversion);
312 20 : ec->cb = cb;
313 20 : ec->cb_cls = cb_cls;
314 20 : pipe_stdin = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ);
315 20 : GNUNET_assert (NULL != pipe_stdin);
316 20 : pipe_stdout = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_WRITE);
317 20 : GNUNET_assert (NULL != pipe_stdout);
318 20 : ec->helper = GNUNET_OS_start_process_vap (GNUNET_OS_INHERIT_STD_ERR,
319 : pipe_stdin,
320 : pipe_stdout,
321 : NULL,
322 : binary,
323 : (char *const *) argv);
324 20 : if (NULL == ec->helper)
325 : {
326 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
327 : "Failed to run conversion helper `%s'\n",
328 : binary);
329 0 : GNUNET_break (GNUNET_OK ==
330 : GNUNET_DISK_pipe_close (pipe_stdin));
331 0 : GNUNET_break (GNUNET_OK ==
332 : GNUNET_DISK_pipe_close (pipe_stdout));
333 0 : GNUNET_free (ec);
334 0 : return NULL;
335 : }
336 20 : ec->chld_stdin =
337 20 : GNUNET_DISK_pipe_detach_end (pipe_stdin,
338 : GNUNET_DISK_PIPE_END_WRITE);
339 20 : ec->chld_stdout =
340 20 : GNUNET_DISK_pipe_detach_end (pipe_stdout,
341 : GNUNET_DISK_PIPE_END_READ);
342 20 : GNUNET_break (GNUNET_OK ==
343 : GNUNET_DISK_pipe_close (pipe_stdin));
344 20 : GNUNET_break (GNUNET_OK ==
345 : GNUNET_DISK_pipe_close (pipe_stdout));
346 20 : ec->write_buf = json_dumps (input, JSON_COMPACT);
347 20 : ec->write_size = strlen (ec->write_buf);
348 20 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
349 : "Passing %llu bytes to JSON conversion tool\n",
350 : (unsigned long long) ec->write_size);
351 : ec->read_task
352 40 : = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
353 20 : ec->chld_stdout,
354 : &read_cb,
355 : ec);
356 : ec->write_task
357 40 : = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
358 20 : ec->chld_stdin,
359 : &write_cb,
360 : ec);
361 20 : ec->cwh = GNUNET_wait_child (ec->helper,
362 : &child_done_cb,
363 : ec);
364 20 : return ec;
365 : }
366 :
367 :
368 : void
369 20 : TALER_JSON_external_conversion_stop (
370 : struct TALER_JSON_ExternalConversion *ec)
371 : {
372 20 : if (NULL != ec->cwh)
373 : {
374 0 : GNUNET_wait_child_cancel (ec->cwh);
375 0 : ec->cwh = NULL;
376 : }
377 20 : if (NULL != ec->helper)
378 : {
379 0 : GNUNET_break (0 ==
380 : GNUNET_OS_process_kill (ec->helper,
381 : SIGKILL));
382 0 : GNUNET_OS_process_destroy (ec->helper);
383 : }
384 20 : if (NULL != ec->read_task)
385 : {
386 0 : GNUNET_SCHEDULER_cancel (ec->read_task);
387 0 : ec->read_task = NULL;
388 : }
389 20 : if (NULL != ec->write_task)
390 : {
391 0 : GNUNET_SCHEDULER_cancel (ec->write_task);
392 0 : ec->write_task = NULL;
393 : }
394 20 : if (NULL != ec->chld_stdin)
395 : {
396 0 : GNUNET_break (GNUNET_OK ==
397 : GNUNET_DISK_file_close (ec->chld_stdin));
398 0 : ec->chld_stdin = NULL;
399 : }
400 20 : if (NULL != ec->chld_stdout)
401 : {
402 20 : GNUNET_break (GNUNET_OK ==
403 : GNUNET_DISK_file_close (ec->chld_stdout));
404 20 : ec->chld_stdout = NULL;
405 : }
406 20 : GNUNET_free (ec->read_buf);
407 20 : free (ec->write_buf);
408 20 : GNUNET_free (ec);
409 20 : }
|