#! /bin/bash # Copyright 1999-2005 Gentoo Foundation # $Header: $ # revdep-rebuild: Reverse dependency rebuilder. # Original Author: Stanislav Brabec # Maintainer: Paul Varner # Requires: etcat # Known problems: # # In exact ebuild mode revdep-rebuild can fail to properly order packages, # which are not up to date. # http://bugs.gentoo.org/show_bug.cgi?id=23018 # # Rebuilding using --package-names mode should be default, but emerge has no # feature to update to latest version of defined SLOT. # http://bugs.gentoo.org/show_bug.cgi?id=4698 # Customizable variables: # # LD_LIBRARY_MASK - Mask of specially evaluated libraries # SEARCH_DIRS - List of directories to search for executibles and libraries # SEARCH_DIRS_MASK - List of directories to not search # # These variables can be prepended to either by setting the variable in # your environment prior to execution, or by placing an entry in # /etc/make.conf. # # An entry of "-*" means to clear the variable from that point forward. # Example: env SEARCH_DIRS="/usr/bin -*" revdep-rebuild will set SEARCH_DIRS # to contain only /usr/bin if [ "$1" = "-h" -o "$1" = "--help" ] then echo "Usage: $0 [OPTIONS] [--] [EMERGE_OPTIONS]" echo echo "Broken reverse dependency rebuilder." echo echo " -X, --package-names recompile based on package names, not exact versions" echo " --soname SONAME recompile packages using library with SONAME instead" echo " of broken library (SONAME providing library must be" echo " present in the system)" echo " --soname-regexp SONAME" echo " the same as --soname, but accepts grep-style regexp" echo " -nc, --no-color turn off colored output" echo " -i, --ignore ignore temporary files from previous runs" echo " -q, --quiet be less verbose" echo echo "Calls emerge, all other options are used for it (e. g. -p, --pretend)." echo echo "Report bugs to " exit 0 fi echo "Configuring search environment for revdep-rebuild" # Obey PORTAGE_NICENESS PORTAGE_NICENESS=$(portageq envvar PORTAGE_NICENESS) [ ! -z "$PORTAGE_NICENESS" ] && renice $PORTAGE_NICENESS $$ > /dev/null # Set the defaults PRELIMINARY_LD_LIBRARY_MASK="$LD_LIBRARY_MASK libodbcinst.so libodbc.so libjava.so libjvm.so" PRELIMINARY_SEARCH_DIRS="$SEARCH_DIRS /bin /sbin /usr/bin /usr/sbin /lib* /usr/lib*" PRELIMINARY_SEARCH_DIRS_MASK="$SEARCH_DIRS_MASK /opt/OpenOffice" # Update the SEARCH_DIRS variable using /etc/profile.env, /etc/ld.so.conf, # /etc/make.conf, and the environment # Read the SEARCH_DIRS variable from /etc/make.conf if [ -e "/etc/make.conf" ] then PRELIMINARY_SEARCH_DIRS="$PRELIMINARY_SEARCH_DIRS $(unset SEARCH_DIRS; . /etc/make.conf; echo $SEARCH_DIRS)" PRELIMINARY_SEARCH_DIRS_MASK="$PRELIMINARY_SEARCH_DIRS_MASK $(unset SEARCH_DIRS_MASK; . /etc/make.conf; echo $SEARCH_DIRS_MASK)" PRELIMINARY_LD_LIBRARY_MASK="$PRELIMINARY_LD_LIBRARY_MASK $(unset LD_LIBRARY_MASK; . /etc/make.conf; echo $LD_LIBRARY_MASK)" 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 SEARCH_DIRS="$(echo $SEARCH_DIRS $i)" done 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 # Use the color preference from portage NOCOLOR=$(portageq envvar NOCOLOR) # Base of temporary files names. LIST=~/.revdep-rebuild shopt -s nullglob shopt -s expand_aliases unalias -a # Color Definitions 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" alias echo_v=echo PACKAGE_NAMES=false SONAME="not found" SONAME_GREP=fgrep SEARCH_BROKEN=true while : ; do case "$1" in -X | --package-names ) PACKAGE_NAMES=true shift ;; -q | --quiet ) alias echo_v=: shift ;; --soname=* ) SONAME="${1#*=}" SEARCH_BROKEN=false shift ;; --soname ) SONAME="$2" SEARCH_BROKEN=false shift 2 ;; --soname-regexp=* ) SONAME="${1#*=}" SONAME_GREP=grep SEARCH_BROKEN=false shift ;; --soname-regexp ) SONAME="$2" SONAME_GREP=grep SEARCH_BROKEN=false shift 2 ;; -nc | --no-color ) NOCOLOR=true shift ;; -i | --ignore ) rm -f ${LIST}* shift ;; -k | --keep-temp ) KEEPTEMP=true shift ;; -- ) shift break ;; * ) break ;; esac done if [ "$NOCOLOR" = "yes" -o "$NOCOLOR" = "true" ] then NOCOLOR=true else NOCOLOR=false fi # Make the NOCOLOR variable visible to emerge export NOCOLOR if $NOCOLOR then NO="" BR="" CY="" GR="" RD="" YL="" BL="" 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 } if $SEARCH_BROKEN ; then SONAME_SEARCH="$SONAME" LLIST=$LIST HEAD_TEXT="broken by a package update" OK_TEXT="Dynamic linking on your system is consistent" WORKING_TEXT=" consistency" else # first case is needed to test against /path/to/foo.so if [ ${SONAME:0:1} == '/' ] ; then SONAME_SEARCH=" $SONAME " else SONAME_SEARCH=" $SONAME " fi LLIST=${LIST}_$(echo "$SONAME_SEARCH$SONAME" | md5sum | head -c 8) HEAD_TEXT="using $SONAME" OK_TEXT="There are no dynamic links to $SONAME" WORKING_TEXT="" fi # If our temporary files are older than 1 day, don't use them TOO_OLD=$((`date +%s` - 86400)) for file in ${LIST}* do if [ -f $file ] then FILE_AGE="$(stat -c %Y $file)" if [ $FILE_AGE -lt $TOO_OLD ] then rm -f ${LIST}* break fi fi done echo echo "Checking reverse dependencies..." echo echo "Packages containing binaries and libraries $HEAD_TEXT" echo "will be recompiled." echo echo -n -e "${GR}Collecting system binaries and libraries...${NO}" if [ -f $LIST.1_files ] then echo " using existing $LIST.1_files." else # Be safe and remove any extraneous temporary files rm -f ${LIST}* set_trap "$LIST.[01]_*" 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 find $SEARCH_DIRS -type f \( -perm +u+x -o -name '*.so' -o -name '*.so.*' \) 2>/dev/null | sort | uniq >$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 -e " done.\n ($LIST.1_files)" fi if $SEARCH_BROKEN ; then echo echo -n -e "${GR}Collecting complete LD_LIBRARY_PATH...${NO}" if [ -f $LIST.2_ldpath ] ; then echo " using existing $LIST.2_ldpath." else 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 -e " done.\n ($LIST.2_ldpath)" fi export COMPLETE_LD_LIBRARY_PATH="$(cat $LIST.2_ldpath)" fi echo echo -n -e "${GR}Checking dynamic linking$WORKING_TEXT...${NO}" if [ -f $LLIST.3_rebuild ] ; then echo " using existing $LLIST.3_rebuild." else echo_v set_trap "$LLIST.3_rebuild" LD_MASK="\\( $(echo "$LD_LIBRARY_MASK" | sed 's/\./\\./g;s/ / \\| /g') \\)" echo -n >$LLIST.3_rebuild cat $LIST.1_files | 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 $SEARCH_BROKEN ; then if LD_LIBRARY_PATH="$COMPLETE_LD_LIBRARY_PATH" ldd "$FILE" 2>/dev/null | grep -v "$LD_MASK" | $SONAME_GREP -q "$SONAME_SEARCH" ; then echo "$FILE" >>$LLIST.3_rebuild echo_v " broken $FILE (requires $(ldd "$FILE" | sed -n 's/ \(.*\) => not found$/\1/p' | tr '\n' ' ' | sed 's/ $//' ))" fi else echo "$FILE" >>$LLIST.3_rebuild echo_v " found $FILE" fi fi done echo -e " done.\n ($LLIST.3_rebuild)" fi if $PACKAGE_NAMES ; then EXACT_EBUILDS=false echo echo -n -e "${GR}Assigning files to packages...${NO}" if [ -f $LLIST.4_packages_raw ] ; then echo " using existing $LLIST.4_packages_raw." else set_trap "$LLIST.4_packages*" echo -n >$LLIST.4_packages_raw echo -n >$LLIST.4_package_owners cat $LLIST.3_rebuild | while read FILE ; do EXACT_PKG="$(echo $FILE | sed 's/^/obj /' | (cd /var/db/pkg; grep -l -f - */*/CONTENTS) | sed s:/CONTENTS:: )" # Ugly sed hack to strip version information PKG="$(echo $EXACT_PKG | sed 's/-r[0-9].*$//;s/\(^.*\/*\)-.*$/\1/')" if [ -z "$PKG" ] ; then echo -n -e "\n ${RD}*** $FILE not owned by any package is broken! ***${NO}" echo "$FILE -> (none)" >> $LLIST.4_package_owners echo_v -n -e "\n $FILE -> (none)" else echo "$EXACT_PKG" >> $LLIST.4_packages_raw echo "$FILE -> $EXACT_PKG" >> $LLIST.4_package_owners echo_v -n -e "\n $FILE -> $PKG" fi done echo_v echo -e " done.\n ($LLIST.4_packages_raw, $LLIST.4_package_owners)" fi echo echo -n -e "${GR}Cleaning list of packages to rebuild...${NO}" if [ -f $LLIST.4_packages ] ; then echo " using existing $LLIST.4_packages." else sort -u $LLIST.4_packages_raw >$LLIST.4_packages echo -e " done.\n ($LLIST.4_packages)" fi echo echo -n -e "${GR}Assigning packages to ebuilds...${NO}" if [ -f $LLIST.4_ebuilds ] ; then echo " using existing $LLIST.4_ebuilds." else if [ -s "$LLIST.4_packages" ] then set_trap "$LLIST.4_ebuilds" cat $LLIST.4_packages | while read EXACT_PKG do # Get the slot PKG="$(echo $EXACT_PKG | sed 's/-r[0-9].*$//;s/\(^.*\/*\)-.*$/\1/')" SLOT=$(cat /var/db/pkg/${EXACT_PKG}/SLOT) # If SLOT is equal to 0, then just see what portage says is latest version if [ "$SLOT" = "0" ] then emerge --nospinner --pretend --nodeps $PKG | sed -n 's/ //g;s/\[[^]]*\]//gp' continue fi # Otherwise mask the other SLOTTED versions and check for latest OTHER_VERSIONS=$(etcat -v $PKG | grep -v "($SLOT)" | grep "(.*)" | sed 's/^.*\[...\] //;s/ .*$//') if [ -f /etc/portage/package.mask ] then mv -f /etc/portage/package.mask /etc/portage/package.mask.revdep-rebuild.backup else # Make sure that /etc/portage/package.mask exists mkdir -p /etc/portage touch /etc/portage/package.mask fi for pkg_version in $(echo $OTHER_VERSIONS | tr '\n' ' ') do echo "=${PKG}-${pkg_version}" >> /etc/portage/package.mask done emerge --nospinner --pretend --nodeps $PKG | sed -n 's/ //g;s/\[[^]]*\]//gp' if [ -f /etc/portage/package.mask.revdep-rebuild.backup ] then mv -f /etc/portage/package.mask.revdep-rebuild.backup /etc/portage/package.mask else rm -f /etc/portage/package.mask fi done > $LLIST.4_ebuilds echo -e " done.\n ($LLIST.4_ebuilds)" else echo " Nothing to rebuild" echo -n > $LLIST.4_ebuilds fi fi else EXACT_EBUILDS=true echo echo -n -e "${GR}Assigning files to ebuilds...${NO}" if [ -f $LLIST.4_ebuilds ] ; then echo " using existing $LLIST.4_ebuilds." else if [ -s "$LLIST.3_rebuild" ] ; then set_trap "$LLIST.4_ebuilds" cat $LLIST.3_rebuild | sed 's/^/obj /;s/$/ /' | ( cd /var/db/pkg fgrep -l -f - */*/CONTENTS ) | sed s:/CONTENTS:: > $LLIST.4_ebuilds echo -e " done.\n ($LLIST.4_ebuilds)" else echo " Nothing to rebuild" echo -n > $LLIST.4_ebuilds fi fi fi echo echo -n -e "${GR}Evaluating package order...${NO}" if [ -f $LLIST.5_order ] ; then echo " using existing $LLIST.5_order." else RAW_REBUILD_LIST="$(cat $LLIST.4_ebuilds | sed s/^/=/ | tr '\n' ' ')" if [ ! -z "$RAW_REBUILD_LIST" ] ; then REBUILD_GREP="^\\($( (emerge --nospinner --pretend --oneshot --nodeps $RAW_REBUILD_LIST ; echo $? >$LLIST.5_status ) | sed -n 's/\./\\&/g;s/ //g;s/$/\\/;s/\[[^]]*\]//gp' | tr '\n' '|' | sed 's/|$//'))\$" if [ $(cat $LLIST.5_status) -gt 0 ] ; then echo "" echo -e "${RD}Warning: Failed to resolve package order." echo -e "Will merge in \"random\" order!${NO}" echo "Possible reasons:" echo "- An ebuild is no longer in the portage tree." echo "- An ebuild is masked, use /etc/portage/packages.keyword" echo " and/or /etc/portage/package.unmask to unmask it" for i in . . . . . ; do echo -n -e '\a.' sleep 1 done ln -f $LLIST.4_ebuilds $LLIST.5_order else emerge --nospinner --pretend --oneshot --emptytree $RAW_REBUILD_LIST | sed -n 's/ //g;s/^.*\]//p' | grep "$REBUILD_GREP" >$LLIST.5_order fi else echo -n "" >$LLIST.5_order fi echo -e " done.\n ($LLIST.5_order)" fi REBUILD_LIST="$(cat $LLIST.5_order | sed s/^/=/ | tr '\n' ' ')" trap - SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM if [ -z "$REBUILD_LIST" ] ; then echo -e "\n${GR}$OK_TEXT... All done.${NO} " if [ ! $KEEPTEMP ] then rm $LIST.[0-2]_* rm $LLIST.[3-9]_* fi exit 0 fi IS_REAL_MERGE=true echo " $* " | grep -q '\( -p \| --pretend \| -f \| --fetchonly \)' && IS_REAL_MERGE=false echo echo -e "${GR}All prepared. Starting rebuild...${NO}" echo "emerge --oneshot $@ $REBUILD_LIST" if $IS_REAL_MERGE ; then for i in . . . . . . . . . . ; do echo -n -e '\a.' sleep 1 done echo fi #if $EXACT_EBUILDS ; then # Uncomment following, if you want to recompile masked ebuilds. ## FIXME: Check for PORTDIR_OVERLAY # echo -e "${GR}Temporarilly disabling package mask...${NO}" # trap "mv -i /usr/portage/profiles/package.mask.hidden /usr/portage/profiles/package.mask ; echo -e "\\n\\nTerminated." ; exit 1" \ # SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM # mv -i /usr/portage/profiles/package.mask /usr/portage/profiles/package.mask.hidden #fi # Run in background to correctly handle Ctrl-C ( emerge --oneshot $@ $REBUILD_LIST echo $? >$LLIST.6_status ) & wait #if $EXACT_EBUILDS ; then # mv -i /usr/portage/profiles/package.mask.hidden /usr/portage/profiles/package.mask # trap - SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM #fi if [ "$(cat $LLIST.6_status)" -gt 0 ] ; then echo echo -e "${RD}revdep-rebuild failed to emerge all packages${NO}" echo -e "${RD}you have the following choices:${NO}" echo echo "- if emerge failed during the build, fix the problems and re-run revdep-rebuild" echo " or" echo "- use -X or --package-names as first argument (trys to rebuild package, not exact" echo " ebuild)" echo " or" echo "- set ACCEPT_KEYWORDS=\"~\" and/or /etc/portage/package.unmask" echo " (and remove $LLIST.5_order to be evaluated again)" echo " or" echo "- modify the above emerge command and run it manually" echo " or" echo "- compile or unmerge unsatisfied packages manually, remove temporary files and" echo " try again (you can edit package/ebuild list first)" echo echo -e "${GR}To remove temporary files, please run:${NO}" echo "rm $LIST*.?_*" exit $(cat $LLIST.6_status) else if $IS_REAL_MERGE ; then trap "echo -e \" terminated. Please remove them manually:\nrm $LIST*.?_*\" ; exit 1" \ SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM echo -n -e "${GR}Build finished correctly. Removing temporary files...${NO} " for i in . . . . . . . . . . ; do echo -n -e '.' sleep 1 done echo rm $LIST.[0-2]_* rm $LLIST.[3-9]_* echo "You can re-run revdep-rebuild to verify that all libraries and binaries" echo "are fixed. If some inconsistency remains, it can be orphaned file, deep" echo "dependency, binary package or specially evaluated library." else echo -e "${GR}Now you can remove -p (or --pretend) from arguments and re-run revdep-rebuild.${NO}" fi fi exit 0