1#!/usr/bin/env bash
2
3# This script scans $(HOST_DIR)/{bin,sbin} for all ELF files, and checks
4# they have an RPATH to $(HOST_DIR)/lib if they need libraries from
5# there.
6
7# Override the user's locale so we are sure we can parse the output of
8# readelf(1) and file(1)
9export LC_ALL=C
10
11main() {
12    local pkg="${1}"
13    local hostdir="${2}"
14    local perpackagedir="${3}"
15    local file ret
16
17    # Remove duplicate and trailing '/' for proper match
18    hostdir="$( sed -r -e 's:/+:/:g; s:/$::;' <<<"${hostdir}" )"
19
20    ret=0
21    while read file; do
22        is_elf "${file}" || continue
23        elf_needs_rpath "${file}" "${hostdir}" || continue
24        check_elf_has_rpath "${file}" "${hostdir}" "${perpackagedir}" && continue
25        if [ ${ret} -eq 0 ]; then
26            ret=1
27            printf "***\n"
28            printf "*** ERROR: package %s installs executables without proper RPATH:\n" "${pkg}"
29        fi
30        printf "***   %s\n" "${file}"
31    done < <( find "${hostdir}"/{bin,sbin} -type f 2>/dev/null )
32
33    return ${ret}
34}
35
36is_elf() {
37    local f="${1}"
38
39    readelf -l "${f}" 2>/dev/null \
40    |grep -E 'Requesting program interpreter:' >/dev/null 2>&1
41}
42
43# This function tells whether a given ELF executable (first argument)
44# needs a RPATH pointing to the host library directory or not. It
45# needs such an RPATH if at least of the libraries used by the ELF
46# executable is available in the host library directory. This function
47# returns 0 when a RPATH is needed, 1 otherwise.
48#
49# With per-package directory support, ${hostdir} will point to the
50# current package per-package host directory, and this is where this
51# function will check if the libraries needed by the executable are
52# located (or not). In practice, the ELF executable RPATH may point to
53# another package per-package host directory, but that is fine because
54# if such an executable is within the current package per-package host
55# directory, its libraries will also have been copied into the current
56# package per-package host directory.
57elf_needs_rpath() {
58    local file="${1}"
59    local hostdir="${2}"
60    local lib
61
62    while read lib; do
63        [ -e "${hostdir}/lib/${lib}" ] && return 0
64    done < <( readelf -d "${file}" 2>/dev/null                             \
65              |sed -r -e '/^.* \(NEEDED\) .*Shared library: \[(.+)\]$/!d;' \
66                     -e 's//\1/;'                                          \
67            )
68
69    return 1
70}
71
72# This function checks whether at least one of the RPATH of the given
73# ELF executable (first argument) properly points to the host library
74# directory (second argument), either through an absolute RPATH or a
75# relative RPATH. In the context of per-package directory support,
76# ${hostdir} (second argument) points to the current package host
77# directory. However, it is perfectly valid for an ELF binary to have
78# a RPATH pointing to another package per-package host directory,
79# which is why such RPATH is also accepted (the per-package directory
80# gets passed as third argument). Having a RPATH pointing to the host
81# directory will make sure the ELF executable will find at runtime the
82# shared libraries it depends on. This function returns 0 when a
83# proper RPATH was found, or 1 otherwise.
84check_elf_has_rpath() {
85    local file="${1}"
86    local hostdir="${2}"
87    local perpackagedir="${3}"
88    local rpath dir
89
90    while read rpath; do
91        for dir in ${rpath//:/ }; do
92            # Remove duplicate and trailing '/' for proper match
93            dir="$( sed -r -e 's:/+:/:g; s:/$::;' <<<"${dir}" )"
94            [ "${dir}" = "${hostdir}/lib" ] && return 0
95            [ "${dir}" = "\$ORIGIN/../lib" ] && return 0
96            # This check is done even for builds where
97            # BR2_PER_PACKAGE_DIRECTORIES is disabled. In this case,
98            # PER_PACKAGE_DIR and therefore ${perpackagedir} points to
99            # a non-existent directory, and this check will always be
100            # false.
101            [[ ${dir} =~ "${perpackagedir}/"[^/]+/host/lib ]] && return 0
102        done
103    done < <( readelf -d "${file}" 2>/dev/null                                  \
104              |sed -r -e '/.* \(R(UN)?PATH\) +Library r(un)?path: \[(.+)\]$/!d' \
105                      -e 's//\3/;'                                              \
106            )
107
108    return 1
109}
110
111main "${@}"
112