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:
| D | src/mhd/mhd_typist.c | | | 581 | ------------------------------------------------------------------------------- |
| A | src/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;
+ }
+}