exchange

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

commit 17e2243af51b84b8f074e082f0a03bfe1183990a
parent 09c8beb73e3de044f17d512b8ad75ed091bcc0e6
Author: Christian Grothoff <grothoff@gnunet.org>
Date:   Fri,  7 Nov 2025 17:40:21 +0100

draft logic for typist integration

Diffstat:
Asrc/mhd/mhd_typist.c | 581+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 581 insertions(+), 0 deletions(-)

diff --git a/src/mhd/mhd_typist.c b/src/mhd/mhd_typist.c @@ -0,0 +1,581 @@ +/* + 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; + } + } +}