#!/bin/sh -f # # Copyright (c) 2007-2009 # Dominic Fandrey # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # readonly name=pkg_libchk readonly version=1.6.1 readonly osname=`uname -s` readonly pkgng=`make -f /usr/share/mk/bsd.port.mk -V WITH_PKGNG` # Use a line break as delimiter. IFS=' ' # Filename prefix for shared data sharedprefix="%%TMP%%/$$" shared="locks" # # This function remembers a lock to allow later deletion with the # lockUnregisterAll() function. # # @param $1 # The name of the lock. lockRegister() { local lock lock="$sharedprefix-$shared" lockf -k "$lock" sh -c " if ! grep -qE '^$1\$' '$lock'; then echo '$1' >> '$lock' fi " } # # Unregisters all locks. # lockUnregisterAll() { wait for register in $(cat "$sharedprefix-$shared"); { lockf "$sharedprefix-$register" wait } lockf "$sharedprefix-$shared" wait } # # This function creates a semaphore. # # @param $1 # The name of the semaphore. # @param $2 # The size of the semaphore. # semaphoreCreate() { local lock lockRegister "semaphore-$1" lock="$sharedprefix-semaphore-$1" lockf -k "$lock" echo "$2" > "$lock" eval "semaphore_$1_size=$2" } # # This function waits until the semaphore is free und registers its use. # Everything that uses this also has to call the semaphoreFree() function. # # @param $1 # The name of the semaphore. # semaphoreUse() { local lock semaphores lock="$sharedprefix-semaphore-$1" while ! lockf -k "$lock" sh -c " state=\$(cat '$lock') if [ \"\$state\" -gt 0 ]; then echo \"\$((\$state - 1))\" > '$lock' exit 0 fi exit 1 "; do sleep 0.1 done } # # This function frees a semaphore. # # @param $1 # The name of the semaphore. # semaphoreFree() { local lock lock="$sharedprefix-semaphore-$1" lockf -k "$lock" sh -c " state=\"\$((\"\$(cat '$lock')\" + 1))\" echo \"\$state\" > '$lock' " } # # This function sets a new status and prints it. # # @param $1 # The status message. # @param $clean # If set status handling is disabled. # statusSet() { # In clean mode status handling is disabled. test -z "$clean" || return 0 local lock lock="$sharedprefix-status" lockf -k "$lock" sh -c " status=\"\$(cat '$lock')\" echo '$1' > '$lock' printf \"\\r%-\${#status}s\\r\" '$1' > /dev/tty " } # # This function prints a message and the current status behind it. # # @param $1 # The message to print. # @param $clean # If set the status will not be printed. # statusPrint() { if [ -z "$clean" ]; then local lock lock="$sharedprefix-status" lockf -k "$lock" sh -c " status=\"\$(cat '$lock')\" printf \"%-\${#status}s\\r\" '' > /dev/tty echo '$1' printf '%s\\r' \"\$status\" > /dev/tty " else echo "$1" fi } # # Waits for a semaphore to be completely free and counts down the remaining # number of locks. # # @param $1 # The semaphore to watch. # @param $2 # The status message to print, insert %d in the place where the number # of remaining locks belong. # semaphoreCountDown() { local free size while read -t1 free < "$sharedprefix-semaphore-$1"; do size=$(eval "echo \$semaphore_$1_size") statusSet "$(printf "$2" $(( $size - $free )))" test "$free" -eq "$size" && break sleep 0.1 done wait } # Clean up upon exit. trap ' semaphoreCountDown jobs "Terminated by signal, waiting for %d jobs to die." echo > /dev/tty lockUnregisterAll exit 255 ' int term # # This function checks whether a given binary or library directly depends # on a missing library. # It goes a long way to prevent all kinds of false positives. # It always returns 2 (false) for Linux and other non-native libraries # and binaries. # It also checks whether the missing dependency is really a direct dependency # (indirect dependencies have to be fixed somewhere else). # # @param $1 # The library or binary to check. # @return # Returns 0 (true) if a library is missing. # Returns 1 if everything is all right. # Returns 2 if the check cannot be performed (not a native library). # dependencyMissing() { local missing file direct libfound # We cannot handle non-native binaries, # so assume everything is in order. if ! readelf -e "$1" 2>&1 | \ grep -E "^[[:space:]]*OS/ABI:[[:space:]]*UNIX - $osname\$" \ > /dev/null then return 2 # Nothing is missing. elif ! missing="$(ldd "$1" 2>&1 | grep -E "$match_expr")"; then return 1 fi # The return status. The value 1 assumes that this is a false positive. status=1 # Only report misses for direct dependencies. direct="$( readelf -d "$1" 2> /dev/null | \ grep 'Shared library:' | \ sed -E -e 's|^[^[]*\[||1' -e 's|\]$||1' )" # Compare every missing depency with the list of direct dependencies # and report that the dependency is missing if the missing file is # a direct dependency. for file in $missing; { # Strip the missing file of additional information. file="$(echo "$file" | sed -E \ -e 's| => .*$||1' \ -e 's|^[[:space:]]*||1' \ -e 's|^.*dependency ||1' \ -e 's| not found$||1' )" # If in mean mode we do not check for false positives. if [ -n "$mean" ]; then test -n "$raw" && return 0 statusPrint "$package_name: $1 misses $file" continue fi # Handle the case where a library is not found, but exists # somewhere in the package. This is for packages that do not # rely on the OS to find libraries. libfound= for library in $(echo "$libraries" | grep -E "/$file\$"); { # The library exists after all. test -e "$library" && libfound=1 && break } if test "$libfound"; then test -n "$verbose" && statusPrint "$package_name: \ located: $1 misses $file found at $library." continue fi # Compare the file with the list of direct dependencies. # If it's not in than it's only an indirect dependency and # cannot be fixed by rebuilding this port. if echo "$direct" | grep -E "^$file\$" > /dev/null; then test -n "$raw" && return 0 statusPrint "$package_name: $1 misses $file" status=0 elif [ -n "$verbose" ]; then statusPrint "$package_name: inderect: $1 \ misses $file is an inderect dependency." fi } return $status } # # Checks the parameters for options. # # @param $packages # The parameters to pkg_info -E that will result in the # names of the packages to work on. # @param $recursive # Contains the appropriate parameter to get the # dependencies of the given packages from pkg_info. # @param $Recursive # Contains the appropriate parameter to get the # packages depending on the given packages from pkg_info. # @param $raw # Is set to trigger raw printing. # @param $clean # Is set to trigger printing without status messages. # @param $verbose # Is set to be verbose about false positives. # @param $mean # Is set to switch into mean mode. That means no # checking of false positives. # @param $compat # Delete to avoid detecting compat libraries as misses. # @param $origin # Is set to turn the print origin mode on. # @semaphore jobs # Is set to limit the amount of parallel jobs. # readParams() { local option for option { case "$option" in "-a" | "--all") packages="-a" ;; "-c" | "--clean") clean=1 ;; "-h" | "--help") printHelp ;; -j* | --jobs*) local jobs jobs="${option#-j}" jobs="${jobs#--jobs}" if [ "$jobs" -ne "$jobs" ] 2> /dev/null; then echo "The -j option must be followed" \ "by a number." exit 3 elif [ "$jobs" -lt 1 ]; then echo "The -j option must specify at" \ "least 1 job." exit 3 else semaphoreCreate jobs "$jobs" fi ;; "-m" | "--mean") mean=1 ;; "-n" | "--no-compat") compat= ;; "-o" | "--origin") origin=1 ;; "-q" | "--raw") raw=1 if [ -n "$verbose" ]; then echo "The parameters -v and -q may" \ "not be used at the same time." exit 2 fi ;; "-r" | "--recursive") recursive="-r" ;; "-R" | "--upward-recursive") Recursive="-R" ;; "-v" | "--verbose") verbose=1 if [ -n "$raw" ]; then echo "The parameters -q and -v may" \ "not be used at the same time." exit 2 fi ;; -? | --*) echo "Unknown parameter \"$option\"." exit 1 ;; -*) readParams "${option%${option#-?}}" readParams "-${option#-?}" ;; *) packages="$packages${packages:+$IFS}$option" ;; esac } } # # Display a short help message. # printHelp() { echo "$name v$version usage: $name [-a] [-c] [-h] [-jN] [-m] [-n] [-o] [-q] [-r] [-R] [-v] [packages]" exit 0 } # Create the expression to match to find files linking against compat libraries. # This can be emptied by readParams to deactivate that feature. prefix="$(make -f /usr/share/mk/bsd.port.mk -VPREFIX 2> /dev/null || \ echo '%%PREFIX%%')" compat="=> $prefix/lib/compat|" # Create the semaphore with CPU cores * 2 jobs. semaphoreCreate jobs "$(($(sysctl -n hw.ncpu 2> /dev/null || echo 1) * 2))" # Register the status lock. lockRegister status # Read the parameters. readParams "$@" statusSet 'Preparing ...' # Get the packages to work on. test -z "$packages" && packages="-a" if [ -n "$pkgng" ]; then packages="$(pkg info -q $packages)" test -z "$recursive" -a -z "$Recursive" || packages="$packages $(pkg info -q $recursive $Recursive "$packages" 2> /dev/null | \ sed -E 's|^@pkgdep[[:space:]]*||1')" else packages="$(pkg_info -E $packages)" test -z "$recursive" -a -z "$Recursive" || packages="$packages $(pkg_info -q $recursive $Recursive "$packages" 2> /dev/null | \ sed -E 's|^@pkgdep[[:space:]]*||1')" fi # Create the regexp to match ldd output match_expr="$compat=> not found|dependency .+ not found" # The packages to check. package_amount="$(echo "$packages" | wc -l | sed 's|[[:space:]]||g')" package_num=0 # Check each selected package. for package in $packages; { package_num="$(($package_num + 1))" if [ -n "$pkgng" ]; then test $origin \ && package_name="$(pkg info -qo "$package")" \ || package_name="$package" else test $origin \ && package_name="$(pkg_info -qo "$package")" \ || package_name="$package" fi # Print what we're doing. statusSet "Starting job $package_num of $package_amount: $package_name" semaphoreUse jobs ( # Remember freeing the semaphore. trap 'semaphoreFree jobs' EXIT files="" if [ -n "$pkgng" ]; then files="$(pkg info -lq "$package")" else files="$(pkg_info -qL "$package")" fi # Get the programs libraries in case it doesn't use the # operating system to find its libraries. libraries="$(echo "$files" | grep -E '\.so[\.0-9]*$')" outdated=0 broken= # Check each file of each package. for file in $files; { if [ ! -L "$file" -a \( \ -x "$file" -o \ -n "$(echo "$file" | grep -E '\.so[\.0-9]*$')" \ \) ]; then if dependencyMissing "$file"; then if [ -n "$raw" ]; then statusPrint "$package_name" break 1 fi fi fi } ) & } semaphoreCountDown jobs "Waiting for %d remaining jobs to finish." statusSet lockUnregisterAll exit 0