exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

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 }