Subject: [PATCH 05/12] gnu: ghostscript: Unbundle dependencies and remove non-free parts.

From: David Elsing <[email-masked]>
Date: Wed, 26 Jun 2024 19:26:58 +0000

The fonts in pcl/urwfonts and the jpegxr library are non-free.
Autogenerated files are removed as well.

Due to a circular dependency with python-sphinx, the documentation is not
built.

* gnu/packages/ghostscript.scm (ghostscript)[source]: Use git-fetch. Adjust
snippet. Replace patches.
[outputs]: Delete field.
[arguments]<configure-flags>: Add "--without-xps". Remove "--enable-dynamic".
<#:phases>: Add 'unbundle' and 'generate-scfdtab' phases.
[native-inputs]: Add autoconf and automake.
[inputs]: Add extract, ijs, lcms2mt, memento and openjpeg.
[license]: Add missing licenses.
(ghostscript/fixed): Delete variable.
* gnu/packages/patches/ghostscript-CVE-2023-36664.patch: Delete file.
* gnu/packages/patches/ghostscript-CVE-2023-36664-fixup.patch: Delete file.
* gnu/packages/patches/ghostscript-leptonica-hurd.patch: Delete file.
* gnu/packages/patches/ghostscript-no-header-creationdate.patch: Delete file.
* gnu/packages/patches/ghostscript-no-header-id.patch: Delete file.
* gnu/packages/patches/ghostscript-no-header-uuid.patch: Delete file.
* gnu/packages/patches/ghostscript-reproducibility.patch: New file.
* gnu/local.mk (dist_patch_DATA): Adjust accordingly.
---
 gnu/local.mk                                  |   7 +-
 gnu/packages/ghostscript.scm                  | 142 +++++++++++++-----
 .../ghostscript-CVE-2023-36664-fixup.patch    |  56 -------
 .../patches/ghostscript-CVE-2023-36664.patch  | 142 ------------------
 .../patches/ghostscript-leptonica-hurd.patch  |  78 ----------
 .../ghostscript-no-header-creationdate.patch  |  46 ------
 .../patches/ghostscript-no-header-id.patch    |  57 -------
 .../patches/ghostscript-no-header-uuid.patch  |  49 ------
 .../patches/ghostscript-reproducibility.patch |  26 ++++
 9 files changed, 129 insertions(+), 474 deletions(-)
 delete mode 100644 gnu/packages/patches/ghostscript-CVE-2023-36664-fixup.patch
 delete mode 100644 gnu/packages/patches/ghostscript-CVE-2023-36664.patch
 delete mode 100644 gnu/packages/patches/ghostscript-leptonica-hurd.patch
 delete mode 100644 gnu/packages/patches/ghostscript-no-header-creationdate.patch
 delete mode 100644 gnu/packages/patches/ghostscript-no-header-id.patch
 delete mode 100644 gnu/packages/patches/ghostscript-no-header-uuid.patch
 create mode 100644 gnu/packages/patches/ghostscript-reproducibility.patch

diff --git a/gnu/local.mk b/gnu/local.mk
index 2fc14e68fe..c44c21ffa0 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -1338,12 +1338,7 @@ dist_patch_DATA =						\
   %D%/packages/patches/ghc-language-haskell-extract-ghc-8.10.patch	\
   %D%/packages/patches/ghc-memory-fix-32bit.patch		\
   %D%/packages/patches/ghc-persistent-fix-32bit.patch		\
-  %D%/packages/patches/ghostscript-CVE-2023-36664.patch		\
-  %D%/packages/patches/ghostscript-CVE-2023-36664-fixup.patch	\
-  %D%/packages/patches/ghostscript-leptonica-hurd.patch		\
-  %D%/packages/patches/ghostscript-no-header-id.patch		\
-  %D%/packages/patches/ghostscript-no-header-uuid.patch		\
-  %D%/packages/patches/ghostscript-no-header-creationdate.patch \
+  %D%/packages/patches/ghostscript-reproducibility.patch	\
   %D%/packages/patches/git-filter-repo-generate-doc.patch	\
   %D%/packages/patches/gklib-suitesparse.patch			\
   %D%/packages/patches/glib-appinfo-watch.patch			\
diff --git a/gnu/packages/ghostscript.scm b/gnu/packages/ghostscript.scm
index 40891676b3..2bc168db68 100644
--- a/gnu/packages/ghostscript.scm
+++ b/gnu/packages/ghostscript.scm
@@ -215,43 +215,61 @@ (define-public psutils
 (define-public ghostscript
   (package
     (name "ghostscript")
-    (version "9.56.1")
-    (replacement ghostscript/fixed)
+    (version "10.03.1")
     (source
      (origin
-       (method url-fetch)
-       (uri (string-append "https://github.com/ArtifexSoftware/"
-                           "ghostpdl-downloads/releases/download/gs"
-                           (string-delete #\. version)
-                           "/ghostscript-" version ".tar.xz"))
+       (method git-fetch)
+       (uri (git-reference
+             (url "git://git.ghostscript.com/ghostpdl.git")
+             (commit (string-append "ghostpdl-" version))))
+       (file-name (git-file-name name version))
        (sha256
         (base32
-         "1r5qash65m6ignki6z72q4rlai9ka99xrxnmqd19n02has00cd6l"))
-       (patches (search-patches "ghostscript-no-header-creationdate.patch"
-                                "ghostscript-no-header-id.patch"
-                                "ghostscript-no-header-uuid.patch"))
+         "1rzgk12vmv3062l8w7vw5kdsgfphmpwbq38alv2qgiz5wpalsrds"))
+       (patches (search-patches "ghostscript-reproducibility.patch"))
        (modules '((guix build utils)))
        (snippet
-        ;; Remove bundled libraries. The bundled OpenJPEG is a patched fork so
-        ;; we leave it, at least for now.
-        ;; TODO Try unbundling ijs, which is developed alongside Ghostscript.
-        ;; Likewise for the thread-safe lcms2 fork called "lcms2art".
         '(begin
-           (for-each delete-file-recursively '("freetype" "jbig2dec" "jpeg"
-                                               "libpng" "tiff" "zlib"))))))
+           (for-each
+            delete-file-recursively
+            (append
+             (list
+              ;; Bundled software (some non-free)
+              "cups" "expat" "freetype" "ijs" "jbig2dec" "jpeg" "jpegxr"
+              "lcms2mt" "libpng" "openjpeg" "pcl" "tiff" "xps" "zlib"
+              ;; Non-free ICC profile
+              "doc/language-bindings/images"
+              ;; Contain autogenerated files
+              "demos/csharp/windows"
+              "toolbin/Acrobat2Tiff"
+              "base/scfdtab.c"
+              "demos/java/jni/gs_jni/com_artifex_gsjava_util_NativePointer.h"
+              "demos/java/jni/gs_jni/com_artifex_gsjava_GSAPI.h"
+              "doc/GS9_Color_Management.pdf"
+              "doc/Ghostscript.pdf"
+              "doc/pclxps/ghostpdl.pdf")
+             (find-files "doc/src/_static" "cm-fig.*\\.png")))
+           (for-each
+            (lambda (name)
+              (delete-file (string-append "arch/" name)))
+            '("osx-x86-x86_64-ppc-gcc.h"
+              "windows-arm-msvc.h"
+              "windows-x64-msvc.h"
+              "windows-x86-msvc.h"))
+           (delete-file "base/memento.c")
+           (delete-file "base/memento.h")))))
     (build-system gnu-build-system)
-    (outputs '("out" "doc"))            ;19 MiB of HTML/PS doc + examples
     (arguments
      (list
-      #:disallowed-references '("doc")
       #:configure-flags
       #~(list (string-append "LDFLAGS=-Wl,-rpath=" #$output "/lib")
               "--with-system-libtiff"
+              ;; Requires jpegxr, which has a nonfree license.
+              "--without-xps"
               "LIBS=-lz"
               (string-append "ZLIBDIR="
                              (dirname (search-input-file %build-inputs
                                                          "include/zlib.h")))
-              "--enable-dynamic"
               "--disable-compile-inits"
               (string-append "--with-fontpath="
                              (search-input-directory
@@ -270,16 +288,19 @@ (define-public ghostscript
                      '()))
       #:phases
       #~(modify-phases %standard-phases
-          #$@(if (target-hurd?)
-                 #~((add-after 'unpack 'patch-leptonica
-                      (lambda _
-                        (let ((patch-file
-                               #$(local-file
-                                  (search-patch
-                                   "ghostscript-leptonica-hurd.patch"))))
-                          (with-directory-excursion "leptonica"
-                            (invoke "patch" "--force" "-p1" "-i" patch-file))))))
-                 #~())
+          (add-after 'unpack 'unbundle
+            (lambda _
+              (substitute* '("base/lib.mak" "base/tiff.mak"
+                             "devices/dcontrib.mak" "devices/devs.mak"
+                             "psi/int.mak")
+                ((".*:\\$\\(GLSRC\\)memento\\.h".*) ""))
+              (substitute* "base/lib.mak"
+                ((" \\$\\(GLOBJ\\)memento\\.\\$\\(OBJ\\)") ""))))
+          (add-before 'bootstrap 'patch-autogen
+            (lambda _
+              (substitute* "autogen.sh"
+                (("^autoreconf.*" orig)
+                 (string-append orig "\nexit\n")))))
           (add-before 'configure 'create-output-directory
             (lambda _
               ;; The configure script refuses to function if the directory
@@ -287,7 +308,6 @@ (define-public ghostscript
               (mkdir-p (string-append #$output "/lib"))))
           (add-after 'configure 'remove-doc-reference
             (lambda _
-              ;; Don't retain a reference to the 'doc' output in 'gs'.
               ;; The only use of this definition is in the output of
               ;; 'gs --help', so this change is fine.
               (substitute* "base/gscdef.c"
@@ -305,6 +325,25 @@ (define-public ghostscript
                          (("^AUXEXTRALIBS=(.*)$" _ value)
                           (string-append "AUXEXTRALIBS = -lz " value "\n"))))))
                  '())
+          (add-before 'build 'generate-scfdtab
+            (lambda _
+              (invoke "make" "obj/arch.h")
+              ;; These changes are necessary for cross builds, where there can
+              ;; be a conflict for int64_t.
+              (with-directory-excursion "base"
+                (copy-file "scommon.h" "tmpfile")
+                (substitute* "scommon.h"
+                  ((".*#include \"stdint_\\.h\".*") ""))
+                (let ((include-path (getenv "C_INCLUDE_PATH")))
+                  ;; For cross builds
+                  (setenv "C_INCLUDE_PATH"
+                          (string-append #$(this-package-input "memento")
+                                         "/include"))
+                  (invoke "gcc" "-Dfopen=fopen" "-I../obj"
+                          "scfdgen.c" "scfetab.c" "-o" "scfdgen")
+                  (setenv "C_INCLUDE_PATH" include-path))
+                (rename-file "tmpfile" "scommon.h")
+                (invoke "./scfdgen"))))
           (replace 'build
             (lambda _
               ;; Build 'libgs.so', but don't build the statically-linked 'gs'
@@ -320,7 +359,9 @@ (define-public ghostscript
               (symlink "gsc" (string-append #$output "/bin/gs")))))))
     (native-inputs
      (append
-      (list perl
+      (list autoconf
+            automake
+            perl
             pkg-config                  ;needed for freetype
             python-minimal-wrapper
             tcl)
@@ -330,14 +371,19 @@ (define-public ghostscript
           (list zlib libjpeg-turbo)
           '())))
     (inputs
-     (list fontconfig
+     (list extract
+           fontconfig
            freetype
            font-ghostscript
+           ijs
            jbig2dec
+           lcms2mt
            libjpeg-turbo
            libpaper
            libpng
            libtiff
+           memento
+           openjpeg
            zlib))
     (synopsis "PostScript and PDF interpreter")
     (description
@@ -346,13 +392,29 @@ (define-public ghostscript
 capabilities of the PostScript language.  It supports a wide variety of
 output file formats and printers.")
     (home-page "https://www.ghostscript.com/")
-    (license license:agpl3+)))
-
-(define ghostscript/fixed
-  (package-with-patches
-   ghostscript
-   (search-patches "ghostscript-CVE-2023-36664.patch"
-                   "ghostscript-CVE-2023-36664-fixup.patch")))
+    (license
+     (list
+      ;; Most of the code is distributed under the AGPLv3+.
+      license:agpl3+
+      ;; Some files (or parts of files) are distributed under other,
+      ;; compatible licenses.
+      (license:non-copyleft "file://devices/gdev4693.c")
+      (license:non-copyleft "file://devices/gdevifno.c")
+      (license:non-copyleft "file://base/icc34.h")
+      (license:non-copyleft "file://base/CMap/78-EUC-H")
+      license:asl2.0
+      license:bsd-3
+      license:expat
+      license:freetype
+      license:gpl1+
+      license:gpl2+
+      license:isc
+      license:lgpl2.1
+      license:public-domain
+      license:zlib
+      ;; Additional exception for the font files in Resource/Font for
+      ;; inclusion in Postscript and PDF files.
+      (license:non-copyleft "file://LICENSE")))))
 
 (define-public ghostscript/x
   (package/inherit ghostscript
diff --git a/gnu/packages/patches/ghostscript-CVE-2023-36664-fixup.patch b/gnu/packages/patches/ghostscript-CVE-2023-36664-fixup.patch
deleted file mode 100644
index c2a222701f..0000000000
--- a/gnu/packages/patches/ghostscript-CVE-2023-36664-fixup.patch
+++ /dev/null
@@ -1,56 +0,0 @@
-From 0974e4f2ac0005d3731e0b5c13ebc7e965540f4d Mon Sep 17 00:00:00 2001
-From: Chris Liddell <[email-masked]>
-Date: Wed, 14 Jun 2023 09:08:12 +0100
-Subject: [PATCH] Bug 706778: 706761 revisit
-
-Two problems with the original commit. The first a silly typo inverting the
-logic of a test.
-
-The second was forgetting that we actually actually validate two candidate
-strings for pipe devices. One with the expected "%pipe%" prefix, the other
-using the pipe character prefix: "|".
-
-This addresses both those.
----
- base/gpmisc.c   | 2 +-
- base/gslibctx.c | 4 ++--
- 2 files changed, 3 insertions(+), 3 deletions(-)
-
-diff --git a/base/gpmisc.c b/base/gpmisc.c
-index 58511270e..2b0064bea 100644
---- a/base/gpmisc.c
-+++ b/base/gpmisc.c
-@@ -1081,7 +1081,7 @@ gp_validate_path_len(const gs_memory_t *mem,
-     /* "%pipe%" do not follow the normal rules for path definitions, so we
-        don't "reduce" them to avoid unexpected results
-      */
--    if (len > 5 && memcmp(path, "%pipe", 5) != 0) {
-+    if (path[0] == '|' || (len > 5 && memcmp(path, "%pipe", 5) == 0)) {
-         bufferfull = buffer = (char *)gs_alloc_bytes(mem->thread_safe_memory, len + 1, "gp_validate_path");
-         if (buffer == NULL)
-             return gs_error_VMerror;
-diff --git a/base/gslibctx.c b/base/gslibctx.c
-index d2a1aa91d..42af99090 100644
---- a/base/gslibctx.c
-+++ b/base/gslibctx.c
-@@ -743,7 +743,7 @@ gs_add_control_path_len_flags(const gs_memory_t *mem, gs_path_control_t type, co
-     /* "%pipe%" do not follow the normal rules for path definitions, so we
-        don't "reduce" them to avoid unexpected results
-      */
--    if (len > 5 && memcmp(path, "%pipe", 5) != 0) {
-+    if (path[0] == '|' || (len > 5 && memcmp(path, "%pipe", 5) == 0)) {
-         buffer = (char *)gs_alloc_bytes(core->memory, len + 1, "gs_add_control_path_len");
-         if (buffer == NULL)
-             return gs_error_VMerror;
-@@ -850,7 +850,7 @@ gs_remove_control_path_len_flags(const gs_memory_t *mem, gs_path_control_t type,
-     /* "%pipe%" do not follow the normal rules for path definitions, so we
-        don't "reduce" them to avoid unexpected results
-      */
--    if (len > 5 && memcmp(path, "%pipe", 5) != 0) {
-+    if (path[0] == '|' || (len > 5 && memcmp(path, "%pipe", 5) == 0)) {
-         buffer = (char *)gs_alloc_bytes(core->memory, len + 1, "gs_remove_control_path_len");
-         if (buffer == NULL)
-             return gs_error_VMerror;
--- 
-2.34.1
-
diff --git a/gnu/packages/patches/ghostscript-CVE-2023-36664.patch b/gnu/packages/patches/ghostscript-CVE-2023-36664.patch
deleted file mode 100644
index e9c53c1f87..0000000000
--- a/gnu/packages/patches/ghostscript-CVE-2023-36664.patch
+++ /dev/null
@@ -1,142 +0,0 @@
-From 505eab7782b429017eb434b2b95120855f2b0e3c Mon Sep 17 00:00:00 2001
-From: Chris Liddell <[email-masked]>
-Date: Wed, 7 Jun 2023 10:23:06 +0100
-Subject: [PATCH] Bug 706761: Don't "reduce" %pipe% file names for permission
- validation
-
-For regular file names, we try to simplfy relative paths before we use them.
-
-Because the %pipe% device can, effectively, accept command line calls, we
-shouldn't be simplifying that string, because the command line syntax can end
-up confusing the path simplifying code. That can result in permitting a pipe
-command which does not match what was originally permitted.
-
-Special case "%pipe" in the validation code so we always deal with the entire
-string.
----
- base/gpmisc.c   | 31 +++++++++++++++++++--------
- base/gslibctx.c | 56 ++++++++++++++++++++++++++++++++++++-------------
- 2 files changed, 64 insertions(+), 23 deletions(-)
-
-diff --git a/base/gpmisc.c b/base/gpmisc.c
-index 5f39ebba7..2fb87f769 100644
---- a/base/gpmisc.c
-+++ b/base/gpmisc.c
-@@ -1076,16 +1076,29 @@ gp_validate_path_len(const gs_memory_t *mem,
-              && !memcmp(path + cdirstrl, dirsepstr, dirsepstrl)) {
-           prefix_len = 0;
-     }
--    rlen = len+1;
--    bufferfull = (char *)gs_alloc_bytes(mem->thread_safe_memory, rlen + prefix_len, "gp_validate_path");
--    if (bufferfull == NULL)
--        return gs_error_VMerror;
--
--    buffer = bufferfull + prefix_len;
--    if (gp_file_name_reduce(path, (uint)len, buffer, &rlen) != gp_combine_success)
--        return gs_error_invalidfileaccess;
--    buffer[rlen] = 0;
- 
-+    /* "%pipe%" do not follow the normal rules for path definitions, so we
-+       don't "reduce" them to avoid unexpected results
-+     */
-+    if (len > 5 && memcmp(path, "%pipe", 5) != 0) {
-+        bufferfull = buffer = (char *)gs_alloc_bytes(mem->thread_safe_memory, len + 1, "gp_validate_path");
-+        if (buffer == NULL)
-+            return gs_error_VMerror;
-+        memcpy(buffer, path, len);
-+        buffer[len] = 0;
-+        rlen = len;
-+    }
-+    else {
-+        rlen = len+1;
-+        bufferfull = (char *)gs_alloc_bytes(mem->thread_safe_memory, rlen + prefix_len, "gp_validate_path");
-+        if (bufferfull == NULL)
-+            return gs_error_VMerror;
-+
-+        buffer = bufferfull + prefix_len;
-+        if (gp_file_name_reduce(path, (uint)len, buffer, &rlen) != gp_combine_success)
-+            return gs_error_invalidfileaccess;
-+        buffer[rlen] = 0;
-+    }
-     while (1) {
-         switch (mode[0])
-         {
-diff --git a/base/gslibctx.c b/base/gslibctx.c
-index eb566ed06..d2a1aa91d 100644
---- a/base/gslibctx.c
-+++ b/base/gslibctx.c
-@@ -740,14 +740,28 @@ gs_add_control_path_len_flags(const gs_memory_t *mem, gs_path_control_t type, co
-             return gs_error_rangecheck;
-     }
- 
--    rlen = len+1;
--    buffer = (char *)gs_alloc_bytes(core->memory, rlen, "gp_validate_path");
--    if (buffer == NULL)
--        return gs_error_VMerror;
-+    /* "%pipe%" do not follow the normal rules for path definitions, so we
-+       don't "reduce" them to avoid unexpected results
-+     */
-+    if (len > 5 && memcmp(path, "%pipe", 5) != 0) {
-+        buffer = (char *)gs_alloc_bytes(core->memory, len + 1, "gs_add_control_path_len");
-+        if (buffer == NULL)
-+            return gs_error_VMerror;
-+        memcpy(buffer, path, len);
-+        buffer[len] = 0;
-+        rlen = len;
-+    }
-+    else {
-+        rlen = len + 1;
- 
--    if (gp_file_name_reduce(path, (uint)len, buffer, &rlen) != gp_combine_success)
--        return gs_error_invalidfileaccess;
--    buffer[rlen] = 0;
-+        buffer = (char *)gs_alloc_bytes(core->memory, rlen, "gs_add_control_path_len");
-+        if (buffer == NULL)
-+            return gs_error_VMerror;
-+
-+        if (gp_file_name_reduce(path, (uint)len, buffer, &rlen) != gp_combine_success)
-+            return gs_error_invalidfileaccess;
-+        buffer[rlen] = 0;
-+    }
- 
-     n = control->num;
-     for (i = 0; i < n; i++)
-@@ -833,14 +847,28 @@ gs_remove_control_path_len_flags(const gs_memory_t *mem, gs_path_control_t type,
-             return gs_error_rangecheck;
-     }
- 
--    rlen = len+1;
--    buffer = (char *)gs_alloc_bytes(core->memory, rlen, "gp_validate_path");
--    if (buffer == NULL)
--        return gs_error_VMerror;
-+    /* "%pipe%" do not follow the normal rules for path definitions, so we
-+       don't "reduce" them to avoid unexpected results
-+     */
-+    if (len > 5 && memcmp(path, "%pipe", 5) != 0) {
-+        buffer = (char *)gs_alloc_bytes(core->memory, len + 1, "gs_remove_control_path_len");
-+        if (buffer == NULL)
-+            return gs_error_VMerror;
-+        memcpy(buffer, path, len);
-+        buffer[len] = 0;
-+        rlen = len;
-+    }
-+    else {
-+        rlen = len+1;
- 
--    if (gp_file_name_reduce(path, (uint)len, buffer, &rlen) != gp_combine_success)
--        return gs_error_invalidfileaccess;
--    buffer[rlen] = 0;
-+        buffer = (char *)gs_alloc_bytes(core->memory, rlen, "gs_remove_control_path_len");
-+        if (buffer == NULL)
-+            return gs_error_VMerror;
-+
-+        if (gp_file_name_reduce(path, (uint)len, buffer, &rlen) != gp_combine_success)
-+            return gs_error_invalidfileaccess;
-+        buffer[rlen] = 0;
-+    }
- 
-     n = control->num;
-     for (i = 0; i < n; i++) {
--- 
-2.34.1
-
diff --git a/gnu/packages/patches/ghostscript-leptonica-hurd.patch b/gnu/packages/patches/ghostscript-leptonica-hurd.patch
deleted file mode 100644
index 124365b3e6..0000000000
--- a/gnu/packages/patches/ghostscript-leptonica-hurd.patch
+++ /dev/null
@@ -1,78 +0,0 @@
-Upstream status: This patch was taken from leptonica upstream.
-
-Backported to ghostscripts bundled leptonica.
-
-From f04da7c816feb1d5f689c34f3d0e7e3621edf1f5 Mon Sep 17 00:00:00 2001
-From: Samuel Thibault <[email-masked]>
-Date: Wed, 1 Feb 2023 19:35:43 +0100
-Subject: [PATCH] Fix GNU/Hurd build
-
-There is no PATH_MAX limitation on GNU/Hurd, and realpath() can be
-safely be used with its second parameter set to NULL (as required by
-posix since its version 2001).
----
- src/sarray1.c | 29 +++++++++++++++++++++++------
- 1 file changed, 23 insertions(+), 6 deletions(-)
-
---- a/src/sarray1.c	2023-06-13 12:31:13.393672916 +0200
-+++ a/src/sarray1.c	2023-06-13 12:34:13.574237149 +0200
-@@ -1953,7 +1953,11 @@
- SARRAY *
- getFilenamesInDirectory(const char  *dirname)
- {
-+#if _POSIX_VERSION >= 200112 || defined(__GLIBC__)
-+char           *dir;
-+#else
- char            dir[PATH_MAX + 1];
-+#endif
- char           *realdir, *stat_path, *ignore;
- size_t          size;
- SARRAY         *safiles;
-@@ -1976,17 +1980,28 @@
-             * If the file or directory exists, realpath returns its path;
-               else it returns NULL.
-             * If the second arg to realpath is passed in, the canonical path
--              is returned there.  Use a buffer of sufficient size.  If the
--              second arg is NULL, the path is malloc'd and returned if the
--              file or directory exists.
--           We pass in a buffer for the second arg, and check that the canonical
--           directory path was made.  The existence of the directory is checked
--           later, after its actual path is returned by genPathname().  */
-+              is returned there.  Use a buffer of sufficient size.
-+              We pass in a buffer for the second arg, and check that the
-+              canonical directory path was made.  The existence of the
-+              directory is checked later, after its actual path is returned by
-+              genPathname().
-+              With GNU libc or Posix 2001, if the second arg is NULL, the path
-+              is malloc'd and returned if the file or directory exists.
-+           */
-+#if _POSIX_VERSION >= 200112 || defined(__GLIBC__)
-+    dir = realpath(dirname, NULL);
-+    if (dir == NULL)
-+        return (SARRAY *)ERROR_PTR("dir not made", __func__, NULL);
-+#else
-     dir[0] = '\0';  /* init empty in case realpath() fails to write it */
-     ignore = realpath(dirname, dir);
-     if (dir[0] == '\0')
-         return (SARRAY *)ERROR_PTR("dir not made", procName, NULL);
-+#endif
-     realdir = genPathname(dir, NULL);
-+#if _POSIX_VERSION >= 200112 || defined(__GLIBC__)
-+    LEPT_FREE(dir);
-+#endif
-     if ((pdir = opendir(realdir)) == NULL) {
-         LEPT_FREE(realdir);
-         return (SARRAY *)ERROR_PTR("pdir not opened", procName, NULL);
-@@ -1998,10 +2013,12 @@
-         stat_ret = fstatat(dfd, pdirentry->d_name, &st, 0);
- #else
-         size = strlen(realdir) + strlen(pdirentry->d_name) + 2;
-+#if _POSIX_VERSION < 200112 && !defined(__GLIBC__)
-         if (size > PATH_MAX) {
-             L_ERROR("size = %zu too large; skipping\n", procName, size);
-             continue;
-         }
-+#endif
-         stat_path = (char *)LEPT_CALLOC(size, 1);
-         snprintf(stat_path, size, "%s/%s", realdir, pdirentry->d_name);
-         stat_ret = stat(stat_path, &st);
diff --git a/gnu/packages/patches/ghostscript-no-header-creationdate.patch b/gnu/packages/patches/ghostscript-no-header-creationdate.patch
deleted file mode 100644
index 493693b78d..0000000000
--- a/gnu/packages/patches/ghostscript-no-header-creationdate.patch
+++ /dev/null
@@ -1,46 +0,0 @@
-This patch makes emission of /CreationDate and /ModDate headers optional.
-
-If the environment variable GS_GENERATE_UUIDS is set to "0" or "no", it will
-not write out the "/ID" field (if that's permissible).
-
-Upstream does not want to do this.
-
-See: https://bugs.ghostscript.com/show_bug.cgi?id=698208
-diff --git a/devices/vector/gdevpdf.c b/devices/vector/gdevpdf.c
-index cb268f62e..c5abefde2 100644
---- a/devices/vector/gdevpdf.c
-+++ b/devices/vector/gdevpdf.c
-@@ -425,6 +425,9 @@ pdf_initialize_ids(gx_device_pdf * pdev)
-      * date and time, rather than (for example) %%CreationDate from the
-      * PostScript file.  We think this is wrong, but we do the same.
-      */
-+    if (!getenv("GS_GENERATE_UUIDS") ||
-+        (strcasecmp(getenv("GS_GENERATE_UUIDS"), "0") != 0 &&
-+         strcasecmp(getenv("GS_GENERATE_UUIDS"), "no") != 0))
-     {
-         struct tm tms;
-         time_t t;
-diff --git a/devices/vector/gdevpdfe.c b/devices/vector/gdevpdfe.c
-index ec011d8ba..f083f1e93 100644
---- a/devices/vector/gdevpdfe.c
-+++ b/devices/vector/gdevpdfe.c
-@@ -696,6 +696,9 @@ pdf_write_document_metadata(gx_device_pdf *pdev, const byte digest[6])
-             pdf_xml_attribute_name(s, "xmlns:xmp");
-             pdf_xml_attribute_value(s, "http://ns.adobe.com/xap/1.0/");
-             pdf_xml_tag_end(s);
-+            if (!getenv("GS_GENERATE_UUIDS") ||
-+                (strcasecmp(getenv("GS_GENERATE_UUIDS"), "0") != 0 &&
-+                 strcasecmp(getenv("GS_GENERATE_UUIDS"), "no") != 0))
-             {
-                 pdf_xml_tag_open_beg(s, "xmp:ModifyDate");
-                 pdf_xml_tag_end(s);
-@@ -704,6 +707,9 @@ pdf_write_document_metadata(gx_device_pdf *pdev, const byte digest[6])
-                 pdf_xml_tag_close(s, "xmp:ModifyDate");
-                 pdf_xml_newline(s);
-             }
-+            if (!getenv("GS_GENERATE_UUIDS") ||
-+                (strcasecmp(getenv("GS_GENERATE_UUIDS"), "0") != 0 &&
-+                 strcasecmp(getenv("GS_GENERATE_UUIDS"), "no") != 0))
-             {
-                 pdf_xml_tag_open_beg(s, "xmp:CreateDate");
-                 pdf_xml_tag_end(s);
diff --git a/gnu/packages/patches/ghostscript-no-header-id.patch b/gnu/packages/patches/ghostscript-no-header-id.patch
deleted file mode 100644
index ef518b0fb2..0000000000
--- a/gnu/packages/patches/ghostscript-no-header-id.patch
+++ /dev/null
@@ -1,57 +0,0 @@
-This patch makes the "/ID" field optional.
-
-If the environment variable GS_GENERATE_UUIDS is set to "0" or "no", it will
-not write out the "/ID" field (if that's permissible).
-
-Upstream does not want to do this.
-
-See: https://bugs.ghostscript.com/show_bug.cgi?id=698208
-diff --git a/devices/vector/gdevpdf.c b/devices/vector/gdevpdf.c
-index c5abefde2..3859fc088 100644
---- a/devices/vector/gdevpdf.c
-+++ b/devices/vector/gdevpdf.c
-@@ -1909,8 +1909,11 @@ static int pdf_linearise(gx_device_pdf *pdev, pdf_linearisation_t *linear_params
-      * +1 for the linearisation dict and +1 for the primary hint stream.
-      */
-     linear_params->FirsttrailerOffset = gp_ftell(linear_params->Lin_File.file);
--    gs_snprintf(LDict, sizeof(LDict), "\ntrailer\n<</Size %ld/Info %d 0 R/Root %d 0 R/ID[%s%s]/Prev %d>>\nstartxref\r\n0\n%%%%EOF\n        \n",
--        linear_params->LastResource + 3, pdev->ResourceUsage[linear_params->Info_id].NewObjectNumber, pdev->ResourceUsage[linear_params->Catalog_id].NewObjectNumber, fileID, fileID, 0);
-+    gs_snprintf(LDict, sizeof(LDict), "\ntrailer\n<</Size %ld/Info %d 0 R/Root %d 0 R",
-+        linear_params->LastResource + 3, pdev->ResourceUsage[linear_params->Info_id].NewObjectNumber, pdev->ResourceUsage[linear_params->Catalog_id].NewObjectNumber);
-+    if (pdev->OwnerPassword.size > 0 || !(!getenv("GS_GENERATE_UUIDS") || (strcasecmp(getenv("GS_GENERATE_UUIDS"), "0") != 0 && strcasecmp(getenv("GS_GENERATE_UUIDS"), "no") != 0))) /* ID is mandatory when encrypting */
-+        gs_snprintf(LDict, sizeof(LDict), "/ID[%s%s]", fileID, fileID);
-+    gs_snprintf(LDict, sizeof(LDict), "/Prev %d>>\nstartxref\r\n0\n%%%%EOF\n        \n", 0);
-     gp_fwrite(LDict, strlen(LDict), 1, linear_params->Lin_File.file);
- 
-     /* Write document catalog (Part 4) */
-@@ -2445,8 +2448,11 @@ static int pdf_linearise(gx_device_pdf *pdev, pdf_linearisation_t *linear_params
-     if (code != 0)
-         return_error(gs_error_ioerror);
- 
--    gs_snprintf(LDict, sizeof(LDict), "\ntrailer\n<</Size %ld/Info %d 0 R/Root %d 0 R/ID[%s%s]/Prev %"PRId64">>\nstartxref\r\n0\n%%%%EOF\n",
--        linear_params->LastResource + 3, pdev->ResourceUsage[linear_params->Info_id].NewObjectNumber, pdev->ResourceUsage[linear_params->Catalog_id].NewObjectNumber, fileID, fileID, mainxref);
-+    gs_snprintf(LDict, sizeof(LDict), "\ntrailer\n<</Size %ld/Info %d 0 R/Root %d 0 R",
-+        linear_params->LastResource + 3, pdev->ResourceUsage[linear_params->Info_id].NewObjectNumber, pdev->ResourceUsage[linear_params->Catalog_id].NewObjectNumber);
-+    if (pdev->OwnerPassword.size > 0 || !(!getenv("GS_GENERATE_UUIDS") || (strcasecmp(getenv("GS_GENERATE_UUIDS"), "0") != 0 || strcasecmp(getenv("GS_GENERATE_UUIDS"), "no") != 0))) /* ID is mandatory when encrypting */
-+        gs_snprintf(LDict, sizeof(LDict), "/ID[%s%s]", fileID, fileID);
-+    gs_snprintf(LDict, sizeof(LDict), "/Prev %"PRId64">>\nstartxref\r\n0\n%%%%EOF\n", mainxref);
-     gp_fwrite(LDict, strlen(LDict), 1, linear_params->sfile);
- 
-     code = gp_fseek(linear_params->sfile, pdev->ResourceUsage[HintStreamObj].LinearisedOffset, SEEK_SET);
-@@ -3139,10 +3145,12 @@ pdf_close(gx_device * dev)
-             stream_puts(s, "trailer\n");
-             pprintld3(s, "<< /Size %ld /Root %ld 0 R /Info %ld 0 R\n",
-                   pdev->next_id, Catalog_id, Info_id);
--            stream_puts(s, "/ID [");
--            psdf_write_string(pdev->strm, pdev->fileID, sizeof(pdev->fileID), 0);
--            psdf_write_string(pdev->strm, pdev->fileID, sizeof(pdev->fileID), 0);
--            stream_puts(s, "]\n");
-+            if (pdev->OwnerPassword.size > 0 || !(!getenv("GS_GENERATE_UUIDS") || (strcasecmp(getenv("GS_GENERATE_UUIDS"), "0") != 0 || strcasecmp(getenv("GS_GENERATE_UUIDS"), "no") != 0))) { /* ID is mandatory when encrypting */
-+                stream_puts(s, "/ID [");
-+                psdf_write_string(pdev->strm, pdev->fileID, sizeof(pdev->fileID), 0);
-+                psdf_write_string(pdev->strm, pdev->fileID, sizeof(pdev->fileID), 0);
-+                stream_puts(s, "]\n");
-+            }
-             if (pdev->OwnerPassword.size > 0) {
-                 pprintld1(s, "/Encrypt %ld 0 R ", Encrypt_id);
-             }
diff --git a/gnu/packages/patches/ghostscript-no-header-uuid.patch b/gnu/packages/patches/ghostscript-no-header-uuid.patch
deleted file mode 100644
index b277a4bc2e..0000000000
--- a/gnu/packages/patches/ghostscript-no-header-uuid.patch
+++ /dev/null
@@ -1,49 +0,0 @@
-This patch makes the UUIDs in the XMP header optional, depending on the
-setting of the environment variable GS_GENERATE_UUIDS.
-
-If the environment variable GS_GENERATE_UUIDS is set to "0" or "no", it will
-not write out the Document UUID field and also will write the Instance ID
-field value as "".
-
-Upstream does not want to do this.
-
-See: https://bugs.ghostscript.com/show_bug.cgi?id=698208
-diff --git a/devices/vector/gdevpdfe.c b/devices/vector/gdevpdfe.c
-index f083f1e93..a19c64ac0 100644
---- a/devices/vector/gdevpdfe.c
-+++ b/devices/vector/gdevpdfe.c
-@@ -624,7 +624,7 @@ pdf_write_document_metadata(gx_device_pdf *pdev, const byte digest[6])
-         return code;
- 
-     /* PDF/A XMP reference recommends setting UUID to empty. If not empty must be a URI */
--    if (pdev->PDFA != 0)
-+    if (pdev->PDFA != 0 || (getenv("GS_GENERATE_UUIDS") && (strcasecmp(getenv("GS_GENERATE_UUIDS"), "0") == 0 || strcasecmp(getenv("GS_GENERATE_UUIDS"), "no") == 0)))
-         instance_uuid[0] = 0x00;
- 
-     cre_date_time_len = pdf_get_docinfo_item(pdev, "/CreationDate", cre_date_time, sizeof(cre_date_time));
-@@ -730,14 +730,17 @@ pdf_write_document_metadata(gx_device_pdf *pdev, const byte digest[6])
-             pdf_xml_tag_close(s, "rdf:Description");
-             pdf_xml_newline(s);
- 
--            pdf_xml_tag_open_beg(s, "rdf:Description");
--            pdf_xml_copy(s, " rdf:about=\"\"");
--            pdf_xml_attribute_name(s, "xmlns:xapMM");
--            pdf_xml_attribute_value(s, "http://ns.adobe.com/xap/1.0/mm/");
--            pdf_xml_attribute_name(s, "xapMM:DocumentID");
--            pdf_xml_attribute_value(s, document_uuid);
--            pdf_xml_tag_end_empty(s);
--            pdf_xml_newline(s);
-+            if (!getenv("GS_GENERATE_UUIDS") || (strcasecmp(getenv("GS_GENERATE_UUIDS"), "0") != 0 && strcasecmp(getenv("GS_GENERATE_UUIDS"), "no") != 0))
-+            {
-+                pdf_xml_tag_open_beg(s, "rdf:Description");
-+                pdf_xml_copy(s, " rdf:about=\"\"");
-+                pdf_xml_attribute_name(s, "xmlns:xapMM");
-+                pdf_xml_attribute_value(s, "http://ns.adobe.com/xap/1.0/mm/");
-+                pdf_xml_attribute_name(s, "xapMM:DocumentID");
-+                pdf_xml_attribute_value(s, document_uuid);
-+                pdf_xml_tag_end_empty(s);
-+                pdf_xml_newline(s);
-+            }
- 
-             pdf_xml_tag_open_beg(s, "rdf:Description");
-             pdf_xml_copy(s, " rdf:about=\"\"");
diff --git a/gnu/packages/patches/ghostscript-reproducibility.patch b/gnu/packages/patches/ghostscript-reproducibility.patch
new file mode 100644
index 0000000000..9afb052a09
--- /dev/null
+++ b/gnu/packages/patches/ghostscript-reproducibility.patch
@@ -0,0 +1,26 @@
+Set the OmitDateInfo, OmitXMP and OmitID controls to 1 if the environment
+variable GS_GENERATE_UUIDS is set to "0" or "no".
+
+For reference, see:
+https://git.ghostscript.com/?p=ghostpdl.git;a=commit;h=1158b25fe28f9d60c65417712f16a0f9cccac95c
+and https://bugs.ghostscript.com/show_bug.cgi?id=698208
+
+diff --git a/devices/vector/gdevpdfp.c b/devices/vector/gdevpdfp.c
+index 1fdfeae..6c79157 100644
+--- a/devices/vector/gdevpdfp.c
++++ b/devices/vector/gdevpdfp.c
+@@ -669,6 +669,14 @@ gdev_pdf_put_params_impl(gx_device * dev, const gx_device_pdf * save_dev, gs_par
+         param_signal_error(plist, param_name, code);
+     }
+ 
++    if (getenv("GS_GENERATE_UUIDS") &&
++        (strcasecmp(getenv("GS_GENERATE_UUIDS"), "0") == 0 ||
++         strcasecmp(getenv("GS_GENERATE_UUIDS"), "no") == 0)) {
++        pdev->OmitInfoDate = 1;
++        pdev->OmitID = 1;
++        pdev->OmitXMP = 1;
++    }
++
+     if (pdev->OmitInfoDate && pdev->PDFX != 0) {
+         emprintf(pdev->memory, "\nIt is not possible to omit the CreationDate when creating PDF/X\nOmitInfoDate is being ignored.\n");
+         pdev->OmitInfoDate = 0;
-- 
2.45.1