Skip to content

Commit

Permalink
stdenv: add no-broken-symlinks hook
Browse files Browse the repository at this point in the history
  • Loading branch information
ConnorBaker committed Jan 3, 2025
1 parent 07a7125 commit 238c85f
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 0 deletions.
85 changes: 85 additions & 0 deletions pkgs/build-support/setup-hooks/no-broken-symlinks.sh
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions pkgs/stdenv/generic/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 238c85f

Please sign in to comment.