From 2bf6953f97d5dbe8af9a86703f69766686c6f91a Mon Sep 17 00:00:00 2001 From: Ivan Trubach Date: Sun, 18 Aug 2024 13:20:51 +0300 Subject: [PATCH 1/4] stdenv: add applyPatches and support patch directories --- doc/stdenv/stdenv.chapter.md | 27 ++++++++++- pkgs/stdenv/generic/setup.sh | 94 ++++++++++++++++++++++++++++-------- 2 files changed, 99 insertions(+), 22 deletions(-) diff --git a/doc/stdenv/stdenv.chapter.md b/doc/stdenv/stdenv.chapter.md index 35330305f1891..c2207d61fdb10 100644 --- a/doc/stdenv/stdenv.chapter.md +++ b/doc/stdenv/stdenv.chapter.md @@ -610,7 +610,7 @@ The unpack phase evaluates the string `$unpackCmd` for any unrecognised file. Th ### The patch phase {#ssec-patch-phase} -The patch phase applies the list of patches defined in the `patches` variable. +The patch phase applies the list of patches defined in the `patches` variable using the [`applyPatches`](#fun-applyPatches) function. #### Variables controlling the patch phase {#variables-controlling-the-patch-phase} @@ -620,7 +620,7 @@ Set to true to skip the patch phase. ##### `patches` {#var-stdenv-patches} -The list of patches. They must be in the format accepted by the `patch` command, and may optionally be compressed using `gzip` (`.gz`), `bzip2` (`.bz2`) or `xz` (`.xz`). +The list of patches. They must be in the format accepted by the `patch` command, and may optionally be compressed using `gzip` (`.gz`), `bzip2` (`.bz2`) or `xz` (`.xz`, `.lzma`). ##### `patchFlags` {#var-stdenv-patchFlags} @@ -1136,6 +1136,29 @@ Example removing all references to the compiler in the output: } ``` +### `applyPatches` [ `-p` \ ... ] [--] [ \ ... ] {#fun-applyPatches} + +Applies a set of patches to the current directory. Patch files are automatically +decompressed using `gzip` (`.gz`), `bzip2` (`.bz2`) or `xz` (`.xz`, `.lzma`) +based on the file name extension. Additional arguments are passed directly to +the `patch` command. + +A patch set is either a path to an individual patch file or a directory containing +multiple patch files. If a directory contains a `series` file, it is treated as +a list of patch files to apply. The `series` file lists patch file paths, one +per line, relative to the patch set directory. Empty lines and comments (lines +starting with `#`) are ignored. It is an error if a line starts with either `-` +or `+`. + +When processing a directory without a `series` file, only files with standard +patch extensions `.patch` and `.diff` will be considered for application, +optionally with compressed file name extension (e.g. `.patch.gz`). + +Example: +```shell +applyPatches -p patches -- -p1 +``` + ### `substitute` \ \ \ {#fun-substitute} Performs string substitution on the contents of \, writing the result to \. The substitutions in \ are of the following form: diff --git a/pkgs/stdenv/generic/setup.sh b/pkgs/stdenv/generic/setup.sh index 1b4f7a89d358e..1358a075df31d 100644 --- a/pkgs/stdenv/generic/setup.sh +++ b/pkgs/stdenv/generic/setup.sh @@ -1314,38 +1314,92 @@ unpackPhase() { runHook postUnpack } +# Docs in doc/stdenv/stdenv.chapter.md +# See https://nixos.org/manual/nixpkgs/unstable/#fun-applyPatches +applyPatches() { + local -a patchSets=() patchFlags + local o OPTIND OPTARG + while getopts p: o; do + case "$o" in + p) + patchSets+=( "$OPTARG" ) + ;; + esac + done + shift $((OPTIND-1)) + patchFlags=( "$@" ) + + local -a patchFiles=() + local p + + local -i n + local line lines + for p in "${patchSets[@]}"; do + if [[ -f $p/series ]]; then + # We support a simple series file with a list of patches. Empty + # lines and comments are ignored. Guards-like conditions (see + # guards(1) from quilt) are explicitly forbidden to avoid breaking + # changes if support is added in the future. + mapfile -t lines <"$p/series" + n=0 + for line in "${lines[@]}"; do + case "$line" in + -* | +*) + echo "invalid line in patch series file $p/series: $line" >&2 + return 1 + ;; + "" | "#"*) + continue + ;; + esac + lines[n]=$p/$line + n+=1 + done + patchFiles+=( "${lines[@]::n}" ) + elif [[ -d $p ]]; then + # NB nullglob is set in stdenv. + patchFiles+=( "$p"/*.{patch,diff}{,.gz,.bz2,.xz,.lzma} ) + else + patchFiles+=( "$p" ) + fi + done -patchPhase() { - runHook prePatch - - local -a patchesArray - concatTo patchesArray patches - - for i in "${patchesArray[@]}"; do - echo "applying patch $i" - local uncompress=cat - case "$i" in + local -a uncompress=() + for p in "${patchFiles[@]}"; do + echo "applying patch $p" + case "$p" in *.gz) - uncompress="gzip -d" + uncompress=(gzip -d) ;; *.bz2) - uncompress="bzip2 -d" + uncompress=(bzip2 -d) ;; *.xz) - uncompress="xz -d" + uncompress=(xz -d) ;; *.lzma) - uncompress="lzma -d" + uncompress=(lzma -d) ;; esac - - local -a flagsArray - : "${patchFlags:=-p1}" - concatTo flagsArray patchFlags # "2>&1" is a hack to make patch fail if the decompressor fails (nonexistent patch, etc.) - # shellcheck disable=SC2086 - $uncompress < "$i" 2>&1 | patch "${flagsArray[@]}" + if [[ ${#uncompress[@]} -gt 0 ]]; then + "${uncompress[@]}" <"$p" 2>&1 | patch "${patchFlags[@]}" + else + patch "${patchFlags[@]}" <"$p" + fi done +} + +patchPhase() { + runHook prePatch + + : "${patchFlags:=-p1}" + + local -a patchesArray flagsArray + concatTo patchesArray patches + concatTo flagsArray patchFlags + + applyPatches "${patchesArray[@]/#/-p}" -- "${flagsArray[@]}" runHook postPatch } From 2f6b44ebecded3cbeb3819355369043b77061bd8 Mon Sep 17 00:00:00 2001 From: Ivan Trubach Date: Mon, 19 Aug 2024 11:48:22 +0300 Subject: [PATCH 2/4] tests.trivial-builders: test applyPatches --- .../trivial-builders/test/apply-patches.nix | 498 ++++++++++++++++++ .../trivial-builders/test/default.nix | 1 + 2 files changed, 499 insertions(+) create mode 100644 pkgs/build-support/trivial-builders/test/apply-patches.nix diff --git a/pkgs/build-support/trivial-builders/test/apply-patches.nix b/pkgs/build-support/trivial-builders/test/apply-patches.nix new file mode 100644 index 0000000000000..7c91a58b580aa --- /dev/null +++ b/pkgs/build-support/trivial-builders/test/apply-patches.nix @@ -0,0 +1,498 @@ +/* + To run: + + cd nixpkgs + nix build --file . tests.trivial-builders.applyPatches + + or to run an individual test case + + cd nixpkgs + nix build --file . tests.trivial-builders.applyPatches.foo +*/ +{ + lib, + testers, + runCommand, + applyPatches, + writeText, + writeTextDir, + emptyDirectory, +}: +lib.recurseIntoAttrs { + defaultArguments = testers.testEqualContents { + assertion = "applyPatches succeeds without arguments except for src"; + expected = emptyDirectory; + actual = applyPatches { src = emptyDirectory; }; + }; + + emptyArguments = testers.testEqualContents { + assertion = "applyPatches succeeds with empty arguments"; + expected = emptyDirectory; + actual = applyPatches { + src = emptyDirectory; + patches = [ ]; + postPatch = ""; + prePatch = ""; + }; + }; + + missingPatch = testers.testBuildFailure (applyPatches { + src = emptyDirectory; + patches = [ "missing.patch" ]; + }); + + patchWithIncorrectFormat = testers.testBuildFailure (applyPatches { + src = emptyDirectory; + patches = [ (writeText "invalid.patch" "invalid patch content") ]; + }); + + patchWithFlags = testers.testEqualContents { + assertion = "applyPatches applies patch with -p0 flag"; + expected = writeTextDir "content.txt" "Patched content\n"; + actual = applyPatches { + src = writeTextDir "content.txt" "Original content\n"; + patches = [ + (writeText "content.patch" '' + --- content.txt + +++ content.txt + @@ -1 +1 @@ + -Original content + +Patched content + '') + ]; + patchFlags = [ "-p0" ]; + }; + }; + + patchWithEmptyDiff = testers.testEqualContents { + assertion = "applyPatches handles patch with empty diff"; + expected = writeTextDir "file.txt" "original content"; + actual = applyPatches { + src = writeTextDir "file.txt" "original content"; + patches = [ + (writeText "empty-patch.patch" '' + diff --git a/file.txt b/file.txt + index e69de29..d20e9cd 100644 + --- a/file.txt + +++ b/file.txt + '') + ]; + }; + }; + + patchWithIncorrectFilePath = testers.testBuildFailure (applyPatches { + src = writeTextDir "file1.txt" "content"; + patches = [ + (writeText "incorrect-path.patch" '' + --- a/file2.txt + +++ b/file2.txt + @@ -1 +1 @@ + -old content + +new content + '') + ]; + }); + + alreadyAppliedPatch = testers.testBuildFailure (applyPatches { + src = writeTextDir "file.txt" "patched content\n"; + patches = [ + (writeText "$out/patch.patch" '' + --- a/file.txt + +++ b/file.txt + @@ -1 +1 @@ + -original content + +patched content + '') + ]; + }); + + patchFromUnpackedSource = testers.testEqualContents { + assertion = "applyPatches uses patches from working directory with unpacked source"; + expected = writeTextDir "config.toml" '' + key1 = "value1" + key2 = "value2" + ''; + actual = applyPatches { + src = runCommand "patchFromUnpackedSource-src" { } '' + mkdir -p "$out" + cat >"$out/config.toml" <<'EOF' + key1 = "value1" + # Missing key2 + EOF + cat >"$out/config.patch" <<'EOF' + --- a/config.toml + +++ b/config.toml + @@ -1,2 +1,2 @@ + key1 = "value1" + -# Missing key2 + +key2 = "value2" + EOF + ''; + patches = [ "config.patch" ]; + postPatch = '' + rm config.patch + ''; + }; + }; + + patchWithGitExtendedHeaders = testers.testEqualContents { + assertion = "applyPatches supports patches with Git extended headers"; + expected = runCommand "patchWithGitExtendedHeaders-expected" { } '' + mkdir -p "$out" + cat >"$out/config.toml" <<'EOF' + key1 = "value1" + key2 = "value2" + EOF + cat >"$out/data.json" <<'EOF' + {"value": 42} + EOF + ''; + actual = applyPatches { + src = runCommand "patchWithGitExtendedHeaders-src" { } '' + mkdir -p "$out" + cat >"$out/config.toml" <<'EOF' + key1 = "value1" + EOF + cat >"$out/old-data.json" <<'EOF' + {"old_value":37} + EOF + ''; + patches = [ + (writeText "0001-add-key2-to-config.patch" '' + diff --git a/config.toml b/config.toml + index e69de29..d20e9cd 100644 + --- a/config.toml + +++ b/config.toml + @@ -1 +1,2 @@ + key1 = "value1" + +key2 = "value2" + '') + (writeText "0002-rename-and-update-data.patch" '' + diff --git a/old-data.json b/data.json + similarity index 25 dissimilarity index 75 + rename from old-data.json + rename to data.json + --- a/old-data.json + +++ b/data.json + @@ -1 +1 @@ + -{"old_value":37} + +{"value": 42} + '') + ]; + }; + }; + + emptyPatchSeries = testers.testEqualContents { + assertion = "applyPatches succeeds with empty patch series file"; + expected = emptyDirectory; + actual = applyPatches { + src = emptyDirectory; + patches = [ (writeTextDir "series" "") ]; + }; + }; + + patchSeriesWithMultiplePatches = testers.testEqualContents { + assertion = "applyPatches applies multiple patches from series file"; + expected = runCommand "patchSeriesWithMultiplePatches-expected" { } '' + mkdir -p "$out"/{foo,bar} + cat >"$out/foo/foo" <<'EOF' + foo content + EOF + cat >"$out/bar/bar" <<'EOF' + bar content + EOF + ''; + actual = applyPatches { + src = runCommand "patchSeriesWithMultiplePatches-src" { } '' + mkdir -p "$out/foo" + cat >"$out/init" <<'EOF' + init content + EOF + cat >"$out/foo/init" <<'EOF' + init content + EOF + ''; + patches = [ + (runCommand "patch-series-with-multiple-patches" { } '' + mkdir -p "$out/subdir" + cat >"$out/series" <<'EOF' + 0001-add-foo-foo.patch + ./0002-add-bar-init-bar.patch + + # Comments and empty lines are ignored. + # + subdir/0003-remove-init-files.patch + EOF + cat >"$out/0001-add-foo-foo.patch" <<'EOF' + diff --git a/foo/foo b/foo/foo + new file mode 100644 + index 0000000..257cc56 + --- /dev/null + +++ b/foo/foo + @@ -0,0 +1 @@ + +foo content + EOF + cat >"$out/0002-add-bar-init-bar.patch" <<'EOF' + diff --git a/bar/bar b/bar/bar + new file mode 100644 + index 0000000..5716ca5 + --- /dev/null + +++ b/bar/bar + @@ -0,0 +1 @@ + +bar content + diff --git a/bar/init b/bar/init + new file mode 100644 + index 0000000..b1b7161 + --- /dev/null + +++ b/bar/init + @@ -0,0 +1 @@ + +init content + EOF + cat >"$out/subdir/0003-remove-init-files.patch" <<'EOF' + diff --git a/bar/init b/bar/init + deleted file mode 100644 + index b1b7161..0000000 + --- a/bar/init + +++ /dev/null + @@ -1 +0,0 @@ + -init content + diff --git a/foo/init b/foo/init + deleted file mode 100644 + index b1b7161..0000000 + --- a/foo/init + +++ /dev/null + @@ -1 +0,0 @@ + -init content + diff --git a/init b/init + deleted file mode 100644 + index b1b7161..0000000 + --- a/init + +++ /dev/null + @@ -1 +0,0 @@ + -init content + EOF + '') + ]; + }; + }; + + patchSeriesWithIncorrectFilePath = testers.testBuildFailure (applyPatches { + src = emptyDirectory; + patches = [ + (runCommand "patch-series-with-incorrect-file-path" { } '' + mkdir -p "$out" + cat >"$out/series" <<'EOF' + incorrect-path.patch + EOF + cat >"$out/incorrect-path.patch" <<'EOF' + --- a/nonexistent-file + +++ b/nonexistent-file + @@ -1 +1 @@ + -old content + +new content + EOF + '') + ]; + }); + + patchSeriesInvalidExclude = testers.testBuildFailure (applyPatches { + src = emptyDirectory; + patches = [ + (runCommand "patch-series-invalid-exclude" { } '' + mkdir -p "$out" + touch "$out/0000-excluded.patch" + touch "$out/-xxx 0000-excluded.patch" + cat >"$out/series" <<'EOF' + -xxx 0000-excluded.patch + EOF + '') + ]; + }); + + patchSeriesInvalidInclude = testers.testBuildFailure (applyPatches { + src = emptyDirectory; + patches = [ + (runCommand "patch-series-invalid-include" { } '' + mkdir -p "$out" + touch "$out/0000-included.patch" + touch "$out/+xxx 0000-included.patch" + cat >"$out/series" <<'EOF' + +xxx 0000-included.patch + EOF + '') + ]; + }); + + patchSeriesWithMultipleInvalidCharacters = testers.testBuildFailure (applyPatches { + src = emptyDirectory; + patches = [ + (writeTextDir "series" '' + +invalid-patch + -another-invalid-patch + '') + ]; + }); + + patchSeriesNotAFile = testers.testEqualContents { + assertion = "applyPatches ignores non-file patch series"; + expected = emptyDirectory; + actual = applyPatches { + src = emptyDirectory; + patches = [ + (runCommand "patch-series-not-a-file" { } '' + mkdir -p "$out/series" + '') + ]; + }; + }; + + directoryWithoutSeriesFile = testers.testEqualContents { + assertion = "applyPatches applies single patch from a directory without series file"; + expected = writeTextDir "file.txt" "patched content\n"; + actual = applyPatches { + src = writeTextDir "file.txt" "original content\n"; + patches = [ + (writeTextDir "file.patch" '' + --- a/file.txt + +++ b/file.txt + @@ -1 +1 @@ + -original content + +patched content + '') + ]; + }; + }; + + directoryWithNonPatchFiles = testers.testEqualContents { + assertion = "applyPatches ignores non-patch files in a directory"; + expected = writeTextDir "file.txt" "original content"; + actual = applyPatches { + src = writeTextDir "file.txt" "original content"; + patches = [ + (writeTextDir "file.txt" '' + --- a/file.txt + +++ b/file.txt + @@ -1 +1 @@ + -original content + +patched content + '') + ]; + }; + }; + + patchesInSubdirectory = testers.testEqualContents { + assertion = "applyPatches ignores patches in subdirectories"; + expected = writeTextDir "file.txt" "patched content\n"; + actual = applyPatches { + src = writeTextDir "file.txt" "original content\n"; + patches = [ + (runCommand "patches-in-subdirectory" { } '' + mkdir -p "$out/subdir" + + cat >"$out/file.patch" <<'EOF' + --- a/file.txt + +++ b/file.txt + @@ -1 +1 @@ + -original content + +patched content + EOF + + cat >"$out/subdir/ignored.patch" <<'EOF' + --- a/file.txt + +++ b/file.txt + @@ -1 +1 @@ + -original content + +ignored content + EOF + '') + ]; + }; + }; + + compressedPatchGzip = testers.testEqualContents { + assertion = "applyPatches applies gzip compressed patch"; + expected = writeTextDir "file.txt" "patched content\n"; + actual = applyPatches { + src = writeTextDir "file.txt" "original content\n"; + patches = [ + (runCommand "file-patch.gz" { } '' + gzip >"$out" <<'EOF' + --- a/file.txt + +++ b/file.txt + @@ -1 +1 @@ + -original content + +patched content + EOF + '') + ]; + }; + }; + + compressedPatchBzip2 = testers.testEqualContents { + assertion = "applyPatches applies bzip2 compressed patch"; + expected = writeTextDir "file.txt" "patched content\n"; + actual = applyPatches { + src = writeTextDir "file.txt" "original content\n"; + patches = [ + (runCommand "file-patch.bz2" { } '' + bzip2 >"$out" <<'EOF' + --- a/file.txt + +++ b/file.txt + @@ -1 +1 @@ + -original content + +patched content + EOF + '') + ]; + }; + }; + + compressedPatchXz = testers.testEqualContents { + assertion = "applyPatches applies xz compressed patch"; + expected = writeTextDir "file.txt" "patched content\n"; + actual = applyPatches { + src = writeTextDir "file.txt" "original content\n"; + patches = [ + (runCommand "file-patch.xz" { } '' + xz >"$out" <<'EOF' + --- a/file.txt + +++ b/file.txt + @@ -1 +1 @@ + -original content + +patched content + EOF + '') + ]; + }; + }; + + compressedPatchLzma = testers.testEqualContents { + assertion = "applyPatches applies lzma compressed patch"; + expected = writeTextDir "file.txt" "patched content\n"; + actual = applyPatches { + src = writeTextDir "file.txt" "original content\n"; + patches = [ + (runCommand "file-patch.lzma" { } '' + lzma >"$out" <<'EOF' + --- a/file.txt + +++ b/file.txt + @@ -1 +1 @@ + -original content + +patched content + EOF + '') + ]; + }; + }; + + malformedCompressedPatch = testers.testBuildFailure (applyPatches { + src = writeTextDir "file.txt" "original content"; + patches = [ + (runCommand "malformed-patch.gz" { } '' + cat >"$out" <<'EOF' + # malformed gzip data + EOF + '') + ]; + }); +} diff --git a/pkgs/build-support/trivial-builders/test/default.nix b/pkgs/build-support/trivial-builders/test/default.nix index e1ed0be72bf35..fa2116c9d422c 100644 --- a/pkgs/build-support/trivial-builders/test/default.nix +++ b/pkgs/build-support/trivial-builders/test/default.nix @@ -17,6 +17,7 @@ let references = callPackage ./references {}; in recurseIntoAttrs { + applyPatches = callPackage ./apply-patches.nix {}; concat = callPackage ./concat-test.nix {}; linkFarm = callPackage ./link-farm.nix {}; overriding = callPackage ../test-overriding.nix {}; From 4fbdb505d76f21d6522c758ad5b94498f8200266 Mon Sep 17 00:00:00 2001 From: Ivan Trubach Date: Mon, 19 Aug 2024 16:08:35 +0300 Subject: [PATCH 3/4] applyPatches: move documentation to the Nixpkgs manual --- .../trivial-build-helpers.chapter.md | 38 +++++++++++++++++++ .../trivial-builders/default.nix | 20 +--------- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/doc/build-helpers/trivial-build-helpers.chapter.md b/doc/build-helpers/trivial-build-helpers.chapter.md index 8af68845202f9..fabd7196cc0db 100644 --- a/doc/build-helpers/trivial-build-helpers.chapter.md +++ b/doc/build-helpers/trivial-build-helpers.chapter.md @@ -156,6 +156,44 @@ runCommandWith { ``` ::: +## `applyPatches` {#trivial-builder-applyPatches} + +Applies a list of patches to a source directory like [`patchPhase`](#ssec-patch-phase). + +`src :: Path | Derivation` +: The path to the source directory to patch. + +`name :: String` (optional) +: The name that Nix will append to the store path in the same way that + `stdenv.mkDerivation` uses its `name` attribute. If not set, default is + computed from the [basename](https://nixos.org/manual/nix/stable/language/builtins.html#builtins-baseNameOf) + of the `src` if it is a path or `src.name` if `src` has `name` attribute. + +`patches :: [Path | Derivation]` +: The list of patches to apply like [`patches`](#var-stdenv-patches) argument + of `stdenv.mkDerivation`. + +`prePatch :: String` (optional) +: Shell commands to run before applying patches. + +`postPatch :: String` (optional) +: Shell commands to run after applying patches. + +::: {.example #ex-applypatches-nixpkgs} +# Patching Nixpkgs with `applyPatches` + +```nix +applyPatches { + src = pkgs.path; + patches = [ + (pkgs.fetchpatch { + url = "https://github.com/NixOS/nixpkgs/commit/1f770d20550a413e508e081ddc08464e9d08ba3d.patch"; + hash = "sha256-uXrn1hWfI72QQ8oSTOUps9UZwqF2QwzmkiMPH07on9o="; + }) + ]; +} +``` +::: ## Writing text files {#trivial-builder-text-writing} diff --git a/pkgs/build-support/trivial-builders/default.nix b/pkgs/build-support/trivial-builders/default.nix index 3bccc871a7dca..a1c4d44879e14 100644 --- a/pkgs/build-support/trivial-builders/default.nix +++ b/pkgs/build-support/trivial-builders/default.nix @@ -828,24 +828,8 @@ rec { */ copyPathsToStore = builtins.map copyPathToStore; - # TODO: move applyPatches docs to the Nixpkgs manual - /* Applies a list of patches to a source directory. - - Example: - - # Patching nixpkgs: - - applyPatches { - src = pkgs.path; - patches = [ - (pkgs.fetchpatch { - url = "https://github.com/NixOS/nixpkgs/commit/1f770d20550a413e508e081ddc08464e9d08ba3d.patch"; - sha256 = "1nlzx171y3r3jbk0qhvnl711kmdk57jlq4na8f8bs8wz2pbffymr"; - }) - ]; - } - - */ + # Docs in doc/build-helpers/trivial-build-helpers.chapter.md + # See https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-applyPatches applyPatches = { src , name ? (if builtins.typeOf src == "path" From ef1aa00f61a7a1bc9f573186edc2a683c03c5b72 Mon Sep 17 00:00:00 2001 From: Ivan Trubach Date: Mon, 19 Aug 2024 17:28:11 +0300 Subject: [PATCH 4/4] gnupatch: fix segfault on cleanup See https://savannah.gnu.org/bugs/?57717 Reproducer: ```console $ patch -p1 <<'EOF' diff --git a/file2.txt b/file2.txt index e69de29..d20e9cd 100644 --- a/file2.txt +++ b/file2.txt @@ -1 +1 @@ -old content +new content EOF ``` ``` (repeated lines omitted) patch: **** Can't create file file2.txt.orig : Too many open files patch: **** Can't create file file2.txt.orig : Too many open files patch: **** Can't create file file2.txt.origSegmentation fault (core dumped) ``` --- .../Abort_when_cleaning_up_fails.patch | 51 +++++++++++++++++++ pkgs/tools/text/gnupatch/default.nix | 3 ++ 2 files changed, 54 insertions(+) create mode 100644 pkgs/tools/text/gnupatch/Abort_when_cleaning_up_fails.patch diff --git a/pkgs/tools/text/gnupatch/Abort_when_cleaning_up_fails.patch b/pkgs/tools/text/gnupatch/Abort_when_cleaning_up_fails.patch new file mode 100644 index 0000000000000..ab3baf80f8c17 --- /dev/null +++ b/pkgs/tools/text/gnupatch/Abort_when_cleaning_up_fails.patch @@ -0,0 +1,51 @@ +From b7b028a77bd855f6f56b17c8837fc1cca77b469d Mon Sep 17 00:00:00 2001 +From: Andreas Gruenbacher +Date: Fri, 28 Jun 2019 00:30:25 +0200 +Subject: Abort when cleaning up fails + +When a fatal error triggers during cleanup, another attempt will be made to +clean up, which will likely lead to the same fatal error. So instead, bail out +when that happens. +src/patch.c (cleanup): Bail out when called recursively. +(main): There is no need to call output_files() before cleanup() as cleanup() +already does that. +--- + src/patch.c | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/src/patch.c b/src/patch.c +index 4616a48..02fd982 100644 +--- a/src/patch.c ++++ b/src/patch.c +@@ -685,7 +685,6 @@ main (int argc, char **argv) + } + if (outstate.ofp && (ferror (outstate.ofp) || fclose (outstate.ofp) != 0)) + write_fatal (); +- output_files (NULL); + cleanup (); + delete_files (); + if (somefailed) +@@ -1991,7 +1990,6 @@ void + fatal_exit (int sig) + { + cleanup (); +- + if (sig) + exit_with_signal (sig); + +@@ -2011,6 +2009,12 @@ remove_if_needed (char const *name, bool *needs_removal) + static void + cleanup (void) + { ++ static bool already_cleaning_up; ++ ++ if (already_cleaning_up) ++ return; ++ already_cleaning_up = true; ++ + remove_if_needed (TMPINNAME, &TMPINNAME_needs_removal); + remove_if_needed (TMPOUTNAME, &TMPOUTNAME_needs_removal); + remove_if_needed (TMPPATNAME, &TMPPATNAME_needs_removal); +-- +cgit v1.1 + diff --git a/pkgs/tools/text/gnupatch/default.nix b/pkgs/tools/text/gnupatch/default.nix index c19e087ea5478..19d8652396c91 100644 --- a/pkgs/tools/text/gnupatch/default.nix +++ b/pkgs/tools/text/gnupatch/default.nix @@ -18,6 +18,9 @@ stdenv.mkDerivation rec { # https://git.savannah.gnu.org/cgit/patch.git/patch/?id=b5a91a01e5d0897facdd0f49d64b76b0f02b43e1 ./Allow_input_files_to_be_missing_for_ed-style_patches.patch + # https://git.savannah.gnu.org/cgit/patch.git/patch/?id=b7b028a77bd855f6f56b17c8837fc1cca77b469d + ./Abort_when_cleaning_up_fails.patch + # https://git.savannah.gnu.org/cgit/patch.git/patch/?id=123eaff0d5d1aebe128295959435b9ca5909c26d ./CVE-2018-1000156.patch