exchange

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

mhd_typst.c (22955B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2025 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 mhd_typst.c
     18  * @brief MHD utility functions for PDF generation
     19  * @author Christian Grothoff
     20  *
     21  *
     22  */
     23 #include "platform.h"  /* UNNECESSARY? */
     24 #include "taler/taler_util.h"
     25 #include "taler/taler_mhd_lib.h"
     26 #include <microhttpd.h>
     27 
     28 
     29 /**
     30  * Information about a specific typst invocation.
     31  */
     32 struct TypstStage
     33 {
     34   /**
     35    * Name of the FIFO for the typst output.
     36    */
     37   char *filename;
     38 
     39   /**
     40    * Typst context we are part of.
     41    */
     42   struct TALER_MHD_TypstContext *tc;
     43 
     44   /**
     45    * Handle to the typst process.
     46    */
     47   struct GNUNET_Process *proc;
     48 
     49   /**
     50    * Handle to be notified about stage completion.
     51    */
     52   struct GNUNET_ChildWaitHandle *cwh;
     53 
     54 };
     55 
     56 
     57 struct TALER_MHD_TypstContext
     58 {
     59 
     60   /**
     61    * Project data to use for Typst.
     62    */
     63   const struct GNUNET_OS_ProjectData *pd;
     64 
     65   /**
     66    * Directory where we create temporary files (or FIFOs) for the IPC.
     67    */
     68   char *tmpdir;
     69 
     70   /**
     71    * Array of stages producing PDFs to be combined.
     72    */
     73   struct TypstStage *stages;
     74 
     75   /**
     76    * Handle for pdftk combining the various PDFs.
     77    */
     78   struct GNUNET_Process *proc;
     79 
     80   /**
     81    * Handle to wait for @e proc to complete.
     82    */
     83   struct GNUNET_ChildWaitHandle *cwh;
     84 
     85   /**
     86    * Callback to call on the final result.
     87    */
     88   TALER_MHD_TypstResultCallback cb;
     89 
     90   /**
     91    * Closure for @e cb
     92    */
     93   void *cb_cls;
     94 
     95   /**
     96    * Task for async work.
     97    */
     98   struct GNUNET_SCHEDULER_Task *t;
     99 
    100   /**
    101    * Name of the final file created by pdftk.
    102    */
    103   char *output_file;
    104 
    105   /**
    106    * Context for fail_async_cb().
    107    */
    108   char *async_hint;
    109 
    110   /**
    111    * Context for fail_async_cb().
    112    */
    113   enum TALER_ErrorCode async_ec;
    114 
    115   /**
    116    * Length of the @e stages array.
    117    */
    118   unsigned int num_stages;
    119 
    120   /**
    121    * Number of still active stages.
    122    */
    123   unsigned int active_stages;
    124 
    125   /**
    126    * Should the directory be removed when done?
    127    */
    128   bool remove_on_exit;
    129 };
    130 
    131 
    132 void
    133 TALER_MHD_typst_cancel (struct TALER_MHD_TypstContext *tc)
    134 {
    135   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    136               "Cleaning up TypstContext\n");
    137   if (NULL != tc->t)
    138   {
    139     GNUNET_SCHEDULER_cancel (tc->t);
    140     tc->t = NULL;
    141   }
    142   for (unsigned int i = 0; i<tc->num_stages; i++)
    143   {
    144     struct TypstStage *stage = &tc->stages[i];
    145 
    146     if (NULL != stage->cwh)
    147     {
    148       GNUNET_wait_child_cancel (stage->cwh);
    149       stage->cwh = NULL;
    150     }
    151     if (NULL != stage->proc)
    152     {
    153       GNUNET_break (GNUNET_OK ==
    154                     GNUNET_process_kill (stage->proc,
    155                                          SIGKILL));
    156       GNUNET_process_destroy (stage->proc);
    157       stage->proc = NULL;
    158     }
    159     GNUNET_free (stage->filename);
    160   }
    161   GNUNET_free (tc->stages);
    162   if (NULL != tc->cwh)
    163   {
    164     GNUNET_wait_child_cancel (tc->cwh);
    165     tc->cwh = NULL;
    166   }
    167   if (NULL != tc->proc)
    168   {
    169     GNUNET_break (GNUNET_OK ==
    170                   GNUNET_process_kill (tc->proc,
    171                                        SIGKILL));
    172     GNUNET_process_destroy (tc->proc);
    173   }
    174   GNUNET_free (tc->output_file);
    175   if (NULL != tc->tmpdir)
    176   {
    177     if (tc->remove_on_exit)
    178       GNUNET_DISK_directory_remove (tc->tmpdir);
    179     GNUNET_free (tc->tmpdir);
    180   }
    181   GNUNET_free (tc);
    182 }
    183 
    184 
    185 /**
    186  * Create file in @a tmpdir with one of the PDF inputs.
    187  *
    188  * @param[out] stage initialized stage data
    189  * @param tmpdir where to place temporary files
    190  * @param data input JSON with PDF data
    191  * @return true on success
    192  */
    193 static bool
    194 inline_pdf_stage (struct TypstStage *stage,
    195                   const char *tmpdir,
    196                   const json_t *data)
    197 {
    198   const char *str = json_string_value (data);
    199   char *fn;
    200   size_t n;
    201   void *b;
    202   int fd;
    203 
    204   if (NULL == str)
    205   {
    206     GNUNET_break (0);
    207     return false;
    208   }
    209   b = NULL;
    210   n = GNUNET_STRINGS_base64_decode (str,
    211                                     strlen (str),
    212                                     &b);
    213   if (NULL == b)
    214   {
    215     GNUNET_break (0);
    216     return false;
    217   }
    218   GNUNET_asprintf (&fn,
    219                    "%s/external-",
    220                    tmpdir);
    221   stage->filename = GNUNET_DISK_mktemp (fn);
    222   if (NULL == stage->filename)
    223   {
    224     GNUNET_break (0);
    225     GNUNET_free (b);
    226     GNUNET_free (fn);
    227     return false;
    228   }
    229   GNUNET_free (fn);
    230   fd = open (stage->filename,
    231              O_WRONLY | O_TRUNC,
    232              S_IRUSR | S_IWUSR);
    233   if (-1 == fd)
    234   {
    235     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
    236                               "open",
    237                               stage->filename);
    238     GNUNET_free (b);
    239     GNUNET_free (stage->filename);
    240     return false;
    241   }
    242 
    243   {
    244     size_t off = 0;
    245 
    246     while (off < n)
    247     {
    248       ssize_t r;
    249 
    250       r = write (fd,
    251                  b + off,
    252                  n - off);
    253       if (-1 == r)
    254       {
    255         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
    256                                   "write",
    257                                   stage->filename);
    258         GNUNET_break (0 == close (fd));
    259         GNUNET_free (b);
    260         GNUNET_free (stage->filename);
    261         return false;
    262       }
    263       off += r;
    264     }
    265   }
    266   GNUNET_break (0 == close (fd));
    267   GNUNET_free (b);
    268   return true;
    269 }
    270 
    271 
    272 /**
    273  * Generate a response for @a tc indicating an error of type @a ec.
    274  *
    275  * @param[in,out] tc context to fail
    276  * @param ec error code to return
    277  * @param hint hint text to return
    278  */
    279 static void
    280 typst_context_fail (struct TALER_MHD_TypstContext *tc,
    281                     enum TALER_ErrorCode ec,
    282                     const char *hint)
    283 {
    284   struct TALER_MHD_TypstResponse resp = {
    285     .ec = ec,
    286     .details.hint = hint
    287   };
    288 
    289   if (NULL != tc->cb)
    290   {
    291     tc->cb (tc->cb_cls,
    292             &resp);
    293     tc->cb = NULL;
    294   }
    295 }
    296 
    297 
    298 /**
    299  * Helper task for typst_context_fail_async().
    300  *
    301  * @param cls a `struct TALER_MHD_TypstContext`
    302  */
    303 static void
    304 fail_async_cb (void *cls)
    305 {
    306   struct TALER_MHD_TypstContext *tc = cls;
    307 
    308   tc->t = NULL;
    309   typst_context_fail (tc,
    310                       tc->async_ec,
    311                       tc->async_hint);
    312   GNUNET_free (tc->async_hint);
    313   TALER_MHD_typst_cancel (tc);
    314 }
    315 
    316 
    317 /**
    318  * Generate a response for @a tc indicating an error of type @a ec.
    319  *
    320  * @param[in,out] tc context to fail
    321  * @param ec error code to return
    322  * @param hint hint text to return
    323  */
    324 static void
    325 typst_context_fail_async (struct TALER_MHD_TypstContext *tc,
    326                           enum TALER_ErrorCode ec,
    327                           const char *hint)
    328 {
    329   tc->async_ec = ec;
    330   tc->async_hint = (NULL == hint) ? NULL : GNUNET_strdup (hint);
    331   tc->t = GNUNET_SCHEDULER_add_now (&fail_async_cb,
    332                                     tc);
    333 }
    334 
    335 
    336 /**
    337  * Called when the pdftk helper exited.
    338  *
    339  * @param cls our `struct TALER_MHD_TypstContext *`
    340  * @param type type of the process
    341  * @param exit_code status code of the process
    342  */
    343 static void
    344 pdftk_done_cb (void *cls,
    345                enum GNUNET_OS_ProcessStatusType type,
    346                long unsigned int exit_code)
    347 {
    348   struct TALER_MHD_TypstContext *tc = cls;
    349 
    350   tc->cwh = NULL;
    351   GNUNET_process_destroy (tc->proc);
    352   tc->proc = NULL;
    353   switch (type)
    354   {
    355   case GNUNET_OS_PROCESS_UNKNOWN:
    356     GNUNET_assert (0);
    357     return;
    358   case GNUNET_OS_PROCESS_RUNNING:
    359     /* we should not get this notification */
    360     GNUNET_break (0);
    361     return;
    362   case GNUNET_OS_PROCESS_STOPPED:
    363     /* Someone is SIGSTOPing our helper!? */
    364     GNUNET_break (0);
    365     return;
    366   case GNUNET_OS_PROCESS_EXITED:
    367     if (0 != exit_code)
    368     {
    369       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    370                   "pdftk exited with status %d\n",
    371                   (int) exit_code);
    372       typst_context_fail (tc,
    373                           TALER_EC_EXCHANGE_GENERIC_PDFTK_FAILURE,
    374                           "pdftk failed");
    375     }
    376     else
    377     {
    378       struct TALER_MHD_TypstResponse resp = {
    379         .ec = TALER_EC_NONE,
    380         .details.filename = tc->output_file,
    381       };
    382 
    383       GNUNET_assert (NULL != tc->cb);
    384       tc->cb (tc->cb_cls,
    385               &resp);
    386       tc->cb = NULL;
    387     }
    388     break;
    389   case GNUNET_OS_PROCESS_SIGNALED:
    390     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    391                 "pdftk died with signal %d\n",
    392                 (int) exit_code);
    393     typst_context_fail (tc,
    394                         TALER_EC_EXCHANGE_GENERIC_PDFTK_CRASH,
    395                         "pdftk killed by signal");
    396     break;
    397   }
    398   TALER_MHD_typst_cancel (tc);
    399 }
    400 
    401 
    402 /**
    403  * Function called once all of the individual stages are done.
    404  * Triggers the pdftk run for @a tc.
    405  *
    406  * @param[in,out] cls a `struct TALER_MHD_TypstContext *` context to run pdftk for
    407  */
    408 static void
    409 complete_response (void *cls)
    410 {
    411   struct TALER_MHD_TypstContext *tc = cls;
    412   const char *argv[tc->num_stages + 5];
    413 
    414   tc->t = NULL;
    415   argv[0] = "pdftk";
    416   for (unsigned int i = 0; i<tc->num_stages; i++)
    417     argv[i + 1] = tc->stages[i].filename;
    418   argv[tc->num_stages + 1] = "cat";
    419   argv[tc->num_stages + 2] = "output";
    420   argv[tc->num_stages + 3] = tc->output_file;
    421   argv[tc->num_stages + 4] = NULL;
    422   tc->proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
    423   if (GNUNET_OK !=
    424       GNUNET_process_run_command_argv (tc->proc,
    425                                        argv[0],
    426                                        argv))
    427   {
    428     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
    429                          "fork");
    430     GNUNET_process_destroy (tc->proc);
    431     tc->proc = NULL;
    432     TALER_MHD_typst_cancel (tc);
    433     return;
    434   }
    435   tc->cwh = GNUNET_wait_child (tc->proc,
    436                                &pdftk_done_cb,
    437                                tc);
    438   GNUNET_assert (NULL != tc->cwh);
    439 }
    440 
    441 
    442 /**
    443  * Cancel typst. Wrapper task to do so asynchronously.
    444  *
    445  * @param[in] cls a `struct TALER_MHD_TypstContext`
    446  */
    447 static void
    448 cancel_async (void *cls)
    449 {
    450   struct TALER_MHD_TypstContext *tc = cls;
    451 
    452   tc->t = NULL;
    453   TALER_MHD_typst_cancel (tc);
    454 }
    455 
    456 
    457 /**
    458  * Called when a typst helper exited.
    459  *
    460  * @param cls our `struct TypstStage *`
    461  * @param type type of the process
    462  * @param exit_code status code of the process
    463  */
    464 static void
    465 typst_done_cb (void *cls,
    466                enum GNUNET_OS_ProcessStatusType type,
    467                long unsigned int exit_code)
    468 {
    469   struct TypstStage *stage = cls;
    470   struct TALER_MHD_TypstContext *tc = stage->tc;
    471 
    472   stage->cwh = NULL;
    473   GNUNET_process_destroy (stage->proc);
    474   stage->proc = NULL;
    475   switch (type)
    476   {
    477   case GNUNET_OS_PROCESS_UNKNOWN:
    478     GNUNET_assert (0);
    479     return;
    480   case GNUNET_OS_PROCESS_RUNNING:
    481     /* we should not get this notification */
    482     GNUNET_break (0);
    483     return;
    484   case GNUNET_OS_PROCESS_STOPPED:
    485     /* Someone is SIGSTOPing our helper!? */
    486     GNUNET_break (0);
    487     return;
    488   case GNUNET_OS_PROCESS_EXITED:
    489     if (0 != exit_code)
    490     {
    491       char err[128];
    492 
    493       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    494                   "typst exited with status %d\n",
    495                   (int) exit_code);
    496       GNUNET_snprintf (err,
    497                        sizeof (err),
    498                        "Typst exited with status %d",
    499                        (int) exit_code);
    500       typst_context_fail (tc,
    501                           TALER_EC_EXCHANGE_GENERIC_TYPST_TEMPLATE_FAILURE,
    502                           err);
    503       GNUNET_assert (NULL == tc->t);
    504       tc->t = GNUNET_SCHEDULER_add_now (&cancel_async,
    505                                         tc);
    506       return;
    507     }
    508     break;
    509   case GNUNET_OS_PROCESS_SIGNALED:
    510     {
    511       char err[128];
    512 
    513       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    514                   "typst died with signal %d\n",
    515                   (int) exit_code);
    516       GNUNET_snprintf (err,
    517                        sizeof (err),
    518                        "Typst died with signal %d",
    519                        (int) exit_code);
    520       typst_context_fail (tc,
    521                           TALER_EC_EXCHANGE_GENERIC_TYPST_CRASH,
    522                           err);
    523       GNUNET_assert (NULL == tc->t);
    524       tc->t = GNUNET_SCHEDULER_add_now (&cancel_async,
    525                                         tc);
    526       return;
    527     }
    528     break;
    529   }
    530   tc->active_stages--;
    531   if (NULL != stage->proc)
    532   {
    533     GNUNET_process_destroy (stage->proc);
    534     stage->proc = NULL;
    535   }
    536   if (0 != tc->active_stages)
    537     return;
    538   GNUNET_assert (NULL == tc->t);
    539   tc->t = GNUNET_SCHEDULER_add_now (&complete_response,
    540                                     tc);
    541 }
    542 
    543 
    544 /**
    545  * Setup typst stage to produce one of the PDF inputs.
    546  *
    547  * @param[out] stage initialized stage data
    548  * @param i index of the stage
    549  * @param tmpdir where to place temporary files
    550  * @param template_path where to find templates
    551  * @param doc input document specification
    552  * @return true on success
    553  */
    554 static bool
    555 setup_stage (struct TypstStage *stage,
    556              unsigned int i,
    557              const char *tmpdir,
    558              const char *template_path,
    559              const struct TALER_MHD_TypstDocument *doc)
    560 {
    561   struct TALER_MHD_TypstContext *tc = stage->tc;
    562   char *input;
    563   bool is_dot_typ;
    564 
    565   if (NULL == doc->form_name)
    566   {
    567     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    568                 "Stage %u: Dumping inlined PDF attachment\n",
    569                 i);
    570     return inline_pdf_stage (stage,
    571                              tmpdir,
    572                              doc->data);
    573   }
    574 
    575   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    576               "Stage %u: Handling form %s\n",
    577               i,
    578               doc->form_name);
    579 
    580   /* Setup inputs */
    581   {
    582     char *dirname;
    583 
    584     GNUNET_asprintf (&dirname,
    585                      "%s/%u/",
    586                      tmpdir,
    587                      i);
    588     if (GNUNET_OK !=
    589         GNUNET_DISK_directory_create (dirname))
    590     {
    591       GNUNET_free (dirname);
    592       return false;
    593     }
    594     GNUNET_free (dirname);
    595   }
    596 
    597   /* Setup data input */
    598   {
    599     char *jfn;
    600 
    601     GNUNET_asprintf (&jfn,
    602                      "%s/%u/input.json",
    603                      tmpdir,
    604                      i);
    605     if (0 !=
    606         json_dump_file (doc->data,
    607                         jfn,
    608                         JSON_INDENT (2)))
    609     {
    610       GNUNET_break (0);
    611       GNUNET_free (jfn);
    612       return false;
    613     }
    614     GNUNET_free (jfn);
    615   }
    616 
    617   /* setup output file name */
    618   GNUNET_asprintf (&stage->filename,
    619                    "%s/%u/input.pdf",
    620                    tmpdir,
    621                    i);
    622 
    623   /* setup main input Typst file */
    624   {
    625     char *intyp;
    626     size_t slen = strlen (doc->form_name);
    627 
    628     is_dot_typ = ( (slen > 4) &&
    629                    (0 == memcmp (&doc->form_name[slen - 4],
    630                                  ".typ",
    631                                  4)) );
    632     /* We do not append the ":$VERSION" if a filename ending with ".typ"
    633        is given. Otherwise we append the version, or ":0.0.0" if no
    634        explicit version is given. */
    635     GNUNET_asprintf (&intyp,
    636                      "#import \"%s/%s%s%s\": form\n"
    637                      "#form(json(\"input.json\"))\n",
    638                      template_path,
    639                      doc->form_name,
    640                      is_dot_typ ? "" : ":",
    641                      is_dot_typ
    642                      ? ""
    643                      : ( (NULL == doc->form_version)
    644                          ? "0.0.0"
    645                          : doc->form_version));
    646     GNUNET_asprintf (&input,
    647                      "%s/%u/input.typ",
    648                      tmpdir,
    649                      i);
    650     if (GNUNET_OK !=
    651         GNUNET_DISK_fn_write (input,
    652                               intyp,
    653                               strlen (intyp),
    654                               GNUNET_DISK_PERM_USER_READ))
    655     {
    656       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
    657                                 "write",
    658                                 input);
    659       GNUNET_free (input);
    660       GNUNET_free (intyp);
    661       return false;
    662     }
    663     GNUNET_free (intyp);
    664   }
    665 
    666   /* now setup typst invocation */
    667   {
    668     const char *argv[6];
    669 
    670     if (is_dot_typ)
    671     {
    672       /* This deliberately breaks the typst sandbox. Why? Because Typst sucks
    673          and does not support multiple roots, but here we have dynamic data in
    674          /tmp and a style file outside of /tmp (and copying is also not
    675          practical as we don't know what all to copy). Typst should really
    676          support multiple roots. Anyway, in production this path should not
    677          happen, because there we use Typst packages. */
    678       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    679                   "Bypassing Typst sandbox. You should use Typst packages instead of `%s'.\n",
    680                   doc->form_name);
    681       argv[0] = "typst";
    682       argv[1] = "compile";
    683       argv[2] = "--root";
    684       argv[3] = "/";
    685       argv[4] = input;
    686       argv[5] = NULL;
    687     }
    688     else
    689     {
    690       argv[0] = "typst";
    691       argv[1] = "compile";
    692       argv[2] = input;
    693       argv[3] = NULL;
    694     }
    695     stage->proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
    696     {
    697       char *datadir;
    698 
    699       datadir = GNUNET_OS_installation_get_path (
    700         tc->pd,
    701         GNUNET_OS_IPK_DATADIR);
    702       GNUNET_assert (GNUNET_OK ==
    703                      GNUNET_process_set_options (
    704                        stage->proc,
    705                        GNUNET_process_option_set_environment ("XDG_DATA_HOME",
    706                                                               datadir)));
    707       GNUNET_free (datadir);
    708     }
    709     if (GNUNET_OK !=
    710         GNUNET_process_run_command_argv (stage->proc,
    711                                          "typst",
    712                                          argv))
    713     {
    714       GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
    715                            "fork");
    716       GNUNET_free (input);
    717       GNUNET_process_destroy (stage->proc);
    718       stage->proc = NULL;
    719       return false;
    720     }
    721     GNUNET_free (input);
    722     tc->active_stages++;
    723     stage->cwh = GNUNET_wait_child (stage->proc,
    724                                     &typst_done_cb,
    725                                     stage);
    726     GNUNET_assert (NULL != stage->cwh);
    727   }
    728   return true;
    729 }
    730 
    731 
    732 struct TALER_MHD_TypstContext *
    733 TALER_MHD_typst (
    734   const struct GNUNET_OS_ProjectData *pd,
    735   const struct GNUNET_CONFIGURATION_Handle *cfg,
    736   bool remove_on_exit,
    737   const char *cfg_section_name,
    738   unsigned int num_documents,
    739   const struct TALER_MHD_TypstDocument docs[static num_documents],
    740   TALER_MHD_TypstResultCallback cb,
    741   void *cb_cls)
    742 {
    743   static enum GNUNET_GenericReturnValue once = GNUNET_NO;
    744   struct TALER_MHD_TypstContext *tc;
    745 
    746   switch (once)
    747   {
    748   case GNUNET_OK:
    749     break;
    750   case GNUNET_NO:
    751     if (GNUNET_SYSERR ==
    752         GNUNET_OS_check_helper_binary ("typst",
    753                                        false,
    754                                        NULL))
    755     {
    756       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    757                   "`typst' command not found\n");
    758       once = GNUNET_SYSERR;
    759       return NULL;
    760     }
    761     if (GNUNET_SYSERR ==
    762         GNUNET_OS_check_helper_binary ("pdftk",
    763                                        false,
    764                                        NULL))
    765     {
    766       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    767                   "`pdftk' command not found\n");
    768       once = GNUNET_SYSERR;
    769       return NULL;
    770     }
    771     once = GNUNET_OK;
    772     break;
    773   case GNUNET_SYSERR:
    774     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    775                 "PDF generation initialization failed before, not even trying again\n");
    776     return NULL;
    777   }
    778   tc = GNUNET_new (struct TALER_MHD_TypstContext);
    779   tc->pd = pd;
    780   tc->tmpdir = GNUNET_strdup ("/tmp/taler-typst-XXXXXX");
    781   tc->remove_on_exit = remove_on_exit;
    782   tc->cb = cb;
    783   tc->cb_cls = cb_cls;
    784   if (NULL == mkdtemp (tc->tmpdir))
    785   {
    786     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
    787                               "mkdtemp",
    788                               tc->tmpdir);
    789     GNUNET_free (tc->tmpdir);
    790     TALER_MHD_typst_cancel (tc);
    791     return NULL;
    792   }
    793   GNUNET_asprintf (&tc->output_file,
    794                    "%s/final.pdf",
    795                    tc->tmpdir);
    796 
    797   /* setup typst stages */
    798   {
    799     char *template_path;
    800 
    801     if (GNUNET_OK !=
    802         GNUNET_CONFIGURATION_get_value_string (cfg,
    803                                                cfg_section_name,
    804                                                "TYPST_TEMPLATES",
    805                                                &template_path))
    806     {
    807       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    808                                  cfg_section_name,
    809                                  "TYPST_TEMPLATES");
    810       TALER_MHD_typst_cancel (tc);
    811       return NULL;
    812     }
    813     if ('@' != template_path[0])
    814       template_path = GNUNET_CONFIGURATION_expand_dollar (cfg,
    815                                                           template_path);
    816     tc->stages = GNUNET_new_array (num_documents,
    817                                    struct TypstStage);
    818     tc->num_stages = num_documents;
    819     for (unsigned int i = 0; i<num_documents; i++)
    820     {
    821       tc->stages[i].tc = tc;
    822       if (! setup_stage (&tc->stages[i],
    823                          i,
    824                          tc->tmpdir,
    825                          template_path,
    826                          &docs[i]))
    827       {
    828         char err[128];
    829 
    830         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    831                     "Typst setup failed on stage %u\n",
    832                     i);
    833         GNUNET_snprintf (err,
    834                          sizeof (err),
    835                          "Typst setup failed on stage %u",
    836                          i);
    837         typst_context_fail_async (tc,
    838                                   TALER_EC_EXCHANGE_GENERIC_TYPST_TEMPLATE_FAILURE,
    839                                   err);
    840         return tc;
    841       }
    842     }
    843     GNUNET_free (template_path);
    844   }
    845   if (0 == tc->active_stages)
    846   {
    847     tc->t = GNUNET_SCHEDULER_add_now (&complete_response,
    848                                       tc);
    849   }
    850   return tc;
    851 }
    852 
    853 
    854 struct MHD_Response *
    855 TALER_MHD_response_from_pdf_file (const char *filename)
    856 {
    857   struct MHD_Response *resp;
    858   struct stat s;
    859   int fd;
    860 
    861   fd = open (filename,
    862              O_RDONLY);
    863   if (-1 == fd)
    864   {
    865     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
    866                               "open",
    867                               filename);
    868     return NULL;
    869   }
    870   if (0 !=
    871       fstat (fd,
    872              &s))
    873   {
    874     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
    875                               "fstat",
    876                               filename);
    877     GNUNET_assert (0 == close (fd));
    878     return NULL;
    879   }
    880   resp = MHD_create_response_from_fd (s.st_size,
    881                                       fd);
    882   TALER_MHD_add_global_headers (resp,
    883                                 false);
    884   GNUNET_break (MHD_YES ==
    885                 MHD_add_response_header (resp,
    886                                          MHD_HTTP_HEADER_CONTENT_TYPE,
    887                                          "application/pdf"));
    888   return resp;
    889 }