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 00000000000000..26f4838af5a8f4 --- /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 fe9843c6b1202f..57a58eb2ebe4b0 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