exchange

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

conversion.c (11425B)


      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_stdin));
    134         ec->chld_stdin = 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_stdin));
    145         ec->chld_stdin = 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       fprintf (stderr,
    288                "%.*s\n",
    289                (int) ec->read_pos,
    290                (const char *) ec->read_buf);
    291     }
    292   }
    293   ec->cb (ec->cb_cls,
    294           type,
    295           exit_code,
    296           j);
    297   json_decref (j);
    298   TALER_JSON_external_conversion_stop (ec);
    299 }
    300 
    301 
    302 struct TALER_JSON_ExternalConversion *
    303 TALER_JSON_external_conversion_start (const json_t *input,
    304                                       TALER_JSON_JsonCallback cb,
    305                                       void *cb_cls,
    306                                       const char *binary,
    307                                       const char **argv)
    308 {
    309   struct TALER_JSON_ExternalConversion *ec;
    310   struct GNUNET_DISK_PipeHandle *pipe_stdin;
    311   struct GNUNET_DISK_PipeHandle *pipe_stdout;
    312 
    313   ec = GNUNET_new (struct TALER_JSON_ExternalConversion);
    314   ec->cb = cb;
    315   ec->cb_cls = cb_cls;
    316   pipe_stdin = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ);
    317   GNUNET_assert (NULL != pipe_stdin);
    318   pipe_stdout = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_WRITE);
    319   GNUNET_assert (NULL != pipe_stdout);
    320   ec->helper = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
    321   GNUNET_assert (GNUNET_OK ==
    322                  GNUNET_process_set_options (
    323                    ec->helper,
    324                    GNUNET_process_option_inherit_rpipe (pipe_stdin,
    325                                                         STDIN_FILENO),
    326                    GNUNET_process_option_inherit_wpipe (pipe_stdout,
    327                                                         STDOUT_FILENO)));
    328   if (GNUNET_OK !=
    329       GNUNET_process_run_command_argv (ec->helper,
    330                                        binary,
    331                                        argv))
    332   {
    333     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    334                 "Failed to run conversion helper `%s'\n",
    335                 binary);
    336     GNUNET_break (GNUNET_OK ==
    337                   GNUNET_DISK_pipe_close (pipe_stdin));
    338     GNUNET_break (GNUNET_OK ==
    339                   GNUNET_DISK_pipe_close (pipe_stdout));
    340     GNUNET_process_destroy (ec->helper);
    341     GNUNET_free (ec);
    342     return NULL;
    343   }
    344   ec->chld_stdin =
    345     GNUNET_DISK_pipe_detach_end (pipe_stdin,
    346                                  GNUNET_DISK_PIPE_END_WRITE);
    347   ec->chld_stdout =
    348     GNUNET_DISK_pipe_detach_end (pipe_stdout,
    349                                  GNUNET_DISK_PIPE_END_READ);
    350   GNUNET_break (GNUNET_OK ==
    351                 GNUNET_DISK_pipe_close (pipe_stdin));
    352   GNUNET_break (GNUNET_OK ==
    353                 GNUNET_DISK_pipe_close (pipe_stdout));
    354   ec->write_buf = json_dumps (input,
    355                               JSON_COMPACT);
    356   ec->write_size = strlen (ec->write_buf);
    357   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    358               "Passing %llu bytes to JSON conversion tool\n",
    359               (unsigned long long) ec->write_size);
    360   ec->read_task
    361     = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
    362                                       ec->chld_stdout,
    363                                       &read_cb,
    364                                       ec);
    365   ec->write_task
    366     = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
    367                                        ec->chld_stdin,
    368                                        &write_cb,
    369                                        ec);
    370   ec->cwh = GNUNET_wait_child (ec->helper,
    371                                &child_done_cb,
    372                                ec);
    373   return ec;
    374 }
    375 
    376 
    377 void
    378 TALER_JSON_external_conversion_stop (
    379   struct TALER_JSON_ExternalConversion *ec)
    380 {
    381   if (NULL != ec->cwh)
    382   {
    383     GNUNET_wait_child_cancel (ec->cwh);
    384     ec->cwh = NULL;
    385   }
    386   if (NULL != ec->helper)
    387   {
    388     GNUNET_break (GNUNET_OK ==
    389                   GNUNET_process_kill (ec->helper,
    390                                        SIGKILL));
    391     GNUNET_process_destroy (ec->helper);
    392     ec->helper = NULL;
    393   }
    394   if (NULL != ec->read_task)
    395   {
    396     GNUNET_SCHEDULER_cancel (ec->read_task);
    397     ec->read_task = NULL;
    398   }
    399   if (NULL != ec->write_task)
    400   {
    401     GNUNET_SCHEDULER_cancel (ec->write_task);
    402     ec->write_task = NULL;
    403   }
    404   if (NULL != ec->chld_stdin)
    405   {
    406     GNUNET_break (GNUNET_OK ==
    407                   GNUNET_DISK_file_close (ec->chld_stdin));
    408     ec->chld_stdin = NULL;
    409   }
    410   if (NULL != ec->chld_stdout)
    411   {
    412     GNUNET_break (GNUNET_OK ==
    413                   GNUNET_DISK_file_close (ec->chld_stdout));
    414     ec->chld_stdout = NULL;
    415   }
    416   GNUNET_free (ec->read_buf);
    417   free (ec->write_buf);
    418   GNUNET_free (ec);
    419 }