#! /bin/bash # Distributed under the terms of the GNU General Public License v2 # Shamelessly copied mostly from Gentoo's revdep-rebuild utility. # http://sources.gentoo.org/viewcvs.py/gentoo-x86/app-portage/gentoolkit/ # $ equery belongs /usr/bin/revdep-rebuild # app-portage/gentoolkit-0.2.3-r1 # $ cat /usr/portage/app-portage/gentoolkit/gentoolkit-0.2.3-r1.ebuild | grep LICENSE # LICENSE="GPL-2" # findbrokenpkgs for Arch Linux. # Converted to use pacman instead of emerge by Paul Bredbury # # v1.1 - files now in $HOME/.findbrokenpkgs/ directory - based on changes from # Stefan Husmann, repackaged by Jaroslav Lichtblau # Customizable variables: # # LD_LIBRARY_MASK - Mask of specially evaluated libraries # SEARCH_DIRS - List of directories to search for executables and libraries # SEARCH_DIRS_MASK - List of directories to not search # # These variables can be prepended to by setting the variable in # your environment prior to execution. # # An entry of "-*" means to clear the variable from that point forward. # Example: env SEARCH_DIRS="/usr/bin -*" findbrokenpkgs will set SEARCH_DIRS # to contain only /usr/bin if [ "$1" = "-h" -o "$1" = "-help" -o "$1" = "--help" ] ; then echo "Broken package identifier, version 1.0" echo "Checks dynamic library linking." echo echo "Usage: $0 [OPTIONS]" echo echo " -nc, --no-color Turn off colored output" echo " -nw, --no-warning Disable newbie-friendly warning" echo " -q, --quiet Be less verbose" echo echo "Report bugs to http://bbs.archlinux.org/viewtopic.php?id=13882" exit 0 fi # Update the incremental variables using /etc/profile.env, /etc/ld.so.conf, # and the environment. # Read the incremental variables from environment PRELIMINARY_SEARCH_DIRS="$SEARCH_DIRS" PRELIMINARY_SEARCH_DIRS_MASK="$SEARCH_DIRS_MASK" PRELIMINARY_LD_LIBRARY_MASK="$LD_LIBRARY_MASK" SONAME_SEARCH="not found" SONAME_GREP=grep # Add the defaults if [ -d /etc/findbrokenpkgs ] ; then for file in $(ls /etc/findbrokenpkgs) ; do PRELIMINARY_SEARCH_DIRS="$PRELIMINARY_SEARCH_DIRS $(. /etc/findbrokenpkgs/${file}; echo $SEARCH_DIRS)" PRELIMINARY_SEARCH_DIRS_MASK="$PRELIMINARY_SEARCH_DIRS_MASK $(. /etc/findbrokenpkgs/${file}; echo $SEARCH_DIRS_MASK)" PRELIMINARY_LD_LIBRARY_MASK="$PRELIMINARY_LD_LIBRARY_MASK $(. /etc/findbrokenpkgs/${file}; echo $LD_LIBRARY_MASK)" done else PRELIMINARY_SEARCH_DIRS="$PRELIMINARY_SEARCH_DIRS /bin /sbin /usr/bin /usr/sbin /lib* /usr/lib*" # openoffice is a binary, and we don't want to check that monster PRELIMINARY_SEARCH_DIRS_MASK="$PRELIMINARY_SEARCH_DIRS_MASK /opt/openoffice" # Binary libraries PRELIMINARY_LD_LIBRARY_MASK="$PRELIMINARY_LD_LIBRARY_MASK libodbcinst.so libodbc.so libjava.so libjvm.so" fi # Get the ROOTPATH and PATH from /etc/profile.env if [ -e "/etc/profile.env" ] ; then PRELIMINARY_SEARCH_DIRS="$PRELIMINARY_SEARCH_DIRS $((. /etc/profile.env; echo ${ROOTPATH}:${PATH}) | tr ':' ' ')" fi # Get the directories from /etc/ld.so.conf if [ -e /etc/ld.so.conf ] ; then PRELIMINARY_SEARCH_DIRS="$PRELIMINARY_SEARCH_DIRS $(grep -v "^#" /etc/ld.so.conf | tr '\n' ' ')" fi # Set the final variables # Note: Using $(echo $variable) removes extraneous spaces from variable assignment unset SEARCH_DIRS for i in $(echo $PRELIMINARY_SEARCH_DIRS) ; do [ "$i" = "-*" ] && break # Append a / at the end so that links and directories are treated the same by find # Remove any existing trailing slashes to prevent double-slashes SEARCH_DIRS="$(echo $SEARCH_DIRS ${i/%\//}/)" done # Remove any double-slashes from the path SEARCH_DIRS="$(echo $SEARCH_DIRS | sed 's:/\+:/:g')" unset SEARCH_DIRS_MASK for i in $(echo $PRELIMINARY_SEARCH_DIRS_MASK) ; do [ "$i" = "-*" ] && break SEARCH_DIRS_MASK="$(echo $SEARCH_DIRS_MASK $i)" done unset LD_LIBRARY_MASK for i in $(echo $PRELIMINARY_LD_LIBRARY_MASK) ; do [ "$i" = "-*" ] && break LD_LIBRARY_MASK="$(echo $LD_LIBRARY_MASK $i)" done # Base of temporary files names. [ -d ${HOME}/.findbrokenpkgs ] || mkdir ${HOME}/.findbrokenpkgs touch ${HOME}/.findbrokenpkgs/findbrokenpkgs_0.test 2>/dev/null if [ $? -eq 0 ] ; then LIST="${HOME}/.findbrokenpkgs/findbrokenpkgs" rm ~/.findbrokenpkgs/findbrokenpkgs_0.test else # Try to use /var/tmp since $HOME is not available touch /var/tmp/.findbrokenpkgs/findbrokenpkgs_0.test 2>/dev/null if [ $? -eq 0 ] ; then LIST="/var/tmp/.findbrokenpkgs/findbrokenpkgs" rm /var/tmp/.findbrokenpkgs/findbrokenpkgs_0.test else echo echo "!!! Unable to write temporary files to either $HOME or /var/tmp !!!" echo exit 1 fi fi shopt -s nullglob shopt -s expand_aliases unalias -a alias echo_v=echo while [ ! -z "$1" ] ; do case "$1" in -q | --quiet ) alias echo_v=: shift ;; -nc | --no-color ) NOCOLOR=true shift ;; -nw | --no-warning ) NOWARNING=true shift ;; * ) echo "Unknown option: $1" exit 1 ;; esac done # Color Definitions if [ "$NOCOLOR" = "yes" -o "$NOCOLOR" = "true" ] ; then NO="" BR="" CY="" GR="" RD="" YL="" BL="" else NO="\x1b[0m" BR="\x1b[0;01m" CY="\x1b[36;01m" GR="\x1b[32;01m" RD="\x1b[31;01m" YL="\x1b[33;01m" BL="\x1b[34;01m" fi function set_trap () { trap "rm_temp $1" SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM } function rm_temp () { echo " terminated." echo "Removing incomplete $1." rm $1 echo exit 1 } # Want program results (especially from pacman) in English export LC_ALL=C # Always delete the temporary files from a previous run rm -f ${LIST}.[0-9]_* # Clean up no longer needed environment variables unset PREVIOUS_SEARCH_DIRS PREVIOUS_SEARCH_DIRS_MASK PREVIOUS_LD_LIBRARY_MASK PREVIOUS_PREVIOUS_OPTIONS unset PRELIMINARY_SEARCH_DIRS PRELIMINARY_SEARCH_DIRS_MASK PRELIMINARY_LD_LIBRARY_MASK # Log our environment echo "SEARCH_DIRS=\"$SEARCH_DIRS\"" > $LIST.0_env echo "SEARCH_DIRS_MASK=\"$SEARCH_DIRS_MASK\"" >> $LIST.0_env echo "LD_LIBRARY_MASK=\"$LD_LIBRARY_MASK\"" >> $LIST.0_env echo_v echo_v "Checking reverse dependencies..." echo_v echo_v -n -e "${GR}Collecting system binaries and libraries...${NO}" set_trap "$LIST.1_*" # Be extra paranoid and pipe results through sed to remove multiple slashes # using -perm /u+x for find command find $SEARCH_DIRS -type f \( -perm /u+x -o -name '*.so' -o -name '*.so.*' -o -name '*.la' \) 2>/dev/null | sort | uniq | sed 's:/\+:/:g' >$LIST.0_files # Remove files that match SEARCH_DIR_MASK for dir in $SEARCH_DIRS_MASK ; do grep -v "^$dir" $LIST.0_files > $LIST.1_files mv $LIST.1_files $LIST.0_files done mv $LIST.0_files $LIST.1_files echo_v -e " ${GR}done.${NO}\n ($LIST.1_files)" echo_v echo_v -n -e "${GR}Collecting complete LD_LIBRARY_PATH...${NO}" set_trap "$LIST.2_ldpath" # Ensure that the "trusted" lib directories are at the start of the path ( echo /lib* /usr/lib* | sed 's/ /:/g' sed '/^#/d;s/#.*$//' $LIST.2_ldpath echo_v -e " ${GR}done.${NO}\n ($LIST.2_ldpath)" COMPLETE_LD_LIBRARY_PATH="$(cat $LIST.2_ldpath)" echo_v echo_v -e "${GR}Checking dynamic linking consistency...${NO}" set_trap "$LIST.3_rebuild" LD_MASK="\\( $(echo "$LD_LIBRARY_MASK" | sed 's/\./\\./g;s/ / \\| /g') \\)" echo -n > $LIST.3_rebuild cat $LIST.1_files | egrep -v '*\.la$' | while read FILE ; do # Note: double checking seems to be faster than single # with complete path (special add-ons are rare). if ldd "$FILE" 2>/dev/null | grep -v "$LD_MASK" | $SONAME_GREP -q "$SONAME_SEARCH" ; then if LD_LIBRARY_PATH="$COMPLETE_LD_LIBRARY_PATH" ldd "$FILE" 2>/dev/null | grep -v "$LD_MASK" | $SONAME_GREP -q "$SONAME_SEARCH" ; then # Only build missing direct dependencies ALL_MISSING_LIBS=$(ldd "$FILE" 2>/dev/null | sort -u | sed -n 's/ \(.*\) => not found/\1/p' | tr '\n' ' ' | sed 's/ $//' ) REQUIRED_LIBS=$(objdump -x $FILE | grep NEEDED | awk '{print $2}' | tr '\n' ' ' | sed 's/ $//') MISSING_LIBS="" for lib in $ALL_MISSING_LIBS ; do if echo $REQUIRED_LIBS | grep -q $lib ; then MISSING_LIBS="$MISSING_LIBS $lib" fi done if [ "$MISSING_LIBS" != "" ] ; then echo "$FILE" >> $LIST.3_rebuild # MISSING_LIBS already starts with a space echo_v -e " $FILE ${RD}needs missing${NO}${MISSING_LIBS}" fi fi fi done # Not sure if *.la files should even be checked cat $LIST.1_files | egrep '*\.la$' | while read FILE ; do for depend in $(grep '^dependency_libs' $FILE | awk -F'=' '{print $2}' | sed "s/'//g") ; do [ ${depend:0:1} != '/' ] && continue if [ ! -e $depend ] ; then echo "$FILE" >> $LIST.3_rebuild echo_v -e " $FILE ${RD}needs missing${NO} ${depend}" fi done done echo_v -e " ${GR}done${NO}.\n ($LIST.3_rebuild)" echo_v echo_v -n -e "${GR}Assigning files to packages...${NO}" set_trap "$LIST.4_*" echo -n > $LIST.4_package_owners echo -n > $LIST.4_packages_raw echo -n > $LIST.4_orphans cat $LIST.3_rebuild | while read FILE ; do EXACT_PKG=$(pacman -Qo $FILE | awk '{print $5 " " $6}') PKG=$(echo $EXACT_PKG | awk '{print $1}') if [ -z "$PKG" ] ; then echo_v -n -e "\n ${RD}*** $FILE is orphan & broken! ***${NO}" echo "$FILE -> (none)" >> $LIST.4_package_owners echo "$FILE" >> $LIST.4_orphans echo_v -n -e "\n $FILE ${RD}-> (none)${NO}" else echo "$PKG" >> $LIST.4_packages_raw echo "$FILE -> $EXACT_PKG" >> $LIST.4_package_owners echo_v -n -e "\n $FILE ${CY}->${NO} ${BR}$PKG${NO}" fi done echo_v echo_v -e " ${GR}done.${NO}\n ($LIST.4_*)" echo_v echo_v -n -e "${GR}Cleaning list of packages to rebuild...${NO}" set_trap "$LIST.5_packages" sort -u $LIST.4_packages_raw >$LIST.5_packages echo_v -e " ${GR}done.${NO}\n ($LIST.5_packages)" REBUILD_LIST="$(cat $LIST.5_packages | tr '\n' ' ')" ORPHAN_LIST="$(cat $LIST.4_orphans)" # Clean up no longer needed environment variables unset COMPLETE_LD_LIBRARY_PATH SEARCH_DIRS SEARCH_DIRS_MASK LD_LIBRARY_MASK trap - SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM if [ -z "$REBUILD_LIST" ] && [ -z "$ORPHAN_LIST" ] ; then echo_v -e "\n${GR}Dynamic linking on your system is consistent.${NO}" # All OK, so delete temporary files rm -f ${LIST}.[0-9]_* else # Show broken files & packages if [ -n "$ORPHAN_LIST" ] ; then echo -e "\n${RD}Orphaned broken files:${NO}" echo "$ORPHAN_LIST" echo_v -e "\n${GR}This list of orphaned broken files is in $LIST.4_orphans${NO}" fi if [ -n "$REBUILD_LIST" ] ; then echo -e "\n${RD}Recompile these packages:${NO}" echo -e "${BR}$REBUILD_LIST${NO}" if ! [ "$NOWARNING" = "yes" -o "$NOWARNING" = "true" ] ; then echo_v -e "\nSome/all breakages may be ${GR}OK${NO} - this program cannot distinguish between ${RD}required${NO}" echo_v -e "and ${GR}optional${NO} dependencies. See http://bbs.archlinux.org/viewtopic.php?id=13882" fi fi # The temporary files are deliberately not deleted, as a source of info fi exit 0