From aa1405b6e792e6cb110c46a732ae53e445fc0eaf Mon Sep 17 00:00:00 2001 From: Connor Baker Date: Fri, 3 Jan 2025 23:18:02 +0000 Subject: [PATCH 1/8] stdenv: add no-broken-symlinks hook --- .../setup-hooks/no-broken-symlinks.sh | 85 +++++++++++++++++++ pkgs/stdenv/generic/default.nix | 1 + 2 files changed, 86 insertions(+) create mode 100644 pkgs/build-support/setup-hooks/no-broken-symlinks.sh diff --git a/pkgs/build-support/setup-hooks/no-broken-symlinks.sh b/pkgs/build-support/setup-hooks/no-broken-symlinks.sh new file mode 100644 index 0000000000000..26f4838af5a8f --- /dev/null +++ b/pkgs/build-support/setup-hooks/no-broken-symlinks.sh @@ -0,0 +1,85 @@ +# shellcheck shell=bash +# symlinks are often created in postFixup +# don't use fixupOutputHooks, it is before postFixup +postFixupHooks+=(noBrokenSymlinksInAllOutputs) + +# A symlink is "dangling" if it points to a non-existent target. +# A symlink is "reflexive" if it points to itself. +# A symlink is considered "broken" if it is either dangling or reflexive. +noBrokenSymlinks() { + local -r output="${1:?}" + local path + local pathParent + local symlinkTarget + local errorMessage + local -i numDanglingSymlinks=0 + local -i numReflexiveSymlinks=0 + + # TODO(@connorbaker): This hook doesn't check for cycles in symlinks. + + if [[ ! -e $output ]]; then + nixWarnLog "skipping non-existent output $output" + return 0 + fi + nixInfoLog "running on $output" + + # NOTE: path is absolute because we're running `find` against an absolute path (`output`). + while IFS= read -r -d $'\0' path; do + pathParent="$(dirname "$path")" + symlinkTarget="$(readlink "$path")" + + # Canonicalize symlinkTarget to an absolute path. + if [[ $symlinkTarget == /* ]]; then + nixInfoLog "symlink $path points to absolute target $symlinkTarget" + else + nixInfoLog "symlink $path points to relative target $symlinkTarget" + symlinkTarget="$pathParent/$symlinkTarget" + + # Check to make sure the interpolated target doesn't escape the store path of `output`. + # If it does, Nix probably won't be able to resolve or track dependencies. + if [[ $symlinkTarget != "$output" && $symlinkTarget != "$output"/* ]]; then + nixErrorLog "symlink $path points to target $symlinkTarget, which escapes the current store path $output" + return 1 + fi + fi + + if [[ ! -e $symlinkTarget ]]; then + # symlinkTarget does not exist + errorMessage="the symlink $path points to a missing target $symlinkTarget" + if [[ -z ${allowDanglingSymlinks-} ]]; then + nixErrorLog "$errorMessage" + numDanglingSymlinks+=1 + else + nixInfoLog "$errorMessage" + fi + + elif [[ $path == "$symlinkTarget" ]]; then + # symlinkTarget is exists and is reflexive + errorMessage="the symlink $path is reflexive $symlinkTarget" + if [[ -z ${allowReflexiveSymlinks-} ]]; then + nixErrorLog "$errorMessage" + numReflexiveSymlinks+=1 + else + nixInfoLog "$errorMessage" + fi + + else + # symlinkTarget exists and is irreflexive + nixInfoLog "the symlink $path is irreflexive and points to a target which exists" + fi + done < <(find "$output" -type l -print0) + + if ((numDanglingSymlinks > 0 || numReflexiveSymlinks > 0)); then + nixErrorLog "found $numDanglingSymlinks dangling symlinks and $numReflexiveSymlinks reflexive symlinks" + return 1 + fi + return 0 +} + +noBrokenSymlinksInAllOutputs() { + if [[ -z ${dontCheckForBrokenSymlinks-} ]]; then + for output in $(getAllOutputNames); do + noBrokenSymlinks "${!output}" + done + fi +} diff --git a/pkgs/stdenv/generic/default.nix b/pkgs/stdenv/generic/default.nix index fe9843c6b1202..57a58eb2ebe4b 100644 --- a/pkgs/stdenv/generic/default.nix +++ b/pkgs/stdenv/generic/default.nix @@ -77,6 +77,7 @@ let ../../build-support/setup-hooks/move-sbin.sh ../../build-support/setup-hooks/move-systemd-user-units.sh ../../build-support/setup-hooks/multiple-outputs.sh + ../../build-support/setup-hooks/no-broken-symlinks.sh ../../build-support/setup-hooks/patch-shebangs.sh ../../build-support/setup-hooks/prune-libtool-files.sh ../../build-support/setup-hooks/reproducible-builds.sh From 0ad7c9ee1e2a33a981f81ede385c2f0fc595c6e3 Mon Sep 17 00:00:00 2001 From: Connor Baker Date: Fri, 17 Jan 2025 16:37:26 -0800 Subject: [PATCH 2/8] no-broken-symlinks: guard against double inclusions --- pkgs/build-support/setup-hooks/no-broken-symlinks.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkgs/build-support/setup-hooks/no-broken-symlinks.sh b/pkgs/build-support/setup-hooks/no-broken-symlinks.sh index 26f4838af5a8f..8117da45d80d4 100644 --- a/pkgs/build-support/setup-hooks/no-broken-symlinks.sh +++ b/pkgs/build-support/setup-hooks/no-broken-symlinks.sh @@ -1,4 +1,12 @@ # shellcheck shell=bash + +# Guard against double inclusion. +if (("${noBrokenSymlinksHookInstalled:-0}" > 0)); then + nixInfoLog "skipping because the hook has been propagated more than once" + return 0 +fi +declare -ig noBrokenSymlinksHookInstalled=1 + # symlinks are often created in postFixup # don't use fixupOutputHooks, it is before postFixup postFixupHooks+=(noBrokenSymlinksInAllOutputs) From 9b9badd9578d7a2306177f51c629c5be4a68dcc1 Mon Sep 17 00:00:00 2001 From: Connor Baker Date: Tue, 21 Jan 2025 13:39:44 -0800 Subject: [PATCH 3/8] no-broken-symlinks: check for reflexivity before dangling --- .../setup-hooks/no-broken-symlinks.sh | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pkgs/build-support/setup-hooks/no-broken-symlinks.sh b/pkgs/build-support/setup-hooks/no-broken-symlinks.sh index 8117da45d80d4..142a2fca720fc 100644 --- a/pkgs/build-support/setup-hooks/no-broken-symlinks.sh +++ b/pkgs/build-support/setup-hooks/no-broken-symlinks.sh @@ -51,22 +51,22 @@ noBrokenSymlinks() { fi fi - if [[ ! -e $symlinkTarget ]]; then - # symlinkTarget does not exist - errorMessage="the symlink $path points to a missing target $symlinkTarget" - if [[ -z ${allowDanglingSymlinks-} ]]; then + if [[ $path == "$symlinkTarget" ]]; then + # symlinkTarget is reflexive + errorMessage="the symlink $path is reflexive $symlinkTarget" + if [[ -z ${allowReflexiveSymlinks-} ]]; then nixErrorLog "$errorMessage" - numDanglingSymlinks+=1 + numReflexiveSymlinks+=1 else nixInfoLog "$errorMessage" fi - elif [[ $path == "$symlinkTarget" ]]; then - # symlinkTarget is exists and is reflexive - errorMessage="the symlink $path is reflexive $symlinkTarget" - if [[ -z ${allowReflexiveSymlinks-} ]]; then + elif [[ ! -e $symlinkTarget ]]; then + # symlinkTarget does not exist + errorMessage="the symlink $path points to a missing target $symlinkTarget" + if [[ -z ${allowDanglingSymlinks-} ]]; then nixErrorLog "$errorMessage" - numReflexiveSymlinks+=1 + numDanglingSymlinks+=1 else nixInfoLog "$errorMessage" fi From ba1297b0d3ef7ba992e0d5ffe2712bca71a3b0c2 Mon Sep 17 00:00:00 2001 From: Connor Baker Date: Tue, 21 Jan 2025 13:40:32 -0800 Subject: [PATCH 4/8] no-broken-symlinks: exit instead of returning 1 for cleaner log --- pkgs/build-support/setup-hooks/no-broken-symlinks.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/build-support/setup-hooks/no-broken-symlinks.sh b/pkgs/build-support/setup-hooks/no-broken-symlinks.sh index 142a2fca720fc..6711e0deace4b 100644 --- a/pkgs/build-support/setup-hooks/no-broken-symlinks.sh +++ b/pkgs/build-support/setup-hooks/no-broken-symlinks.sh @@ -47,7 +47,7 @@ noBrokenSymlinks() { # If it does, Nix probably won't be able to resolve or track dependencies. if [[ $symlinkTarget != "$output" && $symlinkTarget != "$output"/* ]]; then nixErrorLog "symlink $path points to target $symlinkTarget, which escapes the current store path $output" - return 1 + exit 1 fi fi @@ -79,7 +79,7 @@ noBrokenSymlinks() { if ((numDanglingSymlinks > 0 || numReflexiveSymlinks > 0)); then nixErrorLog "found $numDanglingSymlinks dangling symlinks and $numReflexiveSymlinks reflexive symlinks" - return 1 + exit 1 fi return 0 } From 4e8e175c7c8edf9218d1f6ed870ff097dd240cb4 Mon Sep 17 00:00:00 2001 From: Connor Baker Date: Tue, 21 Jan 2025 13:55:57 -0800 Subject: [PATCH 5/8] doc: add stdenv entry for no-broken-symlinks.sh --- doc/stdenv/stdenv.chapter.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/stdenv/stdenv.chapter.md b/doc/stdenv/stdenv.chapter.md index 853b56f3c510f..dedeb8d45c92d 100644 --- a/doc/stdenv/stdenv.chapter.md +++ b/doc/stdenv/stdenv.chapter.md @@ -1371,6 +1371,14 @@ This setup hook moves any systemd user units installed in the `lib/` subdirector This hook only runs when compiling for Linux. +### `no-broken-symlinks.sh` {#no-broken-symlinks.sh} + +This setup hook checks for, reports, and (by default) fails builds when "broken" symlinks are found. A symlink is considered "broken" if it's dangling (the target doesn't exist) or reflexive (it refers to itself). + +By setting `allowDanglingSymlinks` or `allowReflexiveSymlinks` the hook can be configured to allow symlinks with non-existent targets or symlinks which are reflexive, respectively. + +This hook can be disabled entirely by setting `dontCheckForBrokenSymlinks`. + ### `set-source-date-epoch-to-latest.sh` {#set-source-date-epoch-to-latest.sh} This sets `SOURCE_DATE_EPOCH` to the modification time of the most recent file. From 229fdf0cf29b057503d2ba1c99d78650c209bd48 Mon Sep 17 00:00:00 2001 From: Connor Baker Date: Tue, 21 Jan 2025 16:29:28 -0800 Subject: [PATCH 6/8] test.stdenv.hooks.no-broken-symlinks: init --- pkgs/test/stdenv/hooks.nix | 1 + pkgs/test/stdenv/no-broken-symlinks.nix | 123 ++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 pkgs/test/stdenv/no-broken-symlinks.nix diff --git a/pkgs/test/stdenv/hooks.nix b/pkgs/test/stdenv/hooks.nix index 6a419ab51e5d0..83e82b77b24b3 100644 --- a/pkgs/test/stdenv/hooks.nix +++ b/pkgs/test/stdenv/hooks.nix @@ -97,6 +97,7 @@ [[ -e $out/bin/foo ]] ''; }; + no-broken-symlinks = import ./no-broken-symlinks.nix { inherit stdenv lib pkgs; }; # TODO: add multiple-outputs patch-shebangs = import ./patch-shebangs.nix { inherit stdenv lib pkgs; }; prune-libtool-files = diff --git a/pkgs/test/stdenv/no-broken-symlinks.nix b/pkgs/test/stdenv/no-broken-symlinks.nix new file mode 100644 index 0000000000000..85963542a04a3 --- /dev/null +++ b/pkgs/test/stdenv/no-broken-symlinks.nix @@ -0,0 +1,123 @@ +{ + lib, + pkgs, + stdenv, +}: + +let + inherit (lib.strings) concatStringsSep; + inherit (pkgs) runCommand; + inherit (pkgs.testers) testBuildFailure; + + mkDanglingSymlink = '' + ln -sr "$out/dangling" "$out/dangling-symlink" + ''; + + mkReflexiveSymlink = '' + ln -sr "$out/reflexive-symlink" "$out/reflexive-symlink" + ''; + + mkValidSymlink = '' + touch "$out/valid" + ln -sr "$out/valid" "$out/valid-symlink" + ''; + + testBuilder = + { + name, + commands ? [ ], + derivationArgs ? { }, + }: + stdenv.mkDerivation ( + { + inherit name; + strictDeps = true; + dontUnpack = true; + dontPatch = true; + dontConfigure = true; + dontBuild = true; + installPhase = + '' + mkdir -p "$out" + + '' + + concatStringsSep "\n" commands; + } + // derivationArgs + ); +in +{ + # Dangling symlinks (allowDanglingSymlinks) + fail-dangling-symlink = + runCommand "fail-dangling-symlink" + { + failed = testBuildFailure (testBuilder { + name = "fail-dangling-symlink-inner"; + commands = [ mkDanglingSymlink ]; + }); + } + '' + (( 1 == "$(cat "$failed/testBuildFailure.exit")" )) + grep -F 'found 1 dangling symlinks and 0 reflexive symlinks' "$failed/testBuildFailure.log" + touch $out + ''; + + pass-dangling-symlink-allowed = testBuilder { + name = "pass-symlink-dangling-allowed"; + commands = [ mkDanglingSymlink ]; + derivationArgs.allowDanglingSymlinks = true; + }; + + # Reflexive symlinks (allowReflexiveSymlinks) + fail-reflexive-symlink = + runCommand "fail-reflexive-symlink" + { + failed = testBuildFailure (testBuilder { + name = "fail-reflexive-symlink-inner"; + commands = [ mkReflexiveSymlink ]; + }); + } + '' + (( 1 == "$(cat "$failed/testBuildFailure.exit")" )) + grep -F 'found 0 dangling symlinks and 1 reflexive symlinks' "$failed/testBuildFailure.log" + touch $out + ''; + + pass-reflexive-symlink-allowed = testBuilder { + name = "pass-reflexive-symlink-allowed"; + commands = [ mkReflexiveSymlink ]; + derivationArgs.allowReflexiveSymlinks = true; + }; + + # Global (dontCheckForBrokenSymlinks) + fail-broken-symlinks = + runCommand "fail-broken-symlinks" + { + failed = testBuildFailure (testBuilder { + name = "fail-broken-symlinks-inner"; + commands = [ + mkDanglingSymlink + mkReflexiveSymlink + ]; + }); + } + '' + (( 1 == "$(cat "$failed/testBuildFailure.exit")" )) + grep -F 'found 1 dangling symlinks and 1 reflexive symlinks' "$failed/testBuildFailure.log" + touch $out + ''; + + pass-broken-symlinks-allowed = testBuilder { + name = "fail-broken-symlinks-allowed"; + commands = [ + mkDanglingSymlink + mkReflexiveSymlink + ]; + derivationArgs.dontCheckForBrokenSymlinks = true; + }; + + pass-valid-symlink = testBuilder { + name = "pass-valid-symlink"; + commands = [ mkValidSymlink ]; + }; +} From 51b2764e9fcd4ef768de7590ce796d619ef82cd2 Mon Sep 17 00:00:00 2001 From: Connor Baker Date: Wed, 22 Jan 2025 11:05:13 -0800 Subject: [PATCH 7/8] no-broken-symlinks: provide only dontCheckForBrokenSymlinks and test against absolute symlinks --- doc/stdenv/stdenv.chapter.md | 6 +- .../setup-hooks/no-broken-symlinks.sh | 28 +--- pkgs/test/stdenv/no-broken-symlinks.nix | 142 +++++++++++++----- 3 files changed, 115 insertions(+), 61 deletions(-) diff --git a/doc/stdenv/stdenv.chapter.md b/doc/stdenv/stdenv.chapter.md index dedeb8d45c92d..868a7543d9ad3 100644 --- a/doc/stdenv/stdenv.chapter.md +++ b/doc/stdenv/stdenv.chapter.md @@ -1375,9 +1375,11 @@ This hook only runs when compiling for Linux. This setup hook checks for, reports, and (by default) fails builds when "broken" symlinks are found. A symlink is considered "broken" if it's dangling (the target doesn't exist) or reflexive (it refers to itself). -By setting `allowDanglingSymlinks` or `allowReflexiveSymlinks` the hook can be configured to allow symlinks with non-existent targets or symlinks which are reflexive, respectively. +This hook can be disabled by setting `dontCheckForBrokenSymlinks`. -This hook can be disabled entirely by setting `dontCheckForBrokenSymlinks`. +::: {.note} +The check for reflexivity is direct and does not account for transitivity, so this hook will not prevent cycles in symlinks. +::: ### `set-source-date-epoch-to-latest.sh` {#set-source-date-epoch-to-latest.sh} diff --git a/pkgs/build-support/setup-hooks/no-broken-symlinks.sh b/pkgs/build-support/setup-hooks/no-broken-symlinks.sh index 6711e0deace4b..d622322a2747f 100644 --- a/pkgs/build-support/setup-hooks/no-broken-symlinks.sh +++ b/pkgs/build-support/setup-hooks/no-broken-symlinks.sh @@ -19,11 +19,10 @@ noBrokenSymlinks() { local path local pathParent local symlinkTarget - local errorMessage local -i numDanglingSymlinks=0 local -i numReflexiveSymlinks=0 - # TODO(@connorbaker): This hook doesn't check for cycles in symlinks. + # NOTE(@connorbaker): This hook doesn't check for cycles in symlinks. if [[ ! -e $output ]]; then nixWarnLog "skipping non-existent output $output" @@ -52,28 +51,13 @@ noBrokenSymlinks() { fi if [[ $path == "$symlinkTarget" ]]; then - # symlinkTarget is reflexive - errorMessage="the symlink $path is reflexive $symlinkTarget" - if [[ -z ${allowReflexiveSymlinks-} ]]; then - nixErrorLog "$errorMessage" - numReflexiveSymlinks+=1 - else - nixInfoLog "$errorMessage" - fi - + nixErrorLog "the symlink $path is reflexive $symlinkTarget" + numReflexiveSymlinks+=1 elif [[ ! -e $symlinkTarget ]]; then - # symlinkTarget does not exist - errorMessage="the symlink $path points to a missing target $symlinkTarget" - if [[ -z ${allowDanglingSymlinks-} ]]; then - nixErrorLog "$errorMessage" - numDanglingSymlinks+=1 - else - nixInfoLog "$errorMessage" - fi - + nixErrorLog "the symlink $path points to a missing target $symlinkTarget" + numDanglingSymlinks+=1 else - # symlinkTarget exists and is irreflexive - nixInfoLog "the symlink $path is irreflexive and points to a target which exists" + nixDebugLog "the symlink $path is irreflexive and points to a target which exists" fi done < <(find "$output" -type l -print0) diff --git a/pkgs/test/stdenv/no-broken-symlinks.nix b/pkgs/test/stdenv/no-broken-symlinks.nix index 85963542a04a3..0eb0ef0f982e6 100644 --- a/pkgs/test/stdenv/no-broken-symlinks.nix +++ b/pkgs/test/stdenv/no-broken-symlinks.nix @@ -9,17 +9,17 @@ let inherit (pkgs) runCommand; inherit (pkgs.testers) testBuildFailure; - mkDanglingSymlink = '' - ln -sr "$out/dangling" "$out/dangling-symlink" + mkDanglingSymlink = absolute: '' + ln -s${if absolute then "r" else ""} "$out/dangling" "$out/dangling-symlink" ''; - mkReflexiveSymlink = '' - ln -sr "$out/reflexive-symlink" "$out/reflexive-symlink" + mkReflexiveSymlink = absolute: '' + ln -s${if absolute then "r" else ""} "$out/reflexive-symlink" "$out/reflexive-symlink" ''; - mkValidSymlink = '' + mkValidSymlink = absolute: '' touch "$out/valid" - ln -sr "$out/valid" "$out/valid-symlink" + ln -s${if absolute then "r" else ""} "$out/valid" "$out/valid-symlink" ''; testBuilder = @@ -47,13 +47,12 @@ let ); in { - # Dangling symlinks (allowDanglingSymlinks) - fail-dangling-symlink = - runCommand "fail-dangling-symlink" + fail-dangling-symlink-relative = + runCommand "fail-dangling-symlink-relative" { failed = testBuildFailure (testBuilder { - name = "fail-dangling-symlink-inner"; - commands = [ mkDanglingSymlink ]; + name = "fail-dangling-symlink-relative-inner"; + commands = [ (mkDanglingSymlink false) ]; }); } '' @@ -62,19 +61,38 @@ in touch $out ''; - pass-dangling-symlink-allowed = testBuilder { - name = "pass-symlink-dangling-allowed"; - commands = [ mkDanglingSymlink ]; - derivationArgs.allowDanglingSymlinks = true; + pass-dangling-symlink-relative-allowed = testBuilder { + name = "pass-dangling-symlink-relative-allowed"; + commands = [ (mkDanglingSymlink false) ]; + derivationArgs.dontCheckForBrokenSymlinks = true; + }; + + fail-dangling-symlink-absolute = + runCommand "fail-dangling-symlink-absolute" + { + failed = testBuildFailure (testBuilder { + name = "fail-dangling-symlink-absolute-inner"; + commands = [ (mkDanglingSymlink true) ]; + }); + } + '' + (( 1 == "$(cat "$failed/testBuildFailure.exit")" )) + grep -F 'found 1 dangling symlinks and 0 reflexive symlinks' "$failed/testBuildFailure.log" + touch $out + ''; + + pass-dangling-symlink-absolute-allowed = testBuilder { + name = "pass-dangling-symlink-absolute-allowed"; + commands = [ (mkDanglingSymlink true) ]; + derivationArgs.dontCheckForBrokenSymlinks = true; }; - # Reflexive symlinks (allowReflexiveSymlinks) - fail-reflexive-symlink = - runCommand "fail-reflexive-symlink" + fail-reflexive-symlink-relative = + runCommand "fail-reflexive-symlink-relative" { failed = testBuildFailure (testBuilder { - name = "fail-reflexive-symlink-inner"; - commands = [ mkReflexiveSymlink ]; + name = "fail-reflexive-symlink-relative-inner"; + commands = [ (mkReflexiveSymlink false) ]; }); } '' @@ -83,21 +101,40 @@ in touch $out ''; - pass-reflexive-symlink-allowed = testBuilder { - name = "pass-reflexive-symlink-allowed"; - commands = [ mkReflexiveSymlink ]; - derivationArgs.allowReflexiveSymlinks = true; + pass-reflexive-symlink-relative-allowed = testBuilder { + name = "pass-reflexive-symlink-relative-allowed"; + commands = [ (mkReflexiveSymlink false) ]; + derivationArgs.dontCheckForBrokenSymlinks = true; }; - # Global (dontCheckForBrokenSymlinks) - fail-broken-symlinks = - runCommand "fail-broken-symlinks" + fail-reflexive-symlink-absolute = + runCommand "fail-reflexive-symlink-absolute" { failed = testBuildFailure (testBuilder { - name = "fail-broken-symlinks-inner"; + name = "fail-reflexive-symlink-absolute-inner"; + commands = [ (mkReflexiveSymlink true) ]; + }); + } + '' + (( 1 == "$(cat "$failed/testBuildFailure.exit")" )) + grep -F 'found 0 dangling symlinks and 1 reflexive symlinks' "$failed/testBuildFailure.log" + touch $out + ''; + + pass-reflexive-symlink-absolute-allowed = testBuilder { + name = "pass-reflexive-symlink-absolute-allowed"; + commands = [ (mkReflexiveSymlink true) ]; + derivationArgs.dontCheckForBrokenSymlinks = true; + }; + + fail-broken-symlinks-relative = + runCommand "fail-broken-symlinks-relative" + { + failed = testBuildFailure (testBuilder { + name = "fail-broken-symlinks-relative-inner"; commands = [ - mkDanglingSymlink - mkReflexiveSymlink + (mkDanglingSymlink false) + (mkReflexiveSymlink false) ]; }); } @@ -107,17 +144,48 @@ in touch $out ''; - pass-broken-symlinks-allowed = testBuilder { - name = "fail-broken-symlinks-allowed"; + pass-broken-symlinks-relative-allowed = testBuilder { + name = "pass-broken-symlinks-relative-allowed"; commands = [ - mkDanglingSymlink - mkReflexiveSymlink + (mkDanglingSymlink false) + (mkReflexiveSymlink false) ]; derivationArgs.dontCheckForBrokenSymlinks = true; }; - pass-valid-symlink = testBuilder { - name = "pass-valid-symlink"; - commands = [ mkValidSymlink ]; + fail-broken-symlinks-absolute = + runCommand "fail-broken-symlinks-absolute" + { + failed = testBuildFailure (testBuilder { + name = "fail-broken-symlinks-absolute-inner"; + commands = [ + (mkDanglingSymlink true) + (mkReflexiveSymlink true) + ]; + }); + } + '' + (( 1 == "$(cat "$failed/testBuildFailure.exit")" )) + grep -F 'found 1 dangling symlinks and 1 reflexive symlinks' "$failed/testBuildFailure.log" + touch $out + ''; + + pass-broken-symlinks-absolute-allowed = testBuilder { + name = "pass-broken-symlinks-absolute-allowed"; + commands = [ + (mkDanglingSymlink true) + (mkReflexiveSymlink true) + ]; + derivationArgs.dontCheckForBrokenSymlinks = true; + }; + + pass-valid-symlink-relative = testBuilder { + name = "pass-valid-symlink-relative"; + commands = [ (mkValidSymlink false) ]; + }; + + pass-valid-symlink-absolute = testBuilder { + name = "pass-valid-symlink-absolute"; + commands = [ (mkValidSymlink true) ]; }; } From 34539b291caac26d1c4f944682f54ea0affd37cf Mon Sep 17 00:00:00 2001 From: Connor Baker Date: Wed, 22 Jan 2025 16:05:17 -0800 Subject: [PATCH 8/8] no-broken-symlinks: actually interpolate relative paths --- pkgs/build-support/setup-hooks/no-broken-symlinks.sh | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/pkgs/build-support/setup-hooks/no-broken-symlinks.sh b/pkgs/build-support/setup-hooks/no-broken-symlinks.sh index d622322a2747f..e4acaa1ec1149 100644 --- a/pkgs/build-support/setup-hooks/no-broken-symlinks.sh +++ b/pkgs/build-support/setup-hooks/no-broken-symlinks.sh @@ -40,14 +40,9 @@ noBrokenSymlinks() { nixInfoLog "symlink $path points to absolute target $symlinkTarget" else nixInfoLog "symlink $path points to relative target $symlinkTarget" - symlinkTarget="$pathParent/$symlinkTarget" - - # Check to make sure the interpolated target doesn't escape the store path of `output`. - # If it does, Nix probably won't be able to resolve or track dependencies. - if [[ $symlinkTarget != "$output" && $symlinkTarget != "$output"/* ]]; then - nixErrorLog "symlink $path points to target $symlinkTarget, which escapes the current store path $output" - exit 1 - fi + # Use --no-symlinks to avoid dereferencing again and --canonicalize-missing to avoid existence + # checks at this step (which can lead to infinite recursion). + symlinkTarget="$(realpath --no-symlinks --canonicalize-missing "$pathParent/$symlinkTarget")" fi if [[ $path == "$symlinkTarget" ]]; then