view mrjunejune/latex_renderer.c @ 263:ee04e4e69fed

Add functional JRPG frame and tools Add full-screen background_2 apertures, functional frame chrome, card-driven details, dual-window tools, live conversion workflows, and bounded cleanup for generated downloads. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <mrjunejune@users.noreply.github.com>
date Thu, 06 Aug 2026 11:31:30 -0700
parents b8b6e726964a
children
line wrap: on
line source

#include "mrjunejune/latex_renderer.h"

#ifdef __linux__

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/audit.h>
#include <linux/filter.h>
#include <linux/landlock.h>
#include <linux/seccomp.h>
#include <limits.h>
#include <poll.h>
#include <signal.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#ifndef LANDLOCK_ACCESS_FS_REFER
#define LANDLOCK_ACCESS_FS_REFER (1ULL << 13)
#endif
#ifndef LANDLOCK_ACCESS_FS_TRUNCATE
#define LANDLOCK_ACCESS_FS_TRUNCATE (1ULL << 14)
#endif

#if defined(__x86_64__)
#define LATEX_AUDIT_ARCH AUDIT_ARCH_X86_64
#elif defined(__aarch64__)
#define LATEX_AUDIT_ARCH AUDIT_ARCH_AARCH64
#else
#define LATEX_AUDIT_ARCH 0
#endif

#define LATEX_DIAGNOSTICS_MAX_BYTES (16 * 1024)
#define LATEX_CPU_TIMEOUT_SECONDS 5
#define LATEX_WALL_TIMEOUT_SECONDS 8
#define TECTONIC_BUNDLE_URL "https://data1.fullyjustified.net/tlextras-2022.0r0.tar"
#define TECTONIC_CACHE_PATH "third_party/tectonic/runtime/cache"
#define TECTONIC_COMPILER_PATH "third_party/tectonic/runtime/bin/tectonic"
#define TECTONIC_FONTCONFIG_SYSROOT_PATH "third_party/tectonic/runtime/sysroot"

static char *duplicate_message(const char *message)
{
  size_t length = strlen(message);
  char *copy = malloc(length + 1);
  if (copy)
    memcpy(copy, message, length + 1);
  return copy;
}

static Latex_Render_Result result_with_message(
    Latex_Render_Status status,
    const char *message)
{
  return (Latex_Render_Result){
    .status = status,
    .diagnostics = duplicate_message(message),
  };
}

static boolean write_all(int fd, const uint8 *data, size_t size)
{
  while (size > 0)
  {
    ssize_t written = write(fd, data, size);
    if (written < 0)
    {
      if (errno == EINTR)
        continue;
      return FALSE;
    }
    data += written;
    size -= (size_t)written;
  }
  return TRUE;
}

static int remove_tree(const char *path, unsigned depth)
{
  if (depth > 16)
    return -1;

  struct stat status;
  if (lstat(path, &status) != 0)
    return errno == ENOENT ? 0 : -1;
  if (!S_ISDIR(status.st_mode))
    return unlink(path);

  DIR *directory = opendir(path);
  if (!directory)
    return -1;

  int result = 0;
  struct dirent *entry;
  while ((entry = readdir(directory)) != NULL)
  {
    if (strcmp(entry->d_name, ".") == 0 ||
        strcmp(entry->d_name, "..") == 0)
      continue;

    char child[1024];
    int length = snprintf(
        child,
        sizeof(child),
        "%s/%s",
        path,
        entry->d_name);
    if (length < 0 ||
        (size_t)length >= sizeof(child) ||
        remove_tree(child, depth + 1) != 0)
    {
      result = -1;
      break;
    }
  }
  closedir(directory);
  return result == 0 ? rmdir(path) : result;
}

static boolean set_limit(int resource, rlim_t value)
{
  struct rlimit limit = {
    .rlim_cur = value,
    .rlim_max = value,
  };
  return setrlimit(resource, &limit) == 0;
}

static boolean add_landlock_path_rule(
    int ruleset_fd,
    const char *path,
    uint64 access,
    boolean required)
{
  int path_fd = open(path, O_PATH | O_CLOEXEC);
  if (path_fd < 0)
    return !required && errno == ENOENT;

  struct stat status;
  boolean ok = fstat(path_fd, &status) == 0;
  if (ok && !S_ISDIR(status.st_mode))
  {
    access &= LANDLOCK_ACCESS_FS_EXECUTE |
        LANDLOCK_ACCESS_FS_READ_FILE |
        LANDLOCK_ACCESS_FS_WRITE_FILE;
  }

  struct landlock_path_beneath_attr rule = {
    .allowed_access = access,
    .parent_fd = path_fd,
  };
  if (ok)
    ok = syscall(
        SYS_landlock_add_rule,
        ruleset_fd,
        LANDLOCK_RULE_PATH_BENEATH,
        &rule,
        0) == 0;
  close(path_fd);
  return ok;
}

static boolean add_landlock_tree_rules(
    int ruleset_fd,
    const char *path,
    uint64 access,
    unsigned depth)
{
  if (depth > 32 ||
      !add_landlock_path_rule(ruleset_fd, path, access, TRUE))
    return FALSE;

  struct stat status;
  if (lstat(path, &status) != 0)
    return FALSE;
  if (!S_ISDIR(status.st_mode))
    return TRUE;

  DIR *directory = opendir(path);
  if (!directory)
    return FALSE;
  boolean ok = TRUE;
  struct dirent *entry;
  while (ok && (entry = readdir(directory)) != NULL)
  {
    if (strcmp(entry->d_name, ".") == 0 ||
        strcmp(entry->d_name, "..") == 0)
      continue;
    char child[PATH_MAX];
    int length = snprintf(
        child,
        sizeof(child),
        "%s/%s",
        path,
        entry->d_name);
    ok = length >= 0 &&
        (size_t)length < sizeof(child) &&
        add_landlock_tree_rules(
            ruleset_fd,
            child,
            access,
            depth + 1);
  }
  closedir(directory);
  return ok;
}

static boolean apply_filesystem_sandbox(
    const char *job_directory,
    const char *compiler,
    const char *cache_directory,
    const char *fontconfig_sysroot)
{
  int abi = syscall(
      SYS_landlock_create_ruleset,
      NULL,
      0,
      LANDLOCK_CREATE_RULESET_VERSION);
  if (abi < 1)
    return FALSE;

  uint64 read_access =
      LANDLOCK_ACCESS_FS_EXECUTE |
      LANDLOCK_ACCESS_FS_READ_FILE |
      LANDLOCK_ACCESS_FS_READ_DIR;
  uint64 write_access =
      LANDLOCK_ACCESS_FS_WRITE_FILE |
      LANDLOCK_ACCESS_FS_REMOVE_DIR |
      LANDLOCK_ACCESS_FS_REMOVE_FILE |
      LANDLOCK_ACCESS_FS_MAKE_CHAR |
      LANDLOCK_ACCESS_FS_MAKE_DIR |
      LANDLOCK_ACCESS_FS_MAKE_REG |
      LANDLOCK_ACCESS_FS_MAKE_SOCK |
      LANDLOCK_ACCESS_FS_MAKE_FIFO |
      LANDLOCK_ACCESS_FS_MAKE_BLOCK |
      LANDLOCK_ACCESS_FS_MAKE_SYM;
  if (abi >= 2)
    write_access |= LANDLOCK_ACCESS_FS_REFER;
  if (abi >= 3)
    write_access |= LANDLOCK_ACCESS_FS_TRUNCATE;

  struct landlock_ruleset_attr ruleset = {
    .handled_access_fs = read_access | write_access,
  };
  int ruleset_fd = syscall(
      SYS_landlock_create_ruleset,
      &ruleset,
      sizeof(ruleset),
      0);
  if (ruleset_fd < 0)
    return FALSE;

  boolean ok = TRUE;
  if (ok)
    ok = add_landlock_path_rule(
        ruleset_fd,
        compiler,
        LANDLOCK_ACCESS_FS_EXECUTE | LANDLOCK_ACCESS_FS_READ_FILE,
        TRUE);
  if (ok)
    ok = add_landlock_tree_rules(
        ruleset_fd,
        cache_directory,
        read_access | LANDLOCK_ACCESS_FS_MAKE_DIR,
        0);
  if (ok)
    ok = add_landlock_tree_rules(
        ruleset_fd,
        fontconfig_sysroot,
        read_access,
        0);
  if (ok)
    ok = add_landlock_path_rule(
        ruleset_fd,
        "/dev/null",
        LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_WRITE_FILE,
        TRUE);
  if (ok)
    ok = add_landlock_path_rule(
        ruleset_fd,
        "/dev/urandom",
        LANDLOCK_ACCESS_FS_READ_FILE,
        FALSE);
  if (ok)
    ok = add_landlock_path_rule(
        ruleset_fd,
        job_directory,
        read_access | write_access,
        TRUE);
  if (ok)
    ok = prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == 0;
  if (ok)
    ok = syscall(
        SYS_landlock_restrict_self,
        ruleset_fd,
        0) == 0;
  close(ruleset_fd);
  return ok;
}

static boolean apply_network_sandbox(void)
{
  if (LATEX_AUDIT_ARCH == 0)
    return FALSE;

  struct sock_filter filters[64];
  size_t count = 0;
#define ADD_FILTER(value) filters[count++] = (struct sock_filter)value
#define DENY_SYSCALL(number) \
  do { \
    ADD_FILTER(BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, number, 0, 1)); \
    ADD_FILTER(BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | EPERM)); \
  } while (0)

  ADD_FILTER(BPF_STMT(
      BPF_LD | BPF_W | BPF_ABS,
      offsetof(struct seccomp_data, arch)));
  ADD_FILTER(BPF_JUMP(
      BPF_JMP | BPF_JEQ | BPF_K,
      LATEX_AUDIT_ARCH,
      1,
      0));
  ADD_FILTER(BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS));
  ADD_FILTER(BPF_STMT(
      BPF_LD | BPF_W | BPF_ABS,
      offsetof(struct seccomp_data, nr)));
  DENY_SYSCALL(__NR_socket);
  DENY_SYSCALL(__NR_socketpair);
  DENY_SYSCALL(__NR_connect);
  DENY_SYSCALL(__NR_accept);
  DENY_SYSCALL(__NR_accept4);
  DENY_SYSCALL(__NR_bind);
  DENY_SYSCALL(__NR_listen);
  DENY_SYSCALL(__NR_sendto);
  DENY_SYSCALL(__NR_recvfrom);
  DENY_SYSCALL(__NR_sendmsg);
  DENY_SYSCALL(__NR_recvmsg);
  ADD_FILTER(BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW));

  struct sock_fprog program = {
    .len = (unsigned short)count,
    .filter = filters,
  };
#undef DENY_SYSCALL
#undef ADD_FILTER
  return prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &program) == 0;
}

static void configure_child_environment(
    const char *job_directory,
    const char *cache_directory,
    const char *fontconfig_sysroot)
{
  clearenv();
  setenv("HOME", job_directory, 1);
  setenv("TMPDIR", job_directory, 1);
  setenv("XDG_CACHE_HOME", job_directory, 1);
  char tectonic_cache[PATH_MAX];
  snprintf(
      tectonic_cache,
      sizeof(tectonic_cache),
      "%s/tectonic",
      cache_directory);
  setenv("TECTONIC_CACHE_DIR", tectonic_cache, 1);
  setenv("FONTCONFIG_FILE", "fonts.conf", 1);
  setenv("FONTCONFIG_PATH", "/etc/fonts", 1);
  setenv("FONTCONFIG_SYSROOT", fontconfig_sysroot, 1);
  setenv("TECTONIC_UNTRUSTED_MODE", "1", 1);
  setenv("SOURCE_DATE_EPOCH", "0", 1);
  setenv("TZ", "UTC", 1);
  setenv("LANG", "C.UTF-8", 1);
}

static void close_inherited_descriptors(void)
{
#ifdef SYS_close_range
  if (syscall(SYS_close_range, 3U, UINT_MAX, 0) == 0)
    return;
#endif
  struct rlimit limit;
  rlim_t maximum = 65536;
  if (getrlimit(RLIMIT_NOFILE, &limit) == 0 &&
      limit.rlim_cur != RLIM_INFINITY &&
      limit.rlim_cur < maximum)
    maximum = limit.rlim_cur;
  for (int fd = 3; fd < (int)maximum; fd++)
    close(fd);
}

static void run_compiler_child(
    const char *compiler,
    const char *cache_directory,
    const char *fontconfig_sysroot,
    const char *job_directory,
    int output_fd)
{
  if (dup2(output_fd, STDOUT_FILENO) < 0 ||
      dup2(output_fd, STDERR_FILENO) < 0)
    _exit(120);
  if (output_fd != STDOUT_FILENO && output_fd != STDERR_FILENO)
    close(output_fd);
  close_inherited_descriptors();
  if (setpgid(0, 0) != 0 ||
      prctl(PR_SET_PDEATHSIG, SIGKILL) != 0 ||
      getppid() == 1)
    _exit(119);
  if (chdir(job_directory) != 0)
    _exit(121);

  configure_child_environment(
      job_directory,
      cache_directory,
      fontconfig_sysroot);
  if (!set_limit(RLIMIT_CPU, LATEX_CPU_TIMEOUT_SECONDS) ||
      !set_limit(RLIMIT_AS, 768 * 1024 * 1024) ||
      !set_limit(RLIMIT_FSIZE, LATEX_PDF_MAX_BYTES) ||
      !set_limit(RLIMIT_NOFILE, 64) ||
      !set_limit(RLIMIT_NPROC, 1))
    _exit(122);
  if (!apply_filesystem_sandbox(
          job_directory,
          compiler,
          cache_directory,
          fontconfig_sysroot) ||
      !apply_network_sandbox())
    _exit(123);

  char *const arguments[] = {
    (char *)compiler,
    "--untrusted",
    "--only-cached",
    "--chatter",
    "minimal",
    "--color",
    "never",
    "--reruns",
    "1",
    "--outdir",
    ".",
    "--bundle",
    TECTONIC_BUNDLE_URL,
    "document.tex",
    NULL,
  };
  execv(compiler, arguments);
  _exit(errno == ENOENT ? 127 : 126);
}

static double elapsed_seconds(
    const struct timespec *start,
    const struct timespec *now)
{
  return (double)(now->tv_sec - start->tv_sec) +
      (double)(now->tv_nsec - start->tv_nsec) / 1000000000.0;
}

static int wait_for_compiler(
    pid_t child,
    int output_fd,
    char *diagnostics,
    size_t diagnostics_size,
    boolean *timed_out)
{
  int flags = fcntl(output_fd, F_GETFL, 0);
  if (flags >= 0)
    fcntl(output_fd, F_SETFL, flags | O_NONBLOCK);

  size_t used = 0;
  int child_status = 0;
  boolean child_done = FALSE;
  struct timespec start;
  clock_gettime(CLOCK_MONOTONIC, &start);

  while (!child_done)
  {
    char buffer[2048];
    ssize_t count;
    while ((count = read(output_fd, buffer, sizeof(buffer))) > 0)
    {
      size_t available = diagnostics_size - used - 1;
      size_t copy_size = (size_t)count < available ? (size_t)count : available;
      if (copy_size > 0)
      {
        memcpy(diagnostics + used, buffer, copy_size);
        used += copy_size;
      }
    }

    pid_t waited = waitpid(child, &child_status, WNOHANG);
    if (waited == child)
    {
      child_done = TRUE;
      break;
    }
    if (waited < 0 && errno != EINTR)
      break;

    struct timespec now;
    clock_gettime(CLOCK_MONOTONIC, &now);
    if (elapsed_seconds(&start, &now) >= LATEX_WALL_TIMEOUT_SECONDS)
    {
      *timed_out = TRUE;
      kill(-child, SIGKILL);
      while (waitpid(child, &child_status, 0) < 0 && errno == EINTR)
      {
      }
      child_done = TRUE;
      break;
    }

    struct pollfd poll_fd = {
      .fd = output_fd,
      .events = POLLIN,
    };
    poll(&poll_fd, 1, 50);
  }

  char buffer[2048];
  ssize_t count;
  while ((count = read(output_fd, buffer, sizeof(buffer))) > 0)
  {
    size_t available = diagnostics_size - used - 1;
    size_t copy_size = (size_t)count < available ? (size_t)count : available;
    if (copy_size > 0)
    {
      memcpy(diagnostics + used, buffer, copy_size);
      used += copy_size;
    }
  }
  diagnostics[used] = '\0';
  kill(-child, SIGKILL);
  return child_status;
}

static boolean read_pdf(
    const char *path,
    uint8 **data,
    size_t *size)
{
  int fd = open(path, O_RDONLY | O_CLOEXEC | O_NOFOLLOW);
  if (fd < 0)
    return FALSE;

  struct stat status;
  boolean ok = fstat(fd, &status) == 0 &&
      S_ISREG(status.st_mode) &&
      status.st_size > 4 &&
      status.st_size <= LATEX_PDF_MAX_BYTES;
  uint8 *content = NULL;
  if (ok)
  {
    content = malloc((size_t)status.st_size);
    ok = content != NULL;
  }
  size_t offset = 0;
  while (ok && offset < (size_t)status.st_size)
  {
    ssize_t count = read(
        fd,
        content + offset,
        (size_t)status.st_size - offset);
    if (count < 0 && errno == EINTR)
      continue;
    if (count <= 0)
    {
      ok = FALSE;
      break;
    }
    offset += (size_t)count;
  }
  close(fd);

  if (!ok ||
      memcmp(content, "%PDF-", 5) != 0)
  {
    free(content);
    return FALSE;
  }
  *data = content;
  *size = offset;
  return TRUE;
}

static boolean resolve_runtime_path(
    const char *relative_path,
    char resolved[PATH_MAX])
{
  if (realpath(relative_path, resolved))
    return TRUE;

  const char *runfiles = getenv("RUNFILES_DIR");
  const char *workspace = getenv("TEST_WORKSPACE");
  char candidate[PATH_MAX];
  if (runfiles && workspace)
  {
    int length = snprintf(
        candidate,
        sizeof(candidate),
        "%s/%s/%s",
        runfiles,
        workspace,
        relative_path);
    if (length >= 0 &&
        (size_t)length < sizeof(candidate) &&
        realpath(candidate, resolved))
      return TRUE;
  }

  char executable[PATH_MAX];
  ssize_t executable_length = readlink(
      "/proc/self/exe",
      executable,
      sizeof(executable) - 1);
  if (executable_length <= 0)
    return FALSE;
  executable[executable_length] = '\0';

  char *last_slash = strrchr(executable, '/');
  if (last_slash)
  {
    *last_slash = '\0';
    int length = snprintf(
        candidate,
        sizeof(candidate),
        "%s/%s",
        executable,
        relative_path);
    if (length >= 0 &&
        (size_t)length < sizeof(candidate) &&
        realpath(candidate, resolved))
      return TRUE;
  }

  char *package = strstr(executable, "/mrjunejune/");
  if (!package)
    return FALSE;
  *package = '\0';
  int length = snprintf(
      candidate,
      sizeof(candidate),
      "%s/%s",
      executable,
      relative_path);
  return length >= 0 &&
      (size_t)length < sizeof(candidate) &&
      realpath(candidate, resolved) != NULL;
}

Latex_Render_Result Latex_Render(const uint8 *source, size_t source_size)
{
  if (!source || source_size == 0)
    return result_with_message(
        LATEX_RENDER_INVALID_INPUT,
        "LaTeX source is required.");
  if (source_size > LATEX_SOURCE_MAX_BYTES)
    return result_with_message(
        LATEX_RENDER_INVALID_INPUT,
        "LaTeX source exceeds the 64 KiB limit.");
  if (memchr(source, '\0', source_size))
    return result_with_message(
        LATEX_RENDER_INVALID_INPUT,
        "LaTeX source cannot contain NUL bytes.");

  char compiler[PATH_MAX];
  char cache_directory[PATH_MAX];
  char fontconfig_sysroot[PATH_MAX];
  if (!resolve_runtime_path(TECTONIC_COMPILER_PATH, compiler) ||
      !resolve_runtime_path(TECTONIC_CACHE_PATH, cache_directory) ||
      !resolve_runtime_path(
          TECTONIC_FONTCONFIG_SYSROOT_PATH,
          fontconfig_sysroot) ||
      access(compiler, X_OK) != 0)
    return result_with_message(
        LATEX_RENDER_COMPILER_UNAVAILABLE,
        "The Bazel Tectonic runtime is unavailable.");

  char job_directory[] = "/tmp/mrjunejune-latex-XXXXXX";
  if (!mkdtemp(job_directory))
    return result_with_message(
        LATEX_RENDER_INTERNAL_ERROR,
        "Unable to create the LaTeX workspace.");

  char source_path[512];
  snprintf(
      source_path,
      sizeof(source_path),
      "%s/document.tex",
      job_directory);
  int source_fd = open(
      source_path,
      O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC | O_NOFOLLOW,
      0600);
  if (source_fd < 0 || !write_all(source_fd, source, source_size))
  {
    if (source_fd >= 0)
      close(source_fd);
    remove_tree(job_directory, 0);
    return result_with_message(
        LATEX_RENDER_INTERNAL_ERROR,
        "Unable to write the LaTeX source.");
  }
  close(source_fd);

  int output_pipe[2];
  if (pipe2(output_pipe, O_CLOEXEC) != 0)
  {
    remove_tree(job_directory, 0);
    return result_with_message(
        LATEX_RENDER_INTERNAL_ERROR,
        "Unable to capture compiler output.");
  }

  pid_t child = fork();
  if (child < 0)
  {
    close(output_pipe[0]);
    close(output_pipe[1]);
    remove_tree(job_directory, 0);
    return result_with_message(
        LATEX_RENDER_INTERNAL_ERROR,
        "Unable to start the LaTeX compiler.");
  }
  if (child == 0)
  {
    close(output_pipe[0]);
    run_compiler_child(
        compiler,
        cache_directory,
        fontconfig_sysroot,
        job_directory,
        output_pipe[1]);
  }
  setpgid(child, child);

  close(output_pipe[1]);
  char *diagnostics = calloc(1, LATEX_DIAGNOSTICS_MAX_BYTES + 1);
  if (!diagnostics)
  {
    kill(-child, SIGKILL);
    waitpid(child, NULL, 0);
    close(output_pipe[0]);
    remove_tree(job_directory, 0);
    return result_with_message(
        LATEX_RENDER_INTERNAL_ERROR,
        "Unable to allocate compiler diagnostics.");
  }

  boolean timed_out = FALSE;
  int child_status = wait_for_compiler(
      child,
      output_pipe[0],
      diagnostics,
      LATEX_DIAGNOSTICS_MAX_BYTES + 1,
      &timed_out);
  close(output_pipe[0]);

  Latex_Render_Result result = {
    .status = LATEX_RENDER_INTERNAL_ERROR,
    .diagnostics = diagnostics,
  };
  if (timed_out)
  {
    result.status = LATEX_RENDER_TIMEOUT;
    free(result.diagnostics);
    result.diagnostics = duplicate_message(
        "Compilation exceeded the 8 second limit.");
  }
  else if (WIFEXITED(child_status) && WEXITSTATUS(child_status) == 127)
  {
    result.status = LATEX_RENDER_COMPILER_UNAVAILABLE;
  }
  else if (WIFEXITED(child_status) && WEXITSTATUS(child_status) == 123)
  {
    result.status = LATEX_RENDER_SANDBOX_ERROR;
    free(result.diagnostics);
    result.diagnostics = duplicate_message(
        "The server could not apply the LaTeX security sandbox.");
  }
  else if (WIFSIGNALED(child_status) &&
           (WTERMSIG(child_status) == SIGXCPU ||
            WTERMSIG(child_status) == SIGKILL))
  {
    result.status = LATEX_RENDER_TIMEOUT;
    free(result.diagnostics);
    result.diagnostics = duplicate_message(
        "Compilation exceeded its CPU or memory limit.");
  }
  else if (!WIFEXITED(child_status) || WEXITSTATUS(child_status) != 0)
  {
    result.status = LATEX_RENDER_COMPILE_ERROR;
    if (!result.diagnostics[0])
    {
      free(result.diagnostics);
      result.diagnostics = duplicate_message("LaTeX compilation failed.");
    }
  }
  else
  {
    char pdf_path[512];
    snprintf(pdf_path, sizeof(pdf_path), "%s/document.pdf", job_directory);
    if (read_pdf(pdf_path, &result.pdf_data, &result.pdf_size))
    {
      result.status = LATEX_RENDER_OK;
      free(result.diagnostics);
      result.diagnostics = NULL;
    }
    else
    {
      result.status = LATEX_RENDER_INTERNAL_ERROR;
      free(result.diagnostics);
      result.diagnostics = duplicate_message(
          "The compiler did not produce a valid PDF.");
    }
  }

  remove_tree(job_directory, 0);
  return result;
}

void Latex_Render_Result_Destroy(Latex_Render_Result *result)
{
  if (!result)
    return;
  free(result->pdf_data);
  free(result->diagnostics);
  memset(result, 0, sizeof(*result));
}

#else

#include <stdlib.h>
#include <string.h>

Latex_Render_Result Latex_Render(const uint8 *source, size_t source_size)
{
  (void)source;
  (void)source_size;
  const char *message =
      "The LaTeX security sandbox is only available on Linux.";
  Latex_Render_Result result = {
    .status = LATEX_RENDER_SANDBOX_ERROR,
    .diagnostics = malloc(strlen(message) + 1),
  };
  if (result.diagnostics)
    strcpy(result.diagnostics, message);
  return result;
}

void Latex_Render_Result_Destroy(Latex_Render_Result *result)
{
  if (!result)
    return;
  free(result->pdf_data);
  free(result->diagnostics);
  memset(result, 0, sizeof(*result));
}

#endif