conversion.c (11023B)
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 "taler/platform.h" 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_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 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 return; 135 } 136 tmp = GNUNET_malloc_large (ns); 137 if (NULL == tmp) 138 { 139 /* out of memory, also stop reading */ 140 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, 141 "malloc"); 142 GNUNET_break (GNUNET_OK == 143 GNUNET_DISK_file_close (ec->chld_stdin)); 144 return; 145 } 146 GNUNET_memcpy (tmp, 147 ec->read_buf, 148 ec->read_pos); 149 GNUNET_free (ec->read_buf); 150 ec->read_buf = tmp; 151 ec->read_size = ns; 152 } 153 ret = GNUNET_DISK_file_read (ec->chld_stdout, 154 ec->read_buf + ec->read_pos, 155 ec->read_size - ec->read_pos); 156 if (ret < 0) 157 { 158 if ( (EAGAIN != errno) && 159 (EWOULDBLOCK != errno) && 160 (EINTR != errno) ) 161 { 162 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 163 "read"); 164 return; 165 } 166 break; 167 } 168 if (0 == ret) 169 { 170 /* regular end of stream, good! */ 171 return; 172 } 173 GNUNET_assert (ec->read_size >= ec->read_pos + ret); 174 ec->read_pos += ret; 175 } 176 ec->read_task 177 = GNUNET_SCHEDULER_add_read_file ( 178 GNUNET_TIME_UNIT_FOREVER_REL, 179 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 write_cb (void *cls) 193 { 194 struct TALER_JSON_ExternalConversion *ec = cls; 195 ssize_t ret; 196 197 ec->write_task = NULL; 198 while (ec->write_size > ec->write_pos) 199 { 200 ret = GNUNET_DISK_file_write (ec->chld_stdin, 201 ec->write_buf + ec->write_pos, 202 ec->write_size - ec->write_pos); 203 if (ret < 0) 204 { 205 if ( (EAGAIN != errno) && 206 (EINTR != errno) ) 207 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 208 "write"); 209 break; 210 } 211 if (0 == ret) 212 { 213 GNUNET_break (0); 214 break; 215 } 216 GNUNET_assert (ec->write_size >= ec->write_pos + ret); 217 ec->write_pos += ret; 218 } 219 if ( (ec->write_size > ec->write_pos) && 220 ( (EAGAIN == errno) || 221 (EWOULDBLOCK == errno) || 222 (EINTR == errno) ) ) 223 { 224 ec->write_task 225 = GNUNET_SCHEDULER_add_write_file ( 226 GNUNET_TIME_UNIT_FOREVER_REL, 227 ec->chld_stdin, 228 &write_cb, 229 ec); 230 } 231 else 232 { 233 GNUNET_break (GNUNET_OK == 234 GNUNET_DISK_file_close (ec->chld_stdin)); 235 ec->chld_stdin = NULL; 236 } 237 } 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 child_done_cb (void *cls, 251 enum GNUNET_OS_ProcessStatusType type, 252 long unsigned int exit_code) 253 { 254 struct TALER_JSON_ExternalConversion *ec = cls; 255 json_t *j = NULL; 256 json_error_t err; 257 258 ec->cwh = NULL; 259 if (NULL != ec->read_task) 260 { 261 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 read_cb (ec); 265 } 266 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 GNUNET_OS_process_destroy (ec->helper); 272 ec->helper = NULL; 273 if (0 != ec->read_pos) 274 { 275 j = json_loadb (ec->read_buf, 276 ec->read_pos, 277 JSON_REJECT_DUPLICATES, 278 &err); 279 if (NULL == j) 280 { 281 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 282 "Failed to parse JSON from helper at %d: %s\n", 283 err.position, 284 err.text); 285 fprintf (stderr, 286 "%.*s\n", 287 (int) ec->read_pos, 288 (const char *) ec->read_buf); 289 } 290 } 291 ec->cb (ec->cb_cls, 292 type, 293 exit_code, 294 j); 295 json_decref (j); 296 TALER_JSON_external_conversion_stop (ec); 297 } 298 299 300 struct TALER_JSON_ExternalConversion * 301 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 ec = GNUNET_new (struct TALER_JSON_ExternalConversion); 312 ec->cb = cb; 313 ec->cb_cls = cb_cls; 314 pipe_stdin = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ); 315 GNUNET_assert (NULL != pipe_stdin); 316 pipe_stdout = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_WRITE); 317 GNUNET_assert (NULL != pipe_stdout); 318 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 if (NULL == ec->helper) 325 { 326 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 327 "Failed to run conversion helper `%s'\n", 328 binary); 329 GNUNET_break (GNUNET_OK == 330 GNUNET_DISK_pipe_close (pipe_stdin)); 331 GNUNET_break (GNUNET_OK == 332 GNUNET_DISK_pipe_close (pipe_stdout)); 333 GNUNET_free (ec); 334 return NULL; 335 } 336 ec->chld_stdin = 337 GNUNET_DISK_pipe_detach_end (pipe_stdin, 338 GNUNET_DISK_PIPE_END_WRITE); 339 ec->chld_stdout = 340 GNUNET_DISK_pipe_detach_end (pipe_stdout, 341 GNUNET_DISK_PIPE_END_READ); 342 GNUNET_break (GNUNET_OK == 343 GNUNET_DISK_pipe_close (pipe_stdin)); 344 GNUNET_break (GNUNET_OK == 345 GNUNET_DISK_pipe_close (pipe_stdout)); 346 ec->write_buf = json_dumps (input, JSON_COMPACT); 347 ec->write_size = strlen (ec->write_buf); 348 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 = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL, 353 ec->chld_stdout, 354 &read_cb, 355 ec); 356 ec->write_task 357 = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL, 358 ec->chld_stdin, 359 &write_cb, 360 ec); 361 ec->cwh = GNUNET_wait_child (ec->helper, 362 &child_done_cb, 363 ec); 364 return ec; 365 } 366 367 368 void 369 TALER_JSON_external_conversion_stop ( 370 struct TALER_JSON_ExternalConversion *ec) 371 { 372 if (NULL != ec->cwh) 373 { 374 GNUNET_wait_child_cancel (ec->cwh); 375 ec->cwh = NULL; 376 } 377 if (NULL != ec->helper) 378 { 379 GNUNET_break (0 == 380 GNUNET_OS_process_kill (ec->helper, 381 SIGKILL)); 382 GNUNET_OS_process_destroy (ec->helper); 383 } 384 if (NULL != ec->read_task) 385 { 386 GNUNET_SCHEDULER_cancel (ec->read_task); 387 ec->read_task = NULL; 388 } 389 if (NULL != ec->write_task) 390 { 391 GNUNET_SCHEDULER_cancel (ec->write_task); 392 ec->write_task = NULL; 393 } 394 if (NULL != ec->chld_stdin) 395 { 396 GNUNET_break (GNUNET_OK == 397 GNUNET_DISK_file_close (ec->chld_stdin)); 398 ec->chld_stdin = NULL; 399 } 400 if (NULL != ec->chld_stdout) 401 { 402 GNUNET_break (GNUNET_OK == 403 GNUNET_DISK_file_close (ec->chld_stdout)); 404 ec->chld_stdout = NULL; 405 } 406 GNUNET_free (ec->read_buf); 407 free (ec->write_buf); 408 GNUNET_free (ec); 409 }