LCOV - code coverage report
Current view: top level - json - conversion.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 68.6 % 140 96
Test Date: 2025-12-26 23:00: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 "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           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 : }
        

Generated by: LCOV version 2.0-1