conversion.c (11425B)
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 read_cb (void *cls) 110 { 111 struct TALER_JSON_ExternalConversion *ec = cls; 112 113 ec->read_task = NULL; 114 while (1) 115 { 116 ssize_t ret; 117 118 if (ec->read_size == ec->read_pos) 119 { 120 /* Grow input buffer */ 121 size_t ns; 122 void *tmp; 123 124 ns = GNUNET_MAX (2 * ec->read_size, 125 1024); 126 if (ns > GNUNET_MAX_MALLOC_CHECKED) 127 ns = GNUNET_MAX_MALLOC_CHECKED; 128 if (ec->read_size == ns) 129 { 130 /* Helper returned more than 40 MB of data! Stop reading! */ 131 GNUNET_break (0); 132 GNUNET_break (GNUNET_OK == 133 GNUNET_DISK_file_close (ec->chld_stdin)); 134 ec->chld_stdin = NULL; 135 return; 136 } 137 tmp = GNUNET_malloc_large (ns); 138 if (NULL == tmp) 139 { 140 /* out of memory, also stop reading */ 141 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 142 "malloc"); 143 GNUNET_break (GNUNET_OK == 144 GNUNET_DISK_file_close (ec->chld_stdin)); 145 ec->chld_stdin = NULL; 146 return; 147 } 148 GNUNET_memcpy (tmp, 149 ec->read_buf, 150 ec->read_pos); 151 GNUNET_free (ec->read_buf); 152 ec->read_buf = tmp; 153 ec->read_size = ns; 154 } 155 ret = GNUNET_DISK_file_read (ec->chld_stdout, 156 ec->read_buf + ec->read_pos, 157 ec->read_size - ec->read_pos); 158 if (ret < 0) 159 { 160 if ( (EAGAIN != errno) && 161 (EWOULDBLOCK != errno) && 162 (EINTR != errno) ) 163 { 164 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 165 "read"); 166 return; 167 } 168 break; 169 } 170 if (0 == ret) 171 { 172 /* regular end of stream, good! */ 173 return; 174 } 175 GNUNET_assert (ec->read_size >= ec->read_pos + ret); 176 ec->read_pos += ret; 177 } 178 ec->read_task 179 = GNUNET_SCHEDULER_add_read_file ( 180 GNUNET_TIME_UNIT_FOREVER_REL, 181 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 write_cb (void *cls) 195 { 196 struct TALER_JSON_ExternalConversion *ec = cls; 197 ssize_t ret; 198 199 ec->write_task = NULL; 200 while (ec->write_size > ec->write_pos) 201 { 202 ret = GNUNET_DISK_file_write (ec->chld_stdin, 203 ec->write_buf + ec->write_pos, 204 ec->write_size - ec->write_pos); 205 if (ret < 0) 206 { 207 if ( (EAGAIN != errno) && 208 (EINTR != errno) ) 209 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 210 "write"); 211 break; 212 } 213 if (0 == ret) 214 { 215 GNUNET_break (0); 216 break; 217 } 218 GNUNET_assert (ec->write_size >= ec->write_pos + ret); 219 ec->write_pos += ret; 220 } 221 if ( (ec->write_size > ec->write_pos) && 222 ( (EAGAIN == errno) || 223 (EWOULDBLOCK == errno) || 224 (EINTR == errno) ) ) 225 { 226 ec->write_task 227 = GNUNET_SCHEDULER_add_write_file ( 228 GNUNET_TIME_UNIT_FOREVER_REL, 229 ec->chld_stdin, 230 &write_cb, 231 ec); 232 } 233 else 234 { 235 GNUNET_break (GNUNET_OK == 236 GNUNET_DISK_file_close (ec->chld_stdin)); 237 ec->chld_stdin = NULL; 238 } 239 } 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 child_done_cb (void *cls, 253 enum GNUNET_OS_ProcessStatusType type, 254 long unsigned int exit_code) 255 { 256 struct TALER_JSON_ExternalConversion *ec = cls; 257 json_t *j = NULL; 258 json_error_t err; 259 260 ec->cwh = NULL; 261 if (NULL != ec->read_task) 262 { 263 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 read_cb (ec); 267 } 268 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 GNUNET_process_destroy (ec->helper); 274 ec->helper = NULL; 275 if (0 != ec->read_pos) 276 { 277 j = json_loadb (ec->read_buf, 278 ec->read_pos, 279 JSON_REJECT_DUPLICATES, 280 &err); 281 if (NULL == j) 282 { 283 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 284 "Failed to parse JSON from helper at %d: %s\n", 285 err.position, 286 err.text); 287 fprintf (stderr, 288 "%.*s\n", 289 (int) ec->read_pos, 290 (const char *) ec->read_buf); 291 } 292 } 293 ec->cb (ec->cb_cls, 294 type, 295 exit_code, 296 j); 297 json_decref (j); 298 TALER_JSON_external_conversion_stop (ec); 299 } 300 301 302 struct TALER_JSON_ExternalConversion * 303 TALER_JSON_external_conversion_start (const json_t *input, 304 TALER_JSON_JsonCallback cb, 305 void *cb_cls, 306 const char *binary, 307 const char **argv) 308 { 309 struct TALER_JSON_ExternalConversion *ec; 310 struct GNUNET_DISK_PipeHandle *pipe_stdin; 311 struct GNUNET_DISK_PipeHandle *pipe_stdout; 312 313 ec = GNUNET_new (struct TALER_JSON_ExternalConversion); 314 ec->cb = cb; 315 ec->cb_cls = cb_cls; 316 pipe_stdin = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ); 317 GNUNET_assert (NULL != pipe_stdin); 318 pipe_stdout = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_WRITE); 319 GNUNET_assert (NULL != pipe_stdout); 320 ec->helper = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR); 321 GNUNET_assert (GNUNET_OK == 322 GNUNET_process_set_options ( 323 ec->helper, 324 GNUNET_process_option_inherit_rpipe (pipe_stdin, 325 STDIN_FILENO), 326 GNUNET_process_option_inherit_wpipe (pipe_stdout, 327 STDOUT_FILENO))); 328 if (GNUNET_OK != 329 GNUNET_process_run_command_argv (ec->helper, 330 binary, 331 argv)) 332 { 333 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 334 "Failed to run conversion helper `%s'\n", 335 binary); 336 GNUNET_break (GNUNET_OK == 337 GNUNET_DISK_pipe_close (pipe_stdin)); 338 GNUNET_break (GNUNET_OK == 339 GNUNET_DISK_pipe_close (pipe_stdout)); 340 GNUNET_process_destroy (ec->helper); 341 GNUNET_free (ec); 342 return NULL; 343 } 344 ec->chld_stdin = 345 GNUNET_DISK_pipe_detach_end (pipe_stdin, 346 GNUNET_DISK_PIPE_END_WRITE); 347 ec->chld_stdout = 348 GNUNET_DISK_pipe_detach_end (pipe_stdout, 349 GNUNET_DISK_PIPE_END_READ); 350 GNUNET_break (GNUNET_OK == 351 GNUNET_DISK_pipe_close (pipe_stdin)); 352 GNUNET_break (GNUNET_OK == 353 GNUNET_DISK_pipe_close (pipe_stdout)); 354 ec->write_buf = json_dumps (input, 355 JSON_COMPACT); 356 ec->write_size = strlen (ec->write_buf); 357 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 358 "Passing %llu bytes to JSON conversion tool\n", 359 (unsigned long long) ec->write_size); 360 ec->read_task 361 = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, 362 ec->chld_stdout, 363 &read_cb, 364 ec); 365 ec->write_task 366 = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL, 367 ec->chld_stdin, 368 &write_cb, 369 ec); 370 ec->cwh = GNUNET_wait_child (ec->helper, 371 &child_done_cb, 372 ec); 373 return ec; 374 } 375 376 377 void 378 TALER_JSON_external_conversion_stop ( 379 struct TALER_JSON_ExternalConversion *ec) 380 { 381 if (NULL != ec->cwh) 382 { 383 GNUNET_wait_child_cancel (ec->cwh); 384 ec->cwh = NULL; 385 } 386 if (NULL != ec->helper) 387 { 388 GNUNET_break (GNUNET_OK == 389 GNUNET_process_kill (ec->helper, 390 SIGKILL)); 391 GNUNET_process_destroy (ec->helper); 392 ec->helper = NULL; 393 } 394 if (NULL != ec->read_task) 395 { 396 GNUNET_SCHEDULER_cancel (ec->read_task); 397 ec->read_task = NULL; 398 } 399 if (NULL != ec->write_task) 400 { 401 GNUNET_SCHEDULER_cancel (ec->write_task); 402 ec->write_task = NULL; 403 } 404 if (NULL != ec->chld_stdin) 405 { 406 GNUNET_break (GNUNET_OK == 407 GNUNET_DISK_file_close (ec->chld_stdin)); 408 ec->chld_stdin = NULL; 409 } 410 if (NULL != ec->chld_stdout) 411 { 412 GNUNET_break (GNUNET_OK == 413 GNUNET_DISK_file_close (ec->chld_stdout)); 414 ec->chld_stdout = NULL; 415 } 416 GNUNET_free (ec->read_buf); 417 free (ec->write_buf); 418 GNUNET_free (ec); 419 }