1#!/usr/bin/env bash
2
3# Copyright (C) 2016 Samuel Martin <s.martin49@gmail.com>
4# Copyright (C) 2017 Wolfgang Grandegger <wg@grandegger.com>
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14# General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
20usage() {
21  cat <<EOF >&2
22Usage:  ${0} TREE_KIND
23
24Description:
25
26    This script scans a tree and sanitize ELF files' RPATH found in there.
27
28    Sanitization behaves the same whatever the kind of the processed tree,
29    but the resulting RPATH differs. The rpath sanitization is done using
30    "patchelf --make-rpath-relative".
31
32Arguments:
33
34    TREE_KIND    Kind of tree to be processed.
35                 Allowed values: host, target, staging
36
37Environment:
38
39    PATCHELF     patchelf program to use
40                 (default: HOST_DIR/bin/patchelf)
41
42    HOST_DIR     host directory
43    STAGING_DIR  staging directory
44    TARGET_DIR   target directory
45
46    TOOLCHAIN_EXTERNAL_DOWNLOAD_INSTALL_DIR
47                 (default HOST_DIR/opt/ext-toolchain)
48
49    PARALLEL_JOBS number of parallel jobs to run
50
51Returns:         0 if success or 1 in case of error
52
53EOF
54}
55
56: "${PATCHELF:=${HOST_DIR}/bin/patchelf}"
57
58# ELF files should not be in these sub-directories
59HOST_EXCLUDEPATHS="/share/terminfo"
60STAGING_EXCLUDEPATHS="/usr/include /usr/share/terminfo"
61TARGET_EXCLUDEPATHS="/lib/firmware"
62
63patch_file() {
64    local PATCHELF rootdir file
65    local -a sanitize_extra_args
66
67    PATCHELF="${1}"
68    rootdir="${2}"
69    file="${3}"
70    shift 3
71    sanitize_extra_args=("${@}")
72
73    # check if it's an ELF file
74    rpath="$("${PATCHELF}" --print-rpath "${file}" 2>&1)"
75    if test $? -ne 0 ; then
76        return 0
77    fi
78
79    # make files writable if necessary
80    changed="$(chmod -c u+w "${file}")"
81
82    # With per-package directory support, most RPATH of host
83    # binaries will point to per-package directories. This won't
84    # work with the --make-rpath-relative ${rootdir} invocation as
85    # the per-package host directory is not within ${rootdir}. So,
86    # we rewrite all RPATHs pointing to per-package directories so
87    # that they point to the global host directry.
88    # shellcheck disable=SC2001 # ${var//search/replace} hard when search or replace have / in them
89    changed_rpath="$(echo "${rpath}" | sed "s@${PER_PACKAGE_DIR}/[^/]\+/host@${HOST_DIR}@")"
90    if test "${rpath}" != "${changed_rpath}" ; then
91        "${PATCHELF}" --set-rpath "${changed_rpath}" "${file}"
92    fi
93
94    # call patchelf to sanitize the rpath
95    "${PATCHELF}" --make-rpath-relative "${rootdir}" "${sanitize_extra_args[@]}" "${file}"
96    # restore the original permission
97    test "${changed}" != "" && chmod u-w "${file}"
98}
99
100main() {
101    local rootdir tree
102    local -a find_args sanitize_extra_args
103
104    tree="${1}"
105
106    if ! "${PATCHELF}" --version > /dev/null 2>&1; then
107        echo "Error: can't execute patchelf utility '${PATCHELF}'"
108        exit 1
109    fi
110
111    case "${tree}" in
112        host)
113            rootdir="${HOST_DIR}"
114
115            # do not process the sysroot (only contains target binaries)
116            find_args+=( "-path" "${STAGING_DIR}" "-prune" "-o" )
117
118            # do not process the external toolchain installation directory to
119            # avoid breaking it.
120            test "${TOOLCHAIN_EXTERNAL_DOWNLOAD_INSTALL_DIR}" != "" && \
121                find_args+=( "-path" "${TOOLCHAIN_EXTERNAL_DOWNLOAD_INSTALL_DIR}" "-prune" "-o" )
122
123            for excludepath in ${HOST_EXCLUDEPATHS}; do
124                find_args+=( "-path" "${HOST_DIR}""${excludepath}" "-prune" "-o" )
125            done
126
127            # do not process the patchelf binary but a copy to work-around "file in use"
128            find_args+=( "-path" "${PATCHELF}" "-prune" "-o" )
129            cp "${PATCHELF}" "${PATCHELF}.__to_be_patched"
130
131            # we always want $ORIGIN-based rpaths to make it relocatable.
132            sanitize_extra_args+=( "--relative-to-file" )
133            ;;
134
135        staging)
136            rootdir="${STAGING_DIR}"
137
138            # ELF files should not be in these sub-directories
139            for excludepath in ${STAGING_EXCLUDEPATHS}; do
140                find_args+=( "-path" "${STAGING_DIR}""${excludepath}" "-prune" "-o" )
141            done
142
143            # should be like for the target tree below
144            sanitize_extra_args+=( "--no-standard-lib-dirs" )
145            ;;
146
147        target)
148            rootdir="${TARGET_DIR}"
149
150            for excludepath in ${TARGET_EXCLUDEPATHS}; do
151                find_args+=( "-path" "${TARGET_DIR}""${excludepath}" "-prune" "-o" )
152            done
153
154            # we don't want $ORIGIN-based rpaths but absolute paths without rootdir.
155            # we also want to remove rpaths pointing to /lib or /usr/lib.
156            sanitize_extra_args+=( "--no-standard-lib-dirs" )
157            ;;
158
159        *)
160            usage
161            exit 1
162            ;;
163    esac
164
165    find_args+=( "-type" "f" "-print0" )
166
167    export -f patch_file
168    # Limit the number of cores used
169    # shellcheck disable=SC2016  # ${@} has to be expanded in the sub-shell.
170    find "${rootdir}" "${find_args[@]}" \
171    | xargs -0 -r -P "${PARALLEL_JOBS:-1}" -I {} \
172            bash -c 'patch_file "${@}"' _ "${PATCHELF}" "${rootdir}" {} "${sanitize_extra_args[@]}"
173
174    # Restore patched patchelf utility
175    test "${tree}" = "host" && mv "${PATCHELF}.__to_be_patched" "${PATCHELF}"
176
177    # ignore errors
178    return 0
179}
180
181main "${@}"
182