conversion.c (11575B)
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_stdout)); 134 ec->chld_stdout = 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_stdout)); 145 ec->chld_stdout = 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 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 ec->cb (ec->cb_cls, 295 type, 296 exit_code, 297 j); 298 json_decref (j); 299 TALER_JSON_external_conversion_stop (ec); 300 } 301 302 303 struct TALER_JSON_ExternalConversion * 304 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 ec = GNUNET_new (struct TALER_JSON_ExternalConversion); 315 ec->cb = cb; 316 ec->cb_cls = cb_cls; 317 pipe_stdin = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ); 318 GNUNET_assert (NULL != pipe_stdin); 319 pipe_stdout = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_WRITE); 320 GNUNET_assert (NULL != pipe_stdout); 321 ec->helper = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR); 322 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 if (GNUNET_OK != 330 GNUNET_process_run_command_argv (ec->helper, 331 binary, 332 argv)) 333 { 334 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 335 "Failed to run conversion helper `%s'\n", 336 binary); 337 GNUNET_break (GNUNET_OK == 338 GNUNET_DISK_pipe_close (pipe_stdin)); 339 GNUNET_break (GNUNET_OK == 340 GNUNET_DISK_pipe_close (pipe_stdout)); 341 GNUNET_process_destroy (ec->helper); 342 GNUNET_free (ec); 343 return NULL; 344 } 345 ec->chld_stdin = 346 GNUNET_DISK_pipe_detach_end (pipe_stdin, 347 GNUNET_DISK_PIPE_END_WRITE); 348 ec->chld_stdout = 349 GNUNET_DISK_pipe_detach_end (pipe_stdout, 350 GNUNET_DISK_PIPE_END_READ); 351 GNUNET_break (GNUNET_OK == 352 GNUNET_DISK_pipe_close (pipe_stdin)); 353 GNUNET_break (GNUNET_OK == 354 GNUNET_DISK_pipe_close (pipe_stdout)); 355 ec->write_buf = json_dumps (input, 356 JSON_COMPACT); 357 GNUNET_assert (NULL != ec->write_buf); 358 ec->write_size = strlen (ec->write_buf); 359 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 = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, 364 ec->chld_stdout, 365 &read_cb, 366 ec); 367 ec->write_task 368 = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL, 369 ec->chld_stdin, 370 &write_cb, 371 ec); 372 ec->cwh = GNUNET_wait_child (ec->helper, 373 &child_done_cb, 374 ec); 375 return ec; 376 } 377 378 379 void 380 TALER_JSON_external_conversion_stop ( 381 struct TALER_JSON_ExternalConversion *ec) 382 { 383 if (NULL != ec->cwh) 384 { 385 GNUNET_wait_child_cancel (ec->cwh); 386 ec->cwh = NULL; 387 } 388 if (NULL != ec->helper) 389 { 390 GNUNET_break (GNUNET_OK == 391 GNUNET_process_kill (ec->helper, 392 SIGKILL)); 393 GNUNET_process_destroy (ec->helper); 394 ec->helper = NULL; 395 } 396 if (NULL != ec->read_task) 397 { 398 GNUNET_SCHEDULER_cancel (ec->read_task); 399 ec->read_task = NULL; 400 } 401 if (NULL != ec->write_task) 402 { 403 GNUNET_SCHEDULER_cancel (ec->write_task); 404 ec->write_task = NULL; 405 } 406 if (NULL != ec->chld_stdin) 407 { 408 GNUNET_break (GNUNET_OK == 409 GNUNET_DISK_file_close (ec->chld_stdin)); 410 ec->chld_stdin = NULL; 411 } 412 if (NULL != ec->chld_stdout) 413 { 414 GNUNET_break (GNUNET_OK == 415 GNUNET_DISK_file_close (ec->chld_stdout)); 416 ec->chld_stdout = NULL; 417 } 418 GNUNET_free (ec->read_buf); 419 free (ec->write_buf); 420 GNUNET_free (ec); 421 }