#!/bin/bash # # Copyright (c) 2014 Michael Palimaka # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # A tool to report both undeclared and potentially-unused runtime dependencies. # # Depends on app-misc/pax-utils, app-portage/portage-utils and # sys-apps/gentoo-functions. DEBUG=FALSE IGNORE_DEPS=( "sys-libs/glibc" "sys-devel/gcc" ) IGNORE_LINKS=( "/lib64/libgcc_s.so.1" ) PKG_DIR="/var/db/pkg/" . /lib/gentoo/functions.sh bold() { echo $@ return local bold=$(tput bold) local normal=$(tput sgr0) echo "${bold}${@}${normal}" } debug() { if [ $DEBUG = TRUE ]; then # write to stderr so as not to interfere with function returns echo "$@" 1>&2 fi } # app-foo/bar-1.2.3-r1 -> app-foo/bar remove_atom_version() { local atom=`qatom "${1}" | cut -d " " -f 1-2 | tr " " /` echo $atom } virtualcheck() { debug Checking if ${libowner_pn} is provided by a virtual for virtual in $(qdepends --nocolor --name-only --rdepend --query ${libowner_pn} | grep ^virtual/) do debug Checking if ${virtual} is in dependencies local isvirtualdep isvirtualdep=$(qdepends --${1} ${atom} | grep ${virtual}) if [ $? -eq 0 ]; then used_virtuals+=( ${virtual} ) local resolved=true break fi done if [[ ! ${resolved} ]]; then if [ "${1}" = "depend" ]; then eerror "${obj} links to ${link}" fi eindent eerror Missing ${1^^} on $(bold ${libowner_pn}) eoutdent fi errors=1 } check_atom() { local errors=0 local atom=$1 local checked=() local rdepends=() local used_virtuals=() local objects=`qlist -qo ${atom}` if [ ! "${objects}" ]; then einfo ${atom} does not have any objects installed, skipping... echo return fi if [ -z "${INSIDE_PORTAGE}" ]; then einfo Checking ${atom} for undeclared dependencies eindent fi local obj for obj in $objects do debug "Checking ${obj}" readelf -h ${obj} > /dev/null 2>&1 # can it have stuff linked to it? if [ $? -ne 0 ]; then debug "It can't have links, skipping" continue fi local elf=`scanelf --format "#f%n" --nobanner --use-ldpath ${obj} 2>&1` local links=`echo ${elf} | tr "," " "` # get a list of everything that it's linked to local link for link in $links do local ignore for ignore in "${IGNORE_LINKS[@]}"; do if [ "${ignore}" = "${link}" ]; then debug "Ignoring ${link} due to blacklist" continue 2 fi done # only check a library once per atom for performance reasons local check for check in "${checked[@]}"; do if [ "${check}" = "${link}" ]; then debug "Already checked ${link} for this atom, skipping" continue 2 fi done checked=( "${checked[@]}" "${link}" ) debug "Found ${link}" local libowner=`qfile -vqC ${link} | uniq` if [ ! "${libowner}" ]; then local dereferenced=`qfile -vqC $(readlink -f ${link}) | uniq` if [ "${dereferenced}" ]; then debug "Deferenced symlink and found real lib owner" libowner=${dereferenced} else ewarn "Warning: installed file ${obj} is linked to ${link} which is not owned by any installed atom." continue fi fi debug "Owning package for ${link} is ${libowner}" local libowner_pn=$(remove_atom_version ${libowner}) local my_pn=$(remove_atom_version ${atom}) rdepends+=( "${libowner_pn}" ) if [ "${libowner_pn}" = "${my_pn}" ]; then debug "Owning package is self, ignoring" continue fi local ignorelib for ignorelib in "${IGNORE_DEPS[@]}" do if [ "${libowner_pn}" = "${ignorelib}" ]; then debug "Ignoring objects belonging to ${ignorelib}" continue 2 fi done debug "Checking if ${libowner_pn} is in the [R]DEPEND list of ${atom}" local isdep isdep=`qdepends -d ${atom} | grep ${libowner_pn}` if [ $? -ne 0 ]; then virtualcheck depend fi local isrdep isrdep=`qdepends -r ${atom} | grep ${libowner_pn}` if [ $? -ne 0 ]; then virtualcheck rdepend fi done done local ebuild_rdepends=() for rdepend in $(qdepends --nocolor --quiet --rdepend ${atom} | sed -e "s/\[[^]]*\]//g" | cut -d : -f 2-) do if [[ ${rdepend} = !* ]] ; then debug Skipping blocker: ${rdepend} continue elif [[ ${rdepend} = virtual/* ]] ; then for virtual in "${used_virtuals[@]}" do if [[ ${virtual} == $(remove_atom_version ${rdepend}) ]]; then debug Skipping virtual: ${rdepend} continue 2 fi done fi ebuild_rdepends+=( $(remove_atom_version $rdepend) ) done debug "Ebuild RDEPENDS: ${ebuild_rdepends[@]}" debug "Linked RDEPENDS: ${rdepends[@]}" local suspect_rdepends=$(comm -13 <(echo ${rdepends[@]} | sed 's/ /\n/g' | sort -u) <(echo ${ebuild_rdepends[@]} | sed 's/ /\n/g' | sort -u)) if [ "${suspect_rdepends}" ]; then ewarn "Suspect RDEPEND: $(bold ${suspect_rdepends})" fi if [ -z "${INSIDE_PORTAGE}" ]; then eoutdent if [ -n "${CAT}" ]; then echo fi fi return $errors } check_package() { local package=$1 local atoms=`qlist -IcCS ${package} | tr ' ' '-' | cut -d : -f1` debug Package ${package} own atoms: ${atoms} if [ ! "${atoms}" ]; then eerror ERROR: ${package} is not a valid atom exit 1 fi for atom in ${atoms}; do check_atom ${atom} done } check_category() { local errors=0 for package in `ls "${PKG_DIR}/${1}"`; do check_package "${1}/${package}" if [ $? -ne 0 ]; then errors=1 fi done; return $errors } BINNAME=`basename ${0}` if [ -z $1 ]; then echo "Checks emerged package(s) for undeclared depedencies" echo echo "Usage: ${BINNAME} [ atom | -c category | -a ]" echo echo "Examples:" echo "Check a single package: ${BINNAME} app-foo/bar-1.2.3" echo "Check a category: ${BINNAME} -c app-foo" echo "Check everything: ${BINNAME} -a" exit 1 fi # check a particular category if [ "$1" = "-c" ]; then CAT=$2 if [ ! -d "${PKG_DIR}/${CAT}" ]; then eerror ERROR: No packages from the category ${CAT} have been emerged yet exit 1 fi check_category $CAT # check everything elif [ "$1" = "-a" ]; then for category in `ls ${PKG_DIR}`; do check_category $category done; else check_package $1 fi if [ $DEBUG = TRUE ]; then if [ $? -eq 0 ]; then einfo Checking complete, no errors found else eerror Checking complete, errors found fi fi