LCOV - code coverage report
Current view: top level - json - conversion.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 68.3 % 145 99
Test Date: 2026-09-09 15:11:34 Functions: 100.0 % 5 5

            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 : }
        

Generated by: LCOV version 2.0-1