exchange

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

conversion.c (11349B)


      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_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_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_process_create (GNUNET_OS_INHERIT_STD_ERR);
    319   GNUNET_assert (GNUNET_OK ==
    320                  GNUNET_process_set_options (
    321                    ec->helper,
    322                    GNUNET_process_option_inherit_rpipe (pipe_stdin,
    323                                                         STDIN_FILENO),
    324                    GNUNET_process_option_inherit_wpipe (pipe_stdout,
    325                                                         STDOUT_FILENO)));
    326   if (GNUNET_OK !=
    327       GNUNET_process_run_command_argv (ec->helper,
    328                                        binary,
    329                                        argv))
    330   {
    331     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    332                 "Failed to run conversion helper `%s'\n",
    333                 binary);
    334     GNUNET_break (GNUNET_OK ==
    335                   GNUNET_DISK_pipe_close (pipe_stdin));
    336     GNUNET_break (GNUNET_OK ==
    337                   GNUNET_DISK_pipe_close (pipe_stdout));
    338     GNUNET_process_destroy (ec->helper);
    339     GNUNET_free (ec);
    340     return NULL;
    341   }
    342   ec->chld_stdin =
    343     GNUNET_DISK_pipe_detach_end (pipe_stdin,
    344                                  GNUNET_DISK_PIPE_END_WRITE);
    345   ec->chld_stdout =
    346     GNUNET_DISK_pipe_detach_end (pipe_stdout,
    347                                  GNUNET_DISK_PIPE_END_READ);
    348   GNUNET_break (GNUNET_OK ==
    349                 GNUNET_DISK_pipe_close (pipe_stdin));
    350   GNUNET_break (GNUNET_OK ==
    351                 GNUNET_DISK_pipe_close (pipe_stdout));
    352   ec->write_buf = json_dumps (input,
    353                               JSON_COMPACT);
    354   ec->write_size = strlen (ec->write_buf);
    355   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    356               "Passing %llu bytes to JSON conversion tool\n",
    357               (unsigned long long) ec->write_size);
    358   ec->read_task
    359     = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
    360                                       ec->chld_stdout,
    361                                       &read_cb,
    362                                       ec);
    363   ec->write_task
    364     = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
    365                                        ec->chld_stdin,
    366                                        &write_cb,
    367                                        ec);
    368   ec->cwh = GNUNET_wait_child (ec->helper,
    369                                &child_done_cb,
    370                                ec);
    371   return ec;
    372 }
    373 
    374 
    375 void
    376 TALER_JSON_external_conversion_stop (
    377   struct TALER_JSON_ExternalConversion *ec)
    378 {
    379   if (NULL != ec->cwh)
    380   {
    381     GNUNET_wait_child_cancel (ec->cwh);
    382     ec->cwh = NULL;
    383   }
    384   if (NULL != ec->helper)
    385   {
    386     GNUNET_break (GNUNET_OK ==
    387                   GNUNET_process_kill (ec->helper,
    388                                        SIGKILL));
    389     GNUNET_process_destroy (ec->helper);
    390     ec->helper = NULL;
    391   }
    392   if (NULL != ec->read_task)
    393   {
    394     GNUNET_SCHEDULER_cancel (ec->read_task);
    395     ec->read_task = NULL;
    396   }
    397   if (NULL != ec->write_task)
    398   {
    399     GNUNET_SCHEDULER_cancel (ec->write_task);
    400     ec->write_task = NULL;
    401   }
    402   if (NULL != ec->chld_stdin)
    403   {
    404     GNUNET_break (GNUNET_OK ==
    405                   GNUNET_DISK_file_close (ec->chld_stdin));
    406     ec->chld_stdin = NULL;
    407   }
    408   if (NULL != ec->chld_stdout)
    409   {
    410     GNUNET_break (GNUNET_OK ==
    411                   GNUNET_DISK_file_close (ec->chld_stdout));
    412     ec->chld_stdout = NULL;
    413   }
    414   GNUNET_free (ec->read_buf);
    415   free (ec->write_buf);
    416   GNUNET_free (ec);
    417 }