exchange

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

commit 7aac031f34e8e098938e97b5fcbb7b4312f20a06
parent 2305679765fb14b82512b5140ae113d9633109c8
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat, 15 Nov 2025 19:53:57 +0100

more work on PDF generation logic

Diffstat:
Dsrc/mhd/mhd_typist.c | 581-------------------------------------------------------------------------------
Asrc/mhd/mhd_typst.c | 614+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 614 insertions(+), 581 deletions(-)

diff --git a/src/mhd/mhd_typist.c b/src/mhd/mhd_typist.c @@ -1,581 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2025 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file mhd_typist.c - * @brief MHD utility functions for PDF generation - * @author Christian Grothoff - * - * - */ -#include "taler/platform.h" -#include "taler/taler_util.h" -#include "taler/taler_mhd_lib.h" - - -/** - * Information about a document #TALER_MHD_typist() should output. - */ -struct TALER_MHD_TypistDocument -{ - /** - * Form name, used to determine the Typist template to use. - * NULL if @e data is a JSON string with a PDF to inline. - */ - const char *form_name; - - /** - * Form data. - */ - const json_t *data; -}; - - -/** - * Information about a specific typist invocation. - */ -struct TypistStage -{ - /** - * Name of the FIFO for the typist output. - */ - char *filename; - - /** - * Handle to the typist process. - */ - struct GNUNET_OS_Process *proc; - - /** - * Handle to be notified about stage completion. - */ - struct GNUNET_ChildWaitHandle *cwh; - -}; - - -struct TypistContext -{ - - /** - * Kept in a DLL. - */ - struct TypistContext *next; - - /** - * Kept in a DLL. - */ - struct TypistContext *prev; - - /** - * Directory where we create temporary files (or FIFOs) for the IPC. - */ - char *tmpdir; - - /** - * Array of stages producing PDFs to be combined. - */ - struct TypistStage *stages; - - /** - * Handle for pdftk combining the various PDFs. - */ - struct GNUNET_OS_Process *proc; - - /** - * Handle to wait for @e proc to complete. - */ - struct GNUNET_ChildWaitHandle *cwh; - - /** - * Length of the @e stages array. - */ - unsigned int num_stages; - -}; - - -/** - * Active typist context DLL (for #TALER_MHD_typist_stop_all()). - */ -static struct TypistContext *tc_head; - -/** - * Active typist context DLL (for #TALER_MHD_typist_stop_all()). - */ -static struct TypistContext *tc_tail; - - -/** - * Clean up @a cls, releasing all resources associated with it. - * Stops helper processes if they are still running. - * - * @param[in] tc context to clean up - */ -static void -cleanup_tc (struct TypistContext *tc) -{ - GNUNET_CONTAINER_DLL_remove (tc_head, - tc_tail, - tc); - for (unsigned int i = 0; i<tc->num_stages; i++) - { - struct TypistStage *stage = tc->stages[i]; - - if (NULL != stage->cwh) - { - GNUNET_wait_child_cancel (stage->cwh); - stage->cwh = NULL; - } - if (NULL != stage->proc) - { - GNUNET_break (0 == - GNUNET_OS_process_kill (stage->proc, - SIGKILL)); - GNUNET_OS_process_destroy (stage->proc); - } - GNUNET_free (stage->filename); - } - GNUNET_free (tc->stages); - if (NULL != tc->cwh) - { - GNUNET_wait_child_cancel (tc->cwh); - tc->cwh = NULL; - } - if (NULL != tc->proc) - { - GNUNET_break (0 == - GNUNET_OS_process_kill (tc->proc, - SIGKILL)); - GNUNET_OS_process_destroy (tc->proc); - } - if (NULL != tc->tmpdir) - { - GNUNET_DISK_directory_remove (tc->tmpdir); - GNUNET_free (tc->tmpdir); - } - GNUNET_free (tc); -} - - -void -TALER_MHD_typist_stop_all () -{ - while (NULL != tc_head) - cleanup_tc (tc_head); -} - - -/** - * Create file in @a tmpdir with one of the PDF inputs. - * - * @param[out] stage initialized stage data - * @param tmpdir where to place temporary files - * @param data input JSON with PDF data - * @return true on success - */ -static bool -inline_pdf_stage (struct TypistStage *stage, - const char *tmpdir, - const json_t *data) -{ - const char *str = json_string_value (data); - char *fn; - size_t n; - void *b; - - if (NULL == str) - { - GNUNET_break (0); - return false; - } - if (GNUNET_OK != - GNUNET_STRINGS_string_to_data_alloc (str, - strlen (str), - &b, - &n)) - { - GNUNET_break (0); - return false; - } - GNUNET_asprintf (&fn, - "%s/external-XXXXXX", - tmpdir); - stage->filename = GNUNET_DISK_mktemp (fn); - if (NULL == stage->filename) - { - GNUNET_break (0); - GNUNET_free (b); - GNUNET_free (fn); - return false; - } - GNUNET_free (fn); - - if (n != - GNUNET_DISK_fn_write (stage->filename, - b, - n, - GNUNET_DISK_PERM_USER_READ - | GNUNET_DISK_PERM_USER_WRITE)) - { - GNUNET_free (b); - GNUNET_free (stage->filename); - return false; - } - return true; -} - - -/** - * Called when a typist helper exited. - * - * @param cls our `struct TypistStage *` - * @param type type of the process - * @param exit_code status code of the process - */ -static void -typist_done_cb (void *cls, - enum GNUNET_OS_ProcessStatusType type, - long unsigned int exit_code) -{ - struct TypistStage *stage = cls; - - stage->cwh = NULL; - switch (type) - { - case GNUNET_OS_PROCESS_UNKNOWN: - GNUNET_assert (0); - return; - case GNUNET_OS_PROCESS_RUNNING: - /* we should not get this notification */ - GNUNET_break (0); - return; - case GNUNET_OS_PROCESS_STOPPED: - /* Someone is SIGSTOPing our helper!? */ - GNUNET_break (0); - return; - case GNUNET_OS_PROCESS_EXITED: - if (0 != exit_code) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "typist exited with status %d\n", - (int) exit_code); - } - break; - case GNUNET_OS_PROCESS_SIGNALED: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "typist died with signal %d\n", - (int) exit_code); - break; - } - GNUNET_OS_process_destroy (stage->proc); - stage->proc = NULL; -} - - -/** - * Setup typist stage to produce one of the PDF inputs. - * - * @param[out] stage initialized stage data - * @param i index of the stage - * @param tmpdir where to place temporary files - * @param template_path where to find templates - * @param doc input document specification - * @return true on success - */ -static bool -setup_stage (struct TypistStage *stage, - unsigned int i, - const char *tmpdir, - const char *template_path, - const struct TALER_MHD_TypistDocument *doc) -{ - char *template_fn; - - if (NULL == doc->form_name) - { - return inline_pdf_stage (stage, - tmpdir, - doc->data); - } - GNUNET_asprintf (&template_fn, - "%s%s", - template_path, - doc->form_name); - if (GNUNET_YES != - GNUNET_DISK_file_test_read (template_fn)) - { - GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, - "access", - template_fn); - GNUNET_free (template_fn); - return false; - } - - /* Setup fifo */ - GNUNET_asprintf (&stage->filename, - "%s/%u", - tmpdir, - i); - if (0 != - mkfifo (stage->filename, - S_IRUSR | S_IWUSR)) - { - GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, - "mkfifo", - stage->filename); - GNUNET_free (stage->filename); - GNUNET_free (template_fn); - return false; - } - - - /* now setup typist stage */ - { - struct GNUNET_DISK_FileHandle *fh; - char *argv[42]; - - fh = GNUNET_DISK_file_open (stage->filename, - GNUNET_DISK_OPEN_WRITE, - GNUNET_DISK_PERM_NONE); - if (NULL == fh) - { - GNUNET_free (stage->filename); - GNUNET_free (template_fn); - return false; - } - // FIXME: check correct invocation! - argv[0] = "typist"; - argv[1] = template_fn; - argv[2] = "-"; - argv[3] = NULL; - tc->proc = GNUNET_OS_start_process_vap ( - GNUNET_OS_INHERIT_STD_ERR, - NULL, - fh, - NULL, - "typist", - argv); - if (NULL == tc->proc) - { - GNUNET_log_stderror (GNUNET_ERROR_TYPE_ERROR, - "fork"); - GNUNET_DISK_pipe_close (p); - cleanup_tc (tc); - return NULL; - } - stage->cwh = GNUNET_wait_child (tc->proc, - &typist_done_cb, - stage); - GNUNET_assert (NULL != stage->cwh); - } - return true; -} - - -/** - * Called when the pdftk helper exited. - * - * @param cls our `struct TypistContext *` - * @param type type of the process - * @param exit_code status code of the process - */ -static void -pdftk_done_cb (void *cls, - enum GNUNET_OS_ProcessStatusType type, - long unsigned int exit_code) -{ - struct TypistContext *tc = cls; - - tc->cwh = NULL; - switch (type) - { - case GNUNET_OS_PROCESS_UNKNOWN: - GNUNET_assert (0); - return; - case GNUNET_OS_PROCESS_RUNNING: - /* we should not get this notification */ - GNUNET_break (0); - return; - case GNUNET_OS_PROCESS_STOPPED: - /* Someone is SIGSTOPing our helper!? */ - GNUNET_break (0); - return; - case GNUNET_OS_PROCESS_EXITED: - if (0 != exit_code) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "pdftk exited with status %d\n", - (int) exit_code); - } - break; - case GNUNET_OS_PROCESS_SIGNALED: - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "pdftk died with signal %d\n", - (int) exit_code); - break; - } - GNUNET_OS_process_destroy (tc->proc); - tc->proc = NULL; - cleanup_tc (tc); -} - - -struct MHD_Response * -TALER_MHD_typist ( - const struct GNUNET_CONFIGURATION_Handle *cfg, - const char *cfg_section_name, - unsigned int num_documents, - const struct TALER_MHD_TypistDocument docs[static num_documents]) -{ - static enum GNUNET_GenericReturnValue once = GNUNET_NO; - struct TypistContext *tc; - - switch (once) - { - case GNUNET_OK: - break; - case GNUNET_NO: - if (GNUNET_SYSERR == - GNUNET_OS_check_helper_binary ("typist", - false, - NULL)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "`typist' command not found\n"); - once = GNUNET_SYSERR; - return NULL; - } - if (GNUNET_SYSERR == - GNUNET_OS_check_helper_binary ("pdftk", - false, - NULL)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "`pdftk' command not found\n"); - once = GNUNET_SYSERR; - return NULL; - } - once = GNUNET_OK; - break; - case GNUNET_SYSERR: - return NULL; - } - tc = GNUNET_new (struct TypistContext); - GNUNET_CONTAINER_DLL_insert (tc_head, - tc_tail, - tc); - tc->tmpdir = GNUNET_strdup ("/tmp/taler-typist-XXXXXX"); - if (NULL == mkdtemp (tc->tmpdir)) - { - GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, - "mkdtemp", - tc->tmpdir); - GNUNET_free (tc->tmpdir); - cleanup_tc (tc); - return NULL; - } - - /* setup typist stages */ - { - char *template_path; - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_filename (cfg, - cfg_section_name, - "TYPIST_TEMPLATES", - &template_path)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - cfg_section_name, - "TYPIST_TEMPLATES"); - cleanup_tc (tc); - return NULL; - } - tc->stages = GNUNET_new_array (num_documents, - struct TypistStage); - tc->num_stages = num_documents; - for (unsigned int i = 0; i<num_documents; i++) - { - if (! setup_stage (&tc->stages[i], - i, - tc->tmpdir, - template_path, - &docs[i])) - { - cleanup_tc (tc); - return NULL; - } - } - GNUNET_free (template_path); - } - - /* now setup pdftk stage */ - { - struct GNUNET_DISK_PipeHandle *p; - char *argv[tc->num_stages + 5]; - - p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW); - - argv[0] = "pdftk"; - for (unsigned int i = 0; i<tc->num_stages; i++) - argv[i + 1] = tc->stages[i].filename; - argv[tc->num_stages + 1] = "cat"; - argv[tc->num_stages + 2] = "output"; - argv[tc->num_stages + 3] = "-"; - argv[tc->num_stages + 4] = NULL; - tc->proc = GNUNET_OS_start_process_vap ( - GNUNET_OS_INHERIT_STD_ERR, - NULL, - GNUNET_DISK_pipe_handle (p, - GNUNET_DISK_PF_END_WRITE), - NULL, - "pdftk", - argv); - if (NULL == tc->proc) - { - GNUNET_log_stderror (GNUNET_ERROR_TYPE_ERROR, - "fork"); - GNUNET_DISK_pipe_close (p); - cleanup_tc (tc); - return NULL; - } - tc->cwh = GNUNET_wait_child (tc->proc, - &pdftk_done_cb, - tc); - GNUNET_assert (NULL != tc->cwh); - - { - struct MHD_Response *resp; - struct GNUNET_DISK_FileHandle *fh; - int fd; - - fh = GNUNET_DISK_pipe_detach_end (p, - GNUNET_DISK_PF_END_READ); - fd = fh->fd; - GNUNET_free (fh); - resp = MHD_response_from_fd (MHD_SIZE_UNKNOWN, - fd); - TALER_MHD_add_global_headers (resp, - false); - - GNUNET_break (MHD_YES == - MHD_add_response_header (response, - MHD_HTTP_HEADER_CONTENT_TYPE, - "application/pdf")); - return resp; - } - } -} diff --git a/src/mhd/mhd_typst.c b/src/mhd/mhd_typst.c @@ -0,0 +1,614 @@ +/* + This file is part of TALER + Copyright (C) 2025 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file mhd_typst.c + * @brief MHD utility functions for PDF generation + * @author Christian Grothoff + * + * + */ +#include "taler/platform.h" +#include "taler/taler_util.h" +#include "taler/taler_mhd_lib.h" + + +/** + * Context for the entire typst build. + */ +struct TypstContext; + + +/** + * Information about a specific typst invocation. + */ +struct TypstStage +{ + /** + * Name of the FIFO for the typst output. + */ + char *filename; + + /** + * Typst context we are part of. + */ + struct TypstContext *tc; + + /** + * Handle to the typst process. + */ + struct GNUNET_OS_Process *proc; + + /** + * Handle to be notified about stage completion. + */ + struct GNUNET_ChildWaitHandle *cwh; + +}; + + +struct TypstContext +{ + + /** + * Kept in a DLL. + */ + struct TypstContext *next; + + /** + * Kept in a DLL. + */ + struct TypstContext *prev; + + /** + * Directory where we create temporary files (or FIFOs) for the IPC. + */ + char *tmpdir; + + /** + * Array of stages producing PDFs to be combined. + */ + struct TypstStage *stages; + + /** + * Handle for pdftk combining the various PDFs. + */ + struct GNUNET_OS_Process *proc; + + /** + * Handle to wait for @e proc to complete. + */ + struct GNUNET_ChildWaitHandle *cwh; + + /** + * Output pipe of pdftk. + */ + struct GNUNET_DISK_PipeHandle *p; + + /** + * Length of the @e stages array. + */ + unsigned int num_stages; + + /** + * Number of still active stages. + */ + unsigned int active_stages; + +}; + + +/** + * Active typst context DLL (for #TALER_MHD_typst_stop_all()). + */ +static struct TypstContext *tc_head; + +/** + * Active typst context DLL (for #TALER_MHD_typst_stop_all()). + */ +static struct TypstContext *tc_tail; + + +/** + * Clean up @a cls, releasing all resources associated with it. + * Stops helper processes if they are still running. + * + * @param[in] tc context to clean up + */ +static void +cleanup_tc (struct TypstContext *tc) +{ + GNUNET_CONTAINER_DLL_remove (tc_head, + tc_tail, + tc); + for (unsigned int i = 0; i<tc->num_stages; i++) + { + struct TypstStage *stage = tc->stages[i]; + + if (NULL != stage->cwh) + { + GNUNET_wait_child_cancel (stage->cwh); + stage->cwh = NULL; + } + if (NULL != stage->proc) + { + GNUNET_break (0 == + GNUNET_OS_process_kill (stage->proc, + SIGKILL)); + GNUNET_OS_process_destroy (stage->proc); + } + GNUNET_free (stage->filename); + } + GNUNET_free (tc->stages); + if (NULL != tc->cwh) + { + GNUNET_wait_child_cancel (tc->cwh); + tc->cwh = NULL; + } + if (NULL != tc->proc) + { + GNUNET_break (0 == + GNUNET_OS_process_kill (tc->proc, + SIGKILL)); + GNUNET_OS_process_destroy (tc->proc); + } + if (NULL != tc->tmpdir) + { + GNUNET_DISK_directory_remove (tc->tmpdir); + GNUNET_free (tc->tmpdir); + } + GNUNET_free (tc); +} + + +void +TALER_MHD_typst_stop_all () +{ + while (NULL != tc_head) + cleanup_tc (tc_head); +} + + +/** + * Create file in @a tmpdir with one of the PDF inputs. + * + * @param[out] stage initialized stage data + * @param tmpdir where to place temporary files + * @param data input JSON with PDF data + * @return true on success + */ +static bool +inline_pdf_stage (struct TypstStage *stage, + const char *tmpdir, + const json_t *data) +{ + const char *str = json_string_value (data); + char *fn; + size_t n; + void *b; + + if (NULL == str) + { + GNUNET_break (0); + return false; + } + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data_alloc (str, + strlen (str), + &b, + &n)) + { + GNUNET_break (0); + return false; + } + GNUNET_asprintf (&fn, + "%s/external-XXXXXX", + tmpdir); + stage->filename = GNUNET_DISK_mktemp (fn); + if (NULL == stage->filename) + { + GNUNET_break (0); + GNUNET_free (b); + GNUNET_free (fn); + return false; + } + GNUNET_free (fn); + + if (n != + GNUNET_DISK_fn_write (stage->filename, + b, + n, + GNUNET_DISK_PERM_USER_READ + | GNUNET_DISK_PERM_USER_WRITE)) + { + GNUNET_free (b); + GNUNET_free (stage->filename); + return false; + } + return true; +} + + +/** + * Called when the pdftk helper exited. + * + * @param cls our `struct TypstContext *` + * @param type type of the process + * @param exit_code status code of the process + */ +static void +pdftk_done_cb (void *cls, + enum GNUNET_OS_ProcessStatusType type, + long unsigned int exit_code) +{ + struct TypstContext *tc = cls; + + tc->cwh = NULL; + switch (type) + { + case GNUNET_OS_PROCESS_UNKNOWN: + GNUNET_assert (0); + return; + case GNUNET_OS_PROCESS_RUNNING: + /* we should not get this notification */ + GNUNET_break (0); + return; + case GNUNET_OS_PROCESS_STOPPED: + /* Someone is SIGSTOPing our helper!? */ + GNUNET_break (0); + return; + case GNUNET_OS_PROCESS_EXITED: + if (0 != exit_code) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "pdftk exited with status %d\n", + (int) exit_code); + } + break; + case GNUNET_OS_PROCESS_SIGNALED: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "pdftk died with signal %d\n", + (int) exit_code); + break; + } + GNUNET_OS_process_destroy (tc->proc); + tc->proc = NULL; + cleanup_tc (tc); +} + + +/** + * Function called once all of the individual stages are done. + * Triggers the pdftk run for @a tc. + * + * @param[in,out] tc typst context to run pdftk for + */ +static void +complete_response (struct TypstContext *tc) +{ + char *argv[tc->num_stages + 5]; + + argv[0] = "pdftk"; + for (unsigned int i = 0; i<tc->num_stages; i++) + argv[i + 1] = tc->stages[i].filename; + argv[tc->num_stages + 1] = "cat"; + argv[tc->num_stages + 2] = "output"; + argv[tc->num_stages + 3] = "-"; + argv[tc->num_stages + 4] = NULL; + tc->proc = GNUNET_OS_start_process_vap ( + GNUNET_OS_INHERIT_STD_ERR, + NULL, + GNUNET_DISK_pipe_handle (p, + GNUNET_DISK_PF_END_WRITE), + NULL, + "pdftk", + argv); + if (NULL == tc->proc) + { + GNUNET_log_stderror (GNUNET_ERROR_TYPE_ERROR, + "fork"); + GNUNET_DISK_pipe_close (p); + cleanup_tc (tc); + return NULL; + } + tc->cwh = GNUNET_wait_child (tc->proc, + &pdftk_done_cb, + tc); + GNUNET_assert (NULL != tc->cwh); +} + + +/** + * Called when a typst helper exited. + * + * @param cls our `struct TypstStage *` + * @param type type of the process + * @param exit_code status code of the process + */ +static void +typst_done_cb (void *cls, + enum GNUNET_OS_ProcessStatusType type, + long unsigned int exit_code) +{ + struct TypstStage *stage = cls; + + switch (type) + { + case GNUNET_OS_PROCESS_UNKNOWN: + GNUNET_assert (0); + return; + case GNUNET_OS_PROCESS_RUNNING: + /* we should not get this notification */ + GNUNET_break (0); + return; + case GNUNET_OS_PROCESS_STOPPED: + /* Someone is SIGSTOPing our helper!? */ + GNUNET_break (0); + return; + case GNUNET_OS_PROCESS_EXITED: + if (0 != exit_code) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "typst exited with status %d\n", + (int) exit_code); + } + break; + case GNUNET_OS_PROCESS_SIGNALED: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "typst died with signal %d\n", + (int) exit_code); + break; + } + stage->cwh = NULL; + stage->tc->active_stages--; + GNUNET_OS_process_destroy (stage->proc); + stage->proc = NULL; + if (0 != stage->tc->active_stages) + return; + complete_response (stage->tc); +} + + +/** + * Setup typst stage to produce one of the PDF inputs. + * + * @param[out] stage initialized stage data + * @param i index of the stage + * @param tmpdir where to place temporary files + * @param template_path where to find templates + * @param doc input document specification + * @return true on success + */ +static bool +setup_stage (struct TypstStage *stage, + unsigned int i, + const char *tmpdir, + const char *template_path, + const struct TALER_MHD_TypstDocument *doc) +{ + char *template_fn; + char *input; + + if (NULL == doc->form_name) + { + return inline_pdf_stage (stage, + tmpdir, + doc->data); + } + GNUNET_asprintf (&template_fn, + "%s%s", + template_path, + doc->form_name); + if (GNUNET_YES != + GNUNET_DISK_file_test_read (template_fn)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "access", + template_fn); + GNUNET_free (template_fn); + return false; + } + + /* Setup inputs */ + { + char *dirname; + + GNUNET_asprintf (&dirname, + "%s/%u/", + tmpdir, + i); + if (GNUNET_OK != + GNUNET_DISK_directory_create (dirname)) + { + GNUNET_free (template_fn); + GNUNET_free (dirname); + return false; + } + GNUNET_free (dirname); + } + GNUNET_asprintf (&input, + "%s/%u/input.typ", + tmpdir, + i); + GNUNET_asprintf (&stage->filename, + "%s/%u/input.pdf", + tmpdir, + i); + if (0 != + symlink (input, /* target */ + template_fn)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "symlink", + template_fn); + GNUNET_free (input); + GNUNET_free (template_fn); + return false; + } + GNUNET_free (template_fn); + + /* now setup typst invocation */ + { + char *argv[4]; + + argv[0] = "typst"; + argv[1] = "compile"; + argv[2] = input; + argv[3] = NULL; + tc->proc = GNUNET_OS_start_process_vap ( + GNUNET_OS_INHERIT_STD_ERR, + NULL, + fh, + NULL, + "typst", + argv); + if (NULL == tc->proc) + { + GNUNET_log_stderror (GNUNET_ERROR_TYPE_ERROR, + "fork"); + GNUNET_DISK_pipe_close (p); + cleanup_tc (tc); + GNUNET_free (input); + return NULL; + } + GNUNET_free (input); + stage->tc->active_stages++; + stage->cwh = GNUNET_wait_child (tc->proc, + &typst_done_cb, + stage); + GNUNET_assert (NULL != stage->cwh); + } + return true; +} + + +struct MHD_Response * +TALER_MHD_typst ( + const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *cfg_section_name, + unsigned int num_documents, + const struct TALER_MHD_TypstDocument docs[static num_documents]) +{ + static enum GNUNET_GenericReturnValue once = GNUNET_NO; + struct TypstContext *tc; + + switch (once) + { + case GNUNET_OK: + break; + case GNUNET_NO: + if (GNUNET_SYSERR == + GNUNET_OS_check_helper_binary ("typst", + false, + NULL)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "`typst' command not found\n"); + once = GNUNET_SYSERR; + return NULL; + } + if (GNUNET_SYSERR == + GNUNET_OS_check_helper_binary ("pdftk", + false, + NULL)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "`pdftk' command not found\n"); + once = GNUNET_SYSERR; + return NULL; + } + once = GNUNET_OK; + break; + case GNUNET_SYSERR: + return NULL; + } + tc = GNUNET_new (struct TypstContext); + GNUNET_CONTAINER_DLL_insert (tc_head, + tc_tail, + tc); + tc->tmpdir = GNUNET_strdup ("/tmp/taler-typst-XXXXXX"); + if (NULL == mkdtemp (tc->tmpdir)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "mkdtemp", + tc->tmpdir); + GNUNET_free (tc->tmpdir); + cleanup_tc (tc); + return NULL; + } + + /* setup typst stages */ + { + char *template_path; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (cfg, + cfg_section_name, + "TYPIST_TEMPLATES", + &template_path)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + cfg_section_name, + "TYPIST_TEMPLATES"); + cleanup_tc (tc); + return NULL; + } + tc->stages = GNUNET_new_array (num_documents, + struct TypstStage); + tc->num_stages = num_documents; + for (unsigned int i = 0; i<num_documents; i++) + { + tc->stages[i].tc = tc; + if (! setup_stage (&tc->stages[i], + i, + tc->tmpdir, + template_path, + &docs[i])) + { + cleanup_tc (tc); + return NULL; + } + } + GNUNET_free (template_path); + } + if (0 == stage->tc->active_stages) + { + complete_response (stage->tc); + } + + tc->p = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_RW); + + /* return response */ + { + struct MHD_Response *resp; + struct GNUNET_DISK_FileHandle *fh; + int fd; + + fh = GNUNET_DISK_pipe_detach_end (tc->p, + GNUNET_DISK_PF_END_READ); + fd = fh->fd; + GNUNET_free (fh); + resp = MHD_response_from_fd (MHD_SIZE_UNKNOWN, + fd); + TALER_MHD_add_global_headers (resp, + false); + + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + "application/pdf")); + return resp; + } +}