exchange

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

conversion.c (11023B)


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