diff --git a/pkgs/build-support/setup-hooks/auto-patchelf.sh b/pkgs/build-support/setup-hooks/auto-patchelf.sh index 32fdb1000e27..5bedd1a9f9ca 100644 --- a/pkgs/build-support/setup-hooks/auto-patchelf.sh +++ b/pkgs/build-support/setup-hooks/auto-patchelf.sh @@ -7,21 +7,16 @@ gatherLibraries() { addEnvHooks "$targetOffset" gatherLibraries isExecutable() { - [ "$(file -b -N --mime-type "$1")" = application/x-executable ] -} - -findElfs() { - find "$1" -type f -exec "$SHELL" -c ' - while [ -n "$1" ]; do - mimeType="$(file -b -N --mime-type "$1")" - if [ "$mimeType" = application/x-executable \ - -o "$mimeType" = application/x-pie-executable \ - -o "$mimeType" = application/x-sharedlib ]; then - echo "$1" - fi - shift - done - ' -- {} + + # For dynamically linked ELF files it would be enough to check just for the + # INTERP section. However, we won't catch statically linked executables as + # they only have an ELF type of EXEC but no INTERP. + # + # So what we do here is just check whether *either* the ELF type is EXEC + # *or* there is an INTERP section. This also catches position-independent + # executables, as they typically have an INTERP section but their ELF type + # is DYN. + LANG=C readelf -h -l "$1" 2> /dev/null \ + | grep -q '^ *Type: *EXEC\>\|^ *INTERP\>' } # We cache dependencies so that we don't need to search through all of them on @@ -152,24 +147,73 @@ autoPatchelfFile() { fi } +# Can be used to manually add additional directories with shared object files +# to be included for the next autoPatchelf invocation. +addAutoPatchelfSearchPath() { + local -a findOpts=() + + # XXX: Somewhat similar to the one in the autoPatchelf function, maybe make + # it DRY someday... + while [ $# -gt 0 ]; do + case "$1" in + --) shift; break;; + --no-recurse) shift; findOpts+=("-maxdepth" 1);; + --*) + echo "addAutoPatchelfSearchPath: ERROR: Invalid command line" \ + "argument: $1" >&2 + return 1;; + *) break;; + esac + done + + cachedDependencies+=( + $(find "$@" "${findOpts[@]}" \! -type d \ + \( -name '*.so' -o -name '*.so.*' \)) + ) +} + autoPatchelf() { + local norecurse= + + while [ $# -gt 0 ]; do + case "$1" in + --) shift; break;; + --no-recurse) shift; norecurse=1;; + --*) + echo "autoPatchelf: ERROR: Invalid command line" \ + "argument: $1" >&2 + return 1;; + *) break;; + esac + done + + if [ $# -eq 0 ]; then + echo "autoPatchelf: No paths to patch specified." >&2 + return 1 + fi + echo "automatically fixing dependencies for ELF files" >&2 # Add all shared objects of the current output path to the start of # cachedDependencies so that it's choosen first in findDependency. - cachedDependencies+=( - $(find "$prefix" \! -type d \( -name '*.so' -o -name '*.so.*' \)) - ) - local elffile + addAutoPatchelfSearchPath ${norecurse:+--no-recurse} -- "$@" # Here we actually have a subshell, which also means that # $cachedDependencies is final at this point, so whenever we want to run # findDependency outside of this, the dependency cache needs to be rebuilt # from scratch, so keep this in mind if you want to run findDependency # outside of this function. - findElfs "$prefix" | while read -r elffile; do - autoPatchelfFile "$elffile" - done + while IFS= read -r -d $'\0' file; do + isELF "$file" || continue + segmentHeaders="$(LANG=C readelf -l "$file")" + # Skip if the ELF file doesn't have segment headers (eg. object files). + echo "$segmentHeaders" | grep -q '^Program Headers:' || continue + if isExecutable "$file"; then + # Skip if the executable is statically linked. + echo "$segmentHeaders" | grep -q "^ *INTERP\\>" || continue + fi + autoPatchelfFile "$file" + done < <(find "$@" ${norecurse:+-maxdepth 1} -type f -print0) } # XXX: This should ultimately use fixupOutputHooks but we currently don't have @@ -180,6 +224,11 @@ autoPatchelf() { # So what we do here is basically run in postFixup and emulate the same # behaviour as fixupOutputHooks because the setup hook for patchelf is run in # fixupOutput and the postFixup hook runs later. -postFixupHooks+=( - 'for output in $outputs; do prefix="${!output}" autoPatchelf; done' -) +postFixupHooks+=(' + if [ -z "$dontAutoPatchelf" ]; then + autoPatchelf -- $(for output in $outputs; do + [ -e "${!output}" ] || continue + echo "${!output}" + done) + fi +') diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix index e29766ed7f49..e812c38c2d4a 100644 --- a/pkgs/top-level/all-packages.nix +++ b/pkgs/top-level/all-packages.nix @@ -81,8 +81,7 @@ with pkgs; { deps = [ autoconf264 automake111x gettext libtool ]; } ../build-support/setup-hooks/autoreconf.sh; - autoPatchelfHook = makeSetupHook - { name = "auto-patchelf-hook"; deps = [ file ]; } + autoPatchelfHook = makeSetupHook { name = "auto-patchelf-hook"; } ../build-support/setup-hooks/auto-patchelf.sh; ensureNewerSourcesHook = { year }: makeSetupHook {}