Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
1# shellcheck shell=bash
2
3# Guard against double inclusion.
4if (("${noBrokenSymlinksHookInstalled:-0}" > 0)); then
5 nixInfoLog "skipping because the hook has been propagated more than once"
6 return 0
7fi
8declare -ig noBrokenSymlinksHookInstalled=1
9
10# symlinks are often created in postFixup
11# don't use fixupOutputHooks, it is before postFixup
12postFixupHooks+=(noBrokenSymlinksInAllOutputs)
13
14# A symlink is "dangling" if it points to a non-existent target.
15# A symlink is "reflexive" if it points to itself.
16# A symlink is "unreadable" if the readlink command fails, e.g. because of permission errors.
17# A symlink is considered "broken" if it is either dangling, reflexive or unreadable.
18noBrokenSymlinks() {
19 local -r output="${1:?}"
20 local path
21 local pathParent
22 local symlinkTarget
23 local -i numDanglingSymlinks=0
24 local -i numReflexiveSymlinks=0
25 local -i numUnreadableSymlinks=0
26
27 # NOTE(@connorbaker): This hook doesn't check for cycles in symlinks.
28
29 if [[ ! -e $output ]]; then
30 nixWarnLog "skipping non-existent output $output"
31 return 0
32 fi
33 nixInfoLog "running on $output"
34
35 # NOTE: path is absolute because we're running `find` against an absolute path (`output`).
36 while IFS= read -r -d $'\0' path; do
37 pathParent="$(dirname "$path")"
38 if ! symlinkTarget="$(readlink "$path")"; then
39 nixErrorLog "the symlink $path is unreadable"
40 numUnreadableSymlinks+=1
41 continue
42 fi
43
44 # Canonicalize symlinkTarget to an absolute path.
45 if [[ $symlinkTarget == /* ]]; then
46 nixInfoLog "symlink $path points to absolute target $symlinkTarget"
47 else
48 nixInfoLog "symlink $path points to relative target $symlinkTarget"
49 # Use --no-symlinks to avoid dereferencing again and --canonicalize-missing to avoid existence
50 # checks at this step (which can lead to infinite recursion).
51 symlinkTarget="$(realpath --no-symlinks --canonicalize-missing "$pathParent/$symlinkTarget")"
52 fi
53
54 # use $TMPDIR like audit-tmpdir.sh
55 if [[ $symlinkTarget = "$TMPDIR"/* ]]; then
56 nixErrorLog "the symlink $path points to $TMPDIR directory: $symlinkTarget"
57 numDanglingSymlinks+=1
58 continue
59 fi
60 if [[ $symlinkTarget != "$NIX_STORE"/* ]]; then
61 nixInfoLog "symlink $path points outside the Nix store; ignoring"
62 continue
63 fi
64
65 if [[ $path == "$symlinkTarget" ]]; then
66 nixErrorLog "the symlink $path is reflexive"
67 numReflexiveSymlinks+=1
68 elif [[ ! -e $symlinkTarget ]]; then
69 nixErrorLog "the symlink $path points to a missing target: $symlinkTarget"
70 numDanglingSymlinks+=1
71 else
72 nixDebugLog "the symlink $path is irreflexive and points to a target which exists"
73 fi
74 done < <(find "$output" -type l -print0)
75
76 if ((numDanglingSymlinks > 0 || numReflexiveSymlinks > 0 || numUnreadableSymlinks > 0)); then
77 nixErrorLog "found $numDanglingSymlinks dangling symlinks, $numReflexiveSymlinks reflexive symlinks and $numUnreadableSymlinks unreadable symlinks"
78 exit 1
79 fi
80 return 0
81}
82
83noBrokenSymlinksInAllOutputs() {
84 if [[ -z ${dontCheckForBrokenSymlinks-} ]]; then
85 for output in $(getAllOutputNames); do
86 noBrokenSymlinks "${!output}"
87 done
88 fi
89}