X-Git-Url: http://git.tremily.us/?a=blobdiff_plain;f=trunk%2Fsrc%2Frevdep-rebuild%2Frevdep-rebuild;h=7767990fba78b1755432b84e405ca4a1299f84be;hb=e7841776e2a6ba3dc0d22833fcb976bd66e440fd;hp=657e7ba6703e39131a9d1bd051afe945b18154c8;hpb=ef6c76d25b199deb61fc88b5b7ecbd800b6dedd5;p=gentoolkit.git diff --git a/trunk/src/revdep-rebuild/revdep-rebuild b/trunk/src/revdep-rebuild/revdep-rebuild index 657e7ba..7767990 100755 --- a/trunk/src/revdep-rebuild/revdep-rebuild +++ b/trunk/src/revdep-rebuild/revdep-rebuild @@ -1,677 +1,1086 @@ -#! /bin/bash -# Copyright 1999-2005 Gentoo Foundation -# $Header$ +#!/bin/bash +# Copyright 1999-2008 Gentoo Foundation # revdep-rebuild: Reverse dependency rebuilder. # Original Author: Stanislav Brabec +# Rewrite Author: Michael A. Smith # Current Maintainer: Paul Varner -# 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 +# TODO: +# - Use more /etc/init.d/functions.sh +# - Try to reduce the number of global vars -# 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 either by setting the variable in +## +# Global Variables: + +# Must-be-blank: +unset GREP_OPTIONS + +# Readonly variables: +declare -r APP_NAME="${0##*/}" # The name of this application +declare -r OIFS="$IFS" # Save the IFS +declare -r ENV_FILE=0_env.rr # Contains environment variables +declare -r FILES_FILE=1_files.rr # Contains a list of files to search +declare -r LDPATH_FILE=2_ldpath.rr # Contains the LDPATH +declare -r BROKEN_FILE=3_broken.rr # Contains the list of broken files +declare -r ERRORS_FILE=3_errors.rr # Contains the ldd error output +declare -r RAW_FILE=4_raw.rr # Contains the raw list of packages +declare -r OWNERS_FILE=4_owners.rr # Contains the file owners +declare -r PKGS_FILE=4_pkgs.rr # Contains the unsorted bare package names +declare -r EBUILDS_FILE=4_ebuilds.rr # Contains the unsorted atoms + # (Appropriately slotted or versioned) +declare -r ORDER_FILE=5_order.rr # Contains the sorted atoms +declare -r STATUS_FILE=6_status.rr # Contains the ldd error output +declare -ra FILES=( + "$ENV_FILE" + "$FILES_FILE" + "$LDPATH_FILE" + "$BROKEN_FILE" + "$ERRORS_FILE" + "$RAW_FILE" + "$OWNERS_FILE" + "$PKGS_FILE" + "$EBUILDS_FILE" + "$ORDER_FILE" + "$STATUS_FILE" +) + +# "Boolean" variables: Considered "true" if it has any value at all +# "True" indicates we should... +declare FULL_LD_PATH # ...search across the COMPLETE_LD_LIBRARY_PATH +declare KEEP_TEMP # ...not delete tempfiles from the current run +declare ORDER_PKGS # ...sort the atoms in deep dependency order +declare PACKAGE_NAMES # ...emerge by slot, not by versionated atom +declare RM_OLD_TEMPFILES # ...remove tempfiles from prior runs +declare SEARCH_BROKEN # ...search for broken libraries and binaries +declare VERBOSE # ...give verbose output + +# Globals that impact portage directly: +declare EMERGE_DEFAULT_OPTS # String of options portage assumes to be set +declare EMERGE_OPTIONS # Array of options to pass to portage +declare PORTAGE_NICENESS # Renice to this value +declare PORTAGE_ROOT # The root path for portage + +# Customizable incremental variables: +# 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 +declare LD_LIBRARY_MASK # Mask of specially evaluated libraries +declare SEARCH_DIRS # List of dirs to search for executables and libraries +declare SEARCH_DIRS_MASK # List of dirs not to search -if [ "$1" = "-h" -o "$1" = "--help" ] -then - echo "Usage: $0 [OPTIONS] [--] [EMERGE_OPTIONS]" - echo - echo "Broken reverse dependency rebuilder." - echo - echo " -X, --package-names Emerge based on package names, not exact versions" - echo " --library NAME Emerge existing packages that use the library with NAME" - echo " --library=NAME NAME can be a full path to the library or a basic" - echo " regular expression (man grep)" - echo " -nc, --no-color Turn off colored output" - echo " -i, --ignore Ignore temporary files from previous runs" - echo " -q, --quiet Be less verbose (also passed to emerge command)" - echo " -vv, --extra-verbose Be extra verbose" - echo - echo "Calls emerge, all other options are used for it (e. g. -p, --pretend)." +# Other globals: +declare OLDPROG # Previous pass through the progress meter +declare EXACT_PKG # Versionated atom to emerge +declare HEAD_TEXT # Feedback string about the search +declare NOCOLOR # Set to "true" not to output term colors +declare OK_TEXT # Feedback about a search which found no errors +declare RC_NOCOLOR # Hack to insure we respect NOCOLOR +declare REBUILD_LIST # Array of atoms to emerge +declare SKIP_LIST # Array of atoms that cannot be emerged (masked?) +declare SONAME # Soname/soname path pattern given on commandline +declare SONAME_SEARCH # Value of SONAME modified to match ldd's output +declare WORKING_TEXT # Feedback about the search + +main() { + # preliminary setup + get_opts "$@" + setup_portage + setup_search_paths_and_masks + get_search_env echo - echo "Report bugs to " - exit 0 -fi - -echo "Configuring search environment for revdep-rebuild" - -# Obey PORTAGE_NICENESS -PORTAGE_NICENESS=$(portageq envvar PORTAGE_NICENESS) -if [ ! -z "$PORTAGE_NICENESS" ] -then - renice $PORTAGE_NICENESS $$ > /dev/null - # Since we have already set our nice value for our processes, - # reset PORTAGE_NICENESS to zero to avoid having emerge renice again. - export PORTAGE_NICENESS="0" -fi - -PORTAGE_ROOT=$(portageq envvar ROOT) -[ -z "$PORTAGE_ROOT" ] && PORTAGE_ROOT="/" - -# Update the incremental variables using /etc/profile.env, /etc/ld.so.conf, -# portage, and the environment - -# Read the incremental variables from environment and portage -# Until such time as portage supports these variables as incrementals -# The value will be what is in /etc/make.conf -PRELIMINARY_SEARCH_DIRS="$SEARCH_DIRS $(portageq envvar SEARCH_DIRS)" -PRELIMINARY_SEARCH_DIRS_MASK="$SEARCH_DIRS_MASK $(portageq envvar SEARCH_DIRS_MASK)" -PRELIMINARY_LD_LIBRARY_MASK="$LD_LIBRARY_MASK $(portageq envvar LD_LIBRARY_MASK)" - -# Add the defaults -if [ -d /etc/revdep-rebuild ] -then - for file in $(ls /etc/revdep-rebuild) - do - PRELIMINARY_SEARCH_DIRS="$PRELIMINARY_SEARCH_DIRS $(. /etc/revdep-rebuild/${file}; echo $SEARCH_DIRS)" - PRELIMINARY_SEARCH_DIRS_MASK="$PRELIMINARY_SEARCH_DIRS_MASK $(. /etc/revdep-rebuild/${file}; echo $SEARCH_DIRS_MASK)" - PRELIMINARY_LD_LIBRARY_MASK="$PRELIMINARY_LD_LIBRARY_MASK $(. /etc/revdep-rebuild/${file}; echo $LD_LIBRARY_MASK)" - done -else - PRELIMINARY_SEARCH_DIRS="$PRELIMINARY_SEARCH_DIRS /bin /sbin /usr/bin /usr/sbin /lib* /usr/lib*" - PRELIMINARY_SEARCH_DIRS_MASK="$PRELIMINARY_SEARCH_DIRS_MASK /opt/OpenOffice /usr/lib/openoffice" - 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 - -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=grep -SEARCH_BROKEN=true -EXTRA_VERBOSE=false -KEEP_TEMP=false - -EMERGE_OPTIONS="" -PRELIMINARY_CALLED_OPTIONS="" -while [ ! -z "$1" ] ; do - case "$1" in - -X | --package-names ) - PACKAGE_NAMES=true - PRELIMINARY_CALLED_OPTIONS="${PRELIMINARY_CALLED_OPTIONS} --package_names" - shift - ;; - -q | --quiet ) - alias echo_v=: - EMERGE_OPTIONS="${EMERGE_OPTIONS} $1" - shift - ;; - --library=* | --soname=* | --soname-regexp=* ) - SONAME="${1#*=}" - SEARCH_BROKEN=false - PRELIMINARY_CALLED_OPTIONS="${PRELIMINARY_CALLED_OPTIONS} --library=${SONAME}" - shift - ;; - --library | --soname | --soname-regexp ) - SONAME="$2" - SEARCH_BROKEN=false - PRELIMINARY_CALLED_OPTIONS="${PRELIMINARY_CALLED_OPTIONS} --library=${SONAME}" - shift 2 - ;; - -nc | --no-color ) - NOCOLOR=true - shift - ;; - -i | --ignore ) - rm -f ${LIST}* - shift - ;; - --keep-temp ) - KEEPTEMP=true - shift - ;; - -vv | --extra-verbose ) - EXTRA_VERBOSE=true - shift - ;; - -- ) - shift - ;; - * ) - EMERGE_OPTIONS="${EMERGE_OPTIONS} $1" - shift - ;; - esac -done + # Search for broken binaries + get_files + get_ldpath + main_checks + + # Associate broken binaries with packages to rebuild + if [[ $PACKAGE_NAMES ]]; then + get_packages + clean_packages + assign_packages_to_ebuilds + else + get_exact_ebuilds + fi + + # Rebuild packages owning broken binaries + get_build_order + rebuild + + # All done + cleanup +} +## +# Refuse to delete anything before we cd to our tmpdir +# (See mkdir_and_cd_to_tmpdir() +rm() { + eerror "I was instructed to rm '$@'" + die 1 "Refusing to delete anything before changing to temporary directory." +} +## +# GNU find has -executable, but if our users' finds do not have that flag +# we emulate it with this function. Also emulates -writable and -readable. +# Usage: find PATH ARGS -- use find like normal, except use -executable instead +# of various versions of -perm /+ blah blah and hacks +find() { + hash find || { die 1 'find not found!'; } + # We can be pretty sure find itself should be executable. + local testsubject="$(type -P find)" + if [[ $(command find "$testsubject" -executable 2> /dev/null) ]]; then + unset -f find # We can just use the command find + elif [[ $(command find "$testsubject" -perm /u+x 2> /dev/null) ]]; then + find() { + a=(${@//-executable/-perm \/u+x}) + a=(${a[@]//-writable/-perm \/u+w}) + a=(${a[@]//-readable/-perm \/r+w}) + command find "${a[@]}" + } + elif [[ $(command find "$testsubject" -perm +u+x 2> /dev/null) ]]; then + find() { + a=(${@//-executable/-perm +u+x}) + a=(${a[@]//-writable/-perm +u+w}) + a=(${a[@]//-readable/-perm +r+w}) + command find "${a[@]}" + } + else # Last resort + find() { + a=(${@//-executable/-exec test -x '{}' \; -print}) + a=(${a[@]//-writable/-exec test -w '{}' \; -print}) + a=(${a[@]//-readable/-exec test -r '{}' \; -print}) + command find "${a[@]}" + } + fi + find "$@" +} + +print_usage() { +cat << EOF +Usage: $APP_NAME [OPTIONS] [--] [EMERGE_OPTIONS] + +Broken reverse dependency rebuilder. -EMERGE_OPTIONS=$(echo $EMERGE_OPTIONS | sed 's/^ //') + -C, --nocolor Turn off colored output + -d, --debug Print way too much information (uses bash's set -xv) + -e, --exact Emerge based on exact package version + -h, --help Print this usage + -i, --ignore Ignore temporary files from previous runs + -k, --keep-temp Do not delete temporary files on exit + -L, --library NAME Emerge existing packages that use the library with NAME + --library=NAME NAME can be a full path to the library or a basic + regular expression (man grep) + -l, --no-ld-path Do not set LD_LIBRARY_PATH + -o, --no-order Do not check the build order + (Saves time, but may cause breakage.) + -p, --pretend Do a trial run without actually emerging anything + (also passed to emerge command) + -P, --no-progress Turn off the progress meter + -q, --quiet Be less verbose (also passed to emerge command) + -v, --verbose Be more verbose (also passed to emerge command) -if [ -z "$PRELIMINARY_CALLED_OPTIONS" ] -then - CALLED_OPTIONS="" -else - for i in $(echo $PRELIMINARY_CALLED_OPTIONS | tr ' ' '\n'| sort) - do - CALLED_OPTIONS="$(echo $CALLED_OPTIONS $i)" +Calls emerge, options after -- are ignored by $APP_NAME +and passed directly to emerge. + +Report bugs to +EOF +} +## +# Usage: progress i n +# i: current item +# n: total number of items to process +progress() { + if [[ -t 1 ]]; then + progress() { + local curProg=$(( $1 * 100 / $2 )) + (( curProg == OLDPROG )) && return # no change, output nothing + OLDPROG="$curProg" # must be a global variable + (( $1 == $2 )) && local lb=$'\n' + echo -ne '\r \r'"[ $curProg% ] $lb" + } + progress $@ + else # STDOUT is not a tty. Disable progress meter. + progress() { :; } + fi +} +## +# Usage: countdown n +# n: number of seconds to count +countdown() { + local i + for ((i=1; i<$1; i++)); do + echo -ne '\a.' + ((i<$1)) && sleep 1 done -fi - -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 -e '\a.' +} +## +# Replace whitespace with linebreaks, normalize repeated '/' chars, and sort -u +# (If any libs have whitespace in their filenames, someone needs punishment.) +clean_var() { + awk 'BEGIN {RS="[[:space:]]"} + /-\*/ {exit} + /[^[:space:]]/ {gsub(/\/\/+/, "/"); print}' | sort -u +} +## +# Exit and optionally output to sterr +die() { + local status=$1 + shift + eerror "$@" + exit $status +} +## +# What to do when dynamic linking is consistent +clean_exit() { + [[ $KEEP_TEMP ]] || rm -f "${FILES[@]}" + echo + einfo "$OK_TEXT... All done. " + exit 0 +} +## +# Get the name of the package that owns a file or list of files given as args. +get_file_owner() { + local IFS=$'\n' + # ${*/%/ } adds a space to the end of each object name to prevent false + # matches, for example /usr/bin/dia matching /usr/bin/dialog (bug #196460). + find -L /var/db/pkg -name CONTENTS -print0 | + xargs -0 grep -Fl "${*/%/ }" | + sed 's:/var/db/pkg/\(.*\)/CONTENTS:\1:' +} +## +# Normalize some EMERGE_OPTIONS +normalize_emerge_opts() { + # Normalize some EMERGE_OPTIONS + EMERGE_OPTIONS=(${EMERGE_OPTIONS[@]/%-p/--pretend}) + EMERGE_OPTIONS=(${EMERGE_OPTIONS[@]/%-f/--fetchonly}) + EMERGE_OPTIONS=(${EMERGE_OPTIONS[@]/%-f/--verbose}) +} +## +# Use the color preference from portage +setup_color() { + # This should still work if NOCOLOR is set by the -C flag or in the user's + # environment. + export NOCOLOR=$(portageq envvar NOCOLOR) + [[ $NOCOLOR = yes || $NOCOLOR = true ]] && export RC_NOCOLOR=yes # HACK! (grr) + . /etc/init.d/functions.sh +} +## +# Die if an argument is missing. +die_if_missing_arg() { + [[ ! $2 || $2 = -* ]] && die 1 "Missing expected argument to $1" +} +## +# Die because an option is not recognized. +die_invalid_option() { + # Can't use eerror and einfo because this gets called before function.sh + # is sourced + echo + echo "Encountered unrecognized option $1." >&2 + echo + echo "$APP_NAME no longer automatically passes unrecognized options to portage." + echo "Separate emerge-only options from revdep-rebuild options with the -- flag." + echo + echo "For example, $APP_NAME -v -- --ask" + echo + echo "See the man page or $APP_NAME -h for more detail." echo exit 1 } +## +# Warn about deprecated options. +warn_deprecated_opt() { + # Can't use eerror and einfo because this gets called before function.sh + # is sourced + echo + echo "Encountered deprecated option $1." >&2 + [[ $2 ]] && echo "Please use $2 instead." >&2 +} +## +# Get whole-word commandline options preceded by two dashes. +get_longopts() { + case $1 in + --nocolor) export NOCOLOR="yes";; + --no-color) warn_deprecated_opt "$1" "--nocolor" + export NOCOLOR="yes";; + --debug) set -xv;; + --exact) unset PACKAGE_NAMES;; + --help) print_usage + exit 0;; + --ignore) RM_OLD_TEMPFILES=1;; + --keep-temp) KEEP_TEMP=1;; + --library=*) # TODO: check for invalid values + SONAME="${1#*=}" + unset SEARCH_BROKEN;; + --soname=*|--soname-regexp=*) # TODO: check for invalid values + warn_deprecated_opt "${1%=*}" "--library" + SONAME="${1#*=}" + unset SEARCH_BROKEN;; + --library) # TODO: check for invalid values + die_if_missing_arg $1 $2 + shift + SONAME="$1" + unset SEARCH_BROKEN;; + --soname|--soname-regexp) # TODO: check for invalid values + warn_deprecated_opt "$1" "--library" + die_if_missing_arg $1 $2 + shift + SONAME="$1" + unset SEARCH_BROKEN;; + --no-ld-path) unset FULL_LD_PATH;; + --no-order) unset ORDER_PKGS;; + --no-progress) progress() { :; };; + --pretend) EMERGE_OPTIONS+=("--pretend");; + --quiet) echo_v() { :; } + progress() { :; } + quiet=1 + EMERGE_OPTIONS+=($1);; + --verbose) VERBOSE=1 + EMERGE_OPTIONS+=("--verbose");; + --extra-verbose) warn_deprecated_opt "$1" "--verbose" + VERBOSE=1 + EMERGE_OPTIONS+=("--verbose");; + --package-names) # No longer used, since it is the + # default. We accept it for + # backwards compatibility. + warn_deprecated_opt "$1" + PACKAGE_NAMES=1;; + *) die_invalid_option $1;; + esac +} -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 - # Set to "$SONAME" - SONAME_SEARCH=" $SONAME " - else - # Set to "$SONAME" - SONAME_SEARCH=" $SONAME " +## +# Get single-letter commandline options preceded by a single dash. +get_shortopts() { + local OPT OPTSTRING OPTARG OPTIND + while getopts ":CdehikL:loPpqu:vX" OPT; do + case "$OPT" in + C) # TODO: Match syntax with the rest of gentoolkit + export NOCOLOR="yes";; + d) set -xv;; + e) unset PACKAGE_NAMES;; + h) print_usage + exit 0;; + i) RM_OLD_TEMPFILES=1;; + k) KEEP_TEMP=1;; + L) # TODO: Check for invalid values + SONAME="${OPTARG#*=}" + unset SEARCH_BROKEN;; + l) unset FULL_LD_PATH;; + o) unset ORDER_PKGS;; + P) progress() { :; };; + p) EMERGE_OPTIONS+=("--pretend");; + q) echo_v() { :; } + progress() { :; } + quiet=1 + EMERGE_OPTIONS+=("--quiet");; + v) VERBOSE=1 + EMERGE_OPTIONS+=("--verbose");; + X) # No longer used, since it is the default. + # We accept it for backwards compatibility. + warn_deprecated_opt "-X" + PACKAGE_NAMES=1;; + *) die_invalid_option "-$OPTARG";; + esac + done +} +## +# Get command-line options. +get_opts() { + local avoid_utils + local -a args + echo_v() { ewarn "$@"; } + unset VERBOSE KEEP_TEMP EMERGE_OPTIONS RM_OLD_TEMPFILES + ORDER_PKGS=1 + PACKAGE_NAMES=1 + SONAME="not found" + SEARCH_BROKEN=1 + FULL_LD_PATH=1 + while [[ $1 ]]; do + case $1 in + --) shift + EMERGE_OPTIONS+=("$@") + break;; + -*) while true; do + args+=("$1") + shift + [[ ${1:--} = -* ]] && break + done + if [[ ${args[0]} = --* ]]; then + get_longopts "${args[@]}" + else + get_shortopts "${args[@]}" + fi;; + *) die_invalid_option "$1";; + esac + unset args + done + + setup_color + normalize_emerge_opts + + # If the user is not super, add --pretend to EMERGE_OPTIONS + if [[ ${EMERGE_OPTIONS[@]} != *--pretend* && $UID -ne 0 ]]; then + ewarn "You are not superuser. Adding --pretend to emerge options." + EMERGE_OPTIONS+=(--pretend) 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 any of our temporary files are older than 1 day, remove them all -[ "$(find "${LIST%/*}/." ! -name . -prune -name "${LIST##*/}*" -type f -mmin +1440)" != "" ] && rm -f ${LIST}* - -# Don't use our previous files if environment doesn't match -if [ -f $LIST.0_env ] -then - PREVIOUS_SEARCH_DIRS=$(. ${LIST}.0_env; echo "$SEARCH_DIRS") - PREVIOUS_SEARCH_DIRS_MASK=$(. ${LIST}.0_env; echo "$SEARCH_DIRS_MASK") - PREVIOUS_LD_LIBRARY_MASK=$(. ${LIST}.0_env; echo "$LD_LIBRARY_MASK") - PREVIOUS_PORTAGE_ROOT=$(. ${LIST}.0_env; echo "$PORTAGE_ROOT") - PREVIOUS_OPTIONS=$(. ${LIST}.0_env; echo "$CALLED_OPTIONS") - if [ "$PREVIOUS_SEARCH_DIRS" != "$SEARCH_DIRS" ] || \ - [ "$PREVIOUS_SEARCH_DIRS_MASK" != "$SEARCH_DIRS_MASK" ] || \ - [ "$PREVIOUS_LD_LIBRARY_MASK" != "$LD_LIBRARY_MASK" ] || \ - [ "$PREVIOUS_PORTAGE_ROOT" != "$PORTAGE_ROOT" ] || \ - [ "$PREVIOUS_OPTIONS" != "$CALLED_OPTIONS" ] - then - echo - echo "Environment mismatch from previous run, deleting temporary files..." - rm -f ${LIST}* +} +## +# Is there a --pretend or --fetchonly flag in the EMERGE_OPTIONS array? +is_real_merge() { + [[ ${EMERGE_OPTIONS[@]} != *--pretend* && + ${EMERGE_OPTIONS[@]} != *--fetchonly* ]] +} +## +# Clean up temporary files and exit +cleanup_and_die() { + rm -f "$@" + die 1 " ...terminated. Removing incomplete $@." +} +## +# Clean trap +clean_trap() { + trap "cleanup_and_die $*" SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM + rm -f "$@" +} +## +# Returns 0 if the first arg is found in the remaining args, 1 otherwise +# (Returns 2 if given fewer than 2 arguments) +has() { + (( $# > 1 )) || return 2 + local IFS=$'\a' target="$1" + shift + [[ $'\a'"$*"$'\a' = *$'\a'$target$'\a'* ]] +} +## +# Dies when it can't change directories +cd() { + if builtin cd -P "$@"; then + if [[ $1 != $PWD ]]; then + # Some symlink malfeasance is going on + die 1 "Working directory expected to be $1, but it is $PWD" + fi + else + die 1 "Unable to change working directory to '$@'" fi -fi - -# 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 "PORTAGE_ROOT=\"$PORTAGE_ROOT\"" >> $LIST.0_env -echo "CALLED_OPTIONS=\"$CALLED_OPTIONS\"" >> $LIST.0_env -echo "EMERGE_OPTIONS=\"$EMERGE_OPTIONS\"" >> $LIST.0_env - -if $EXTRA_VERBOSE -then - echo - echo "revdep-rebuild environment:" - cat $LIST.0_env -fi - -echo -echo "Checking reverse dependencies..." -echo -echo "Packages containing binaries and libraries $HEAD_TEXT" -echo "will be emerged." - -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}.[1-9]_* - - set_trap "$LIST.1_*" - - # Hack for the different versions of find. - find_results=$(find /usr/bin/revdep-rebuild -type f -perm /u+x 2>/dev/null) - if [ -z $find_results ] - then - find_results=$(find /usr/bin/revdep-rebuild -type f -perm +u+x 2>/dev/null) - if [ -z $find_results ] - then - echo -e "\n" - echo -e "${RD}Unable to determine how to use find to locate executable files${NO}" - echo -e "${RD}Open a bug at http://bugs.gentoo.org${NO}" - echo - exit 1 - else - # 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 >$LIST.0_files +} +## +# Tries not to delete any files or directories it shouldn't +setup_rm() { + ## + # Anything in the FILES array in tmpdir is fair game for removal + rm() { + local i IFS=$'\a' + [[ $APP_NAME ]] || die 1 '$APP_NAME is not defined! (This is a bug.)' + case $@ in + */*|*-r*|*-R*) die 1 "Oops, I'm not allowed to delete that. ($@)";; + esac + for i; do + # Don't delete files that are not listed in the array + # Allow no slashes or recursive deletes at all. + case $i in + */*|-*r*|-*R*) :;; # Not OK + -*) continue;; # OK + esac + has "$i" "${FILES[@]}" && continue + die 1 "Oops, I'm not allowed to delete that. ($@)" + done + command rm "$@" + } + # delete this setup function so it's harmless to re-run + setup_rm() { :; } +} +## +# Make our temporary files directory +setup_tmpdir() { + umask 007 || die $? "Unable to set umask 007" + if [[ ! $1 ]]; then + die 1 'Temporary file path is unset! (This is a bug.)' + elif [[ -d $1 ]]; then + # HACK: I hate using find this way + if [[ $(find "$1" -type d ! \( -user $2 -group portage -perm -0700 \) ) ]]; then + eerror "Incorrect permissions on $1" + eerror "or at least one file in $1." + die 1 "Please make sure it's not a symlink and then remove it." fi + cd "$1" + elif mkdir -m 0700 "$1" && chown :portage "$1"; then + cd "$1" else - # 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 >$LIST.0_files + die 1 "Unable to find or create a satisfactory location for temporary files" fi + [[ $VERBOSE ]] && einfo "Temporary files are located in $PWD" + setup_rm +} +get_search_env() { + local new_env + local old_env + local uid=$(python -c 'import os; import pwd; print pwd.getpwuid(os.getuid())[0]') + # Find a place to put temporary files + # Use "${TMPDIR}/revdep-rebuild" or /tmp/revdep-rebuild + local tmp_target="${TMPDIR:=/tmp}/${APP_NAME}-${uid}" - # 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 + # From here on all work is done inside the temporary directory + setup_tmpdir "$tmp_target" "$uid" -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." + if [[ $SEARCH_BROKEN ]]; then + SONAME_SEARCH="$SONAME" + HEAD_TEXT="broken by a package update" + OK_TEXT="Dynamic linking on your system is consistent" + WORKING_TEXT="consistency" 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 | 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 $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 - # FIX: I hate duplicating code - # 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" >>$LLIST.3_rebuild - echo_v " broken $FILE (requires ${MISSING_LIBS})" - fi - fi + # first case is needed to test against /path/to/foo.so + if [[ $SONAME = /* ]]; then + # Set to "$SONAME" + SONAME_SEARCH=" $SONAME " + # Escape the "/" characters + SONAME_SEARCH="${SONAME_SEARCH//\//\\/}" else - # FIX: I hate duplicating code - # Only rebuild for direct dependencies - ALL_MISSING_LIBS=$(ldd "$FILE" 2>/dev/null | sort -u | $SONAME_GREP "$SONAME_SEARCH" | awk '{print $1}' | 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" >>$LLIST.3_rebuild - echo_v " found $FILE" - fi + # Set to "$SONAME" + SONAME_SEARCH=$'\t'"$SONAME " fi + local uuid="${SONAME##*/}" + uuid="${uuid//[[:space:]]}" + uuid="${uuid//\*}" + HEAD_TEXT="using $SONAME" + OK_TEXT="There are no dynamic links to $SONAME" + unset WORKING_TEXT + setup_tmpdir "$uuid" fi - done - if $SEARCH_BROKEN ; then - 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" >>$LLIST.3_rebuild - echo_v " broken $FILE (requires ${depend})" - fi - done - done + + # If any of our temporary files are older than 1 day, remove them all + if [[ ! $KEEP_TEMP ]]; then + while read; do + RM_OLD_TEMPFILES=1 + break + done < <(find -L . -maxdepth 1 -type f -name '*.rr' -mmin +1440 -print 2>/dev/null) fi - echo -e " done.\n ($LLIST.3_rebuild)" -fi -if $PACKAGE_NAMES ; then - EXACT_EBUILDS=false + # Compare old and new environments + # Don't use our previous files if environment doesn't match + new_env=$( + # We do not care if these emerge options change + EMERGE_OPTIONS=(${EMERGE_OPTIONS[@]//--pretend/}) + EMERGE_OPTIONS=(${EMERGE_OPTIONS[@]//--fetchonly/}) + EMERGE_OPTIONS=(${EMERGE_OPTIONS[@]//--verbose/}) + cat <<- EOF + SEARCH_DIRS="$SEARCH_DIRS" + SEARCH_DIRS_MASK="$SEARCH_DIRS_MASK" + LD_LIBRARY_MASK="$LD_LIBRARY_MASK" + PORTAGE_ROOT="$PORTAGE_ROOT" + EMERGE_OPTIONS="${EMERGE_OPTIONS[@]}" + ORDER_PKGS="$ORDER_PKGS" + FULL_LD_PATH="$FULL_LD_PATH" + EOF + ) + if [[ -r "$ENV_FILE" && -s "$ENV_FILE" ]]; then + old_env=$(<"$ENV_FILE") + if [[ $old_env != $new_env ]]; then + ewarn 'Environment mismatch from previous run, deleting temporary files...' + RM_OLD_TEMPFILES=1 + fi + else + # No env file found, silently delete any other tempfiles that may exist + RM_OLD_TEMPFILES=1 + fi - echo - echo -n -e "${GR}Assigning files to packages...${NO}" - if [ -f $LLIST.4_packages_raw ] ; then - echo " using existing $LLIST.4_packages_raw." + # If we should remove old tempfiles, do so + if [[ $RM_OLD_TEMPFILES ]]; then + rm -f "${FILES[@]}" 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" + for file in "${FILES[@]}"; do + if [ -e "$file" ]; then + chown ${uid}:portage "$file" + chmod 700 "$file" fi done - echo_v - echo -e " done.\n ($LLIST.4_packages_raw, $LLIST.4_package_owners)" fi + # Save the environment in a file for next time + echo "$new_env" > "$ENV_FILE" + +# [[ $VERBOSE ]] && echo $'\n'"$APP_NAME environment:"$'\n'"$new_env" + [[ $VERBOSE ]] && echo $'\n'"$APP_NAME environment:"$'\n'"$(cat $ENV_FILE)" + echo - echo -n -e "${GR}Cleaning list of packages to rebuild...${NO}" - if [ -f $LLIST.4_packages ] ; then - echo " using existing $LLIST.4_packages." + einfo "Checking reverse dependencies" + einfo "Packages containing binaries and libraries $HEAD_TEXT" + einfo "will be emerged." +} +get_files() { + einfo "Collecting system binaries and libraries" + if [[ -r "$FILES_FILE" && -s "$FILES_FILE" ]]; then + einfo "Found existing $FILES_FILE" else - sort -u $LLIST.4_packages_raw >$LLIST.4_packages - echo -e " done.\n ($LLIST.4_packages)" - fi + # Be safe and remove any extraneous temporary files + # Don't remove -_env.rr - The first file in the array + rm -f "${FILES[@]:1}" - echo - echo -n -e "${GR}Assigning packages to ebuilds...${NO}" - if [ -f $LLIST.4_ebuilds ] ; then - echo " using existing $LLIST.4_ebuilds." + clean_trap "$FILES_FILE" + + if [[ $SEARCH_DIRS_MASK ]]; then + findMask=($SEARCH_DIRS_MASK) + findMask="${findMask[@]/#/-o -path }" + findMask="( ${findMask#-o } ) -prune -o" + fi + # TODO: Check this + find ${SEARCH_DIRS[@]} $findMask -type f \( -executable -o \ + -name '*.so' -o -name '*.so.*' -o -name '*.la' \) -print 2> /dev/null | + sort -u > "$FILES_FILE" || + die $? "find failed to list binary files (This is a bug.)" + einfo "Generated new $FILES_FILE" + fi +} +get_ldpath() { + local COMPLETE_LD_LIBRARY_PATH + [[ $SEARCH_BROKEN && $FULL_LD_PATH ]] || return + einfo 'Collecting complete LD_LIBRARY_PATH' + if [[ -r "$LDPATH_FILE" && -s "$LDPATH_FILE" ]]; then + einfo "Found existing $LDPATH_FILE." 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 - portageq best_visible $PORTAGE_ROOT $PKG - continue - fi - # Otherwise mask the other SLOTTED versions and check for latest - OTHER_VERSIONS=$(/usr/lib/gentoolkit/bin/find_pkgs.py $PKG | grep -v "($SLOT)" | awk '{print $2}') - 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 + clean_trap "$LDPATH_FILE" + # Ensure that the "trusted" lib directories are at the start of the path + COMPLETE_LD_LIBRARY_PATH=( + /lib* + /usr/lib* + $(sed '/^#/d;s/#.*$//' < /etc/ld.so.conf) + $(sed 's:/[^/]*$::' < "$FILES_FILE" | sort -ru) + ) + IFS=':' + COMPLETE_LD_LIBRARY_PATH="${COMPLETE_LD_LIBRARY_PATH[*]}" + IFS="$OIFS" + echo "$COMPLETE_LD_LIBRARY_PATH" > "$LDPATH_FILE" + einfo "Generated new $LDPATH_FILE" + fi +} +main_checks() { + local target_file + local -a files + local i=0 + local ldd_output + local ldd_status + local numFiles + local COMPLETE_LD_LIBRARY_PATH + if [[ $SEARCH_BROKEN && $FULL_LD_PATH ]]; then + [[ -r "$LDPATH_FILE" && -s "$LDPATH_FILE" ]] || + die 1 "Unable to find $LDPATH_FILE" + COMPLETE_LD_LIBRARY_PATH=$(<"$LDPATH_FILE") + fi + einfo "Checking dynamic linking $WORKING_TEXT" + if [[ -r "$BROKEN_FILE" && -s "$BROKEN_FILE" ]]; then + einfo "Found existing $BROKEN_FILE." + else + clean_trap "$BROKEN_FILE" "$ERRORS_FILE" + files=($(<"$FILES_FILE")) + numFiles="${#files[@]}" + for target_file in "${files[@]}"; do + if [[ $target_file != *.la ]]; then + # Note: double checking seems to be faster than single with complete path + # (special add ons are rare). + ldd_output=$(ldd "$target_file" 2>> "$ERRORS_FILE" | sort -u) + ldd_status=$? # TODO: Check this for problems with sort + # HACK: if LD_LIBRARY_MASK is null or undefined grep -vF doesn't work + if grep -vF "${LD_LIBRARY_MASK:=$'\a'}" <<< "$ldd_output" | + grep -q "$SONAME_SEARCH"; then + if [[ $SEARCH_BROKEN && $FULL_LD_PATH ]]; then + if LD_LIBRARY_PATH="$COMPLETE_LD_LIBRARY_PATH" ldd "$target_file" 2>/dev/null | + grep -vF "$LD_LIBRARY_MASK" | grep -q "$SONAME_SEARCH"; then + # FIXME: I hate duplicating code + # Only build missing direct dependencies + MISSING_LIBS=$( + expr='s/[[:space:]]*\([^[:space:]]*\) => not found/\1/p' + sed -n "$expr" <<< "$ldd_output" + ) + REQUIRED_LIBS=$( + expr='s/^[[:space:]]*NEEDED[[:space:]]*\([^[:space:]]*\).*/\1/p'; + objdump -x "$target_file" | grep NEEDED | sed "$expr" | sort -u + ) + MISSING_LIBS=$(grep -F "$REQUIRED_LIBS" <<< "$MISSING_LIBS") + if [[ $MISSING_LIBS ]]; then + echo "obj $target_file" >> "$BROKEN_FILE" + echo_v " broken $target_file (requires $MISSING_LIBS)" + fi + fi + else + # FIXME: I hate duplicating code + # Only rebuild for direct dependencies + MISSING_LIBS=$( + expr="/$SONAME_SEARCH/s/^[[:space:]]*\([^[:space:]]*\).*$/\1/p" + sort -u <<< "$ldd_output" | sed -n "$expr" + ) + REQUIRED_LIBS=$( + expr='s/^[[:space:]]*NEEDED[[:space:]]*\([^[:space:]]*\).*/\1/p'; + objdump -x "$target_file" | grep NEEDED | sed "$expr" | sort -u + ) + MISSING_LIBS=$(grep -F "$REQUIRED_LIBS" <<< "$MISSING_LIBS") + if [[ $MISSING_LIBS ]]; then + echo "obj $target_file" >> "$BROKEN_FILE" + if [[ $SEARCH_BROKEN ]]; then + echo_v " broken $target_file (requires $MISSING_LIBS)" + else + echo_v " found $target_file" + fi + fi + fi fi - for pkg_version in $(echo $OTHER_VERSIONS | tr '\n' ' ') - do - echo "=${PKG}-${pkg_version}" >> /etc/portage/package.mask + elif [[ $SEARCH_BROKEN ]]; then + # Look for broken .la files + for depend in $( + awk -F"[=']" '/^dependency_libs/{ + gsub("^-[^[:space:]]*", "", $3); + gsub("[[:space:]]-[^[:space:]]*", "", $3); + print $3 + }' "$target_file" + ); do + if [[ $depend = /* && ! -e $depend ]]; then + echo "obj $target_file" >> "$BROKEN_FILE" + echo_v " broken $target_file (requires $depend)" + fi done - portageq best_visible $PORTAGE_ROOT $PKG - 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 + [[ $VERBOSE ]] && + progress $((++i)) $numFiles $target_file || + progress $((++i)) $numFiles + done + if [[ $SEARCH_BROKEN ]]; then + # Look for missing version + while read target_file; do + echo "obj $target_file" >> "$BROKEN_FILE" + echo_v " broken $target_file (no version information available)" + done < <( + # Regexify LD_LIBRARY_MASK. Exclude it from the search. + LD_LIBRARY_MASK="${LD_LIBRARY_MASK//$'\n'/|}" + awk -v ldmask="(${LD_LIBRARY_MASK//./\\\.})" ' + /no version information available/ && $0 !~ ldmask { + gsub(/[()]/, "", $NF) + if (seen[$NF]++) next + print $NF + }' "$ERRORS_FILE" + ) fi + [[ -r "$BROKEN_FILE" && -s "$BROKEN_FILE" ]] || clean_exit + sort -u "$BROKEN_FILE" -o "$BROKEN_FILE" + einfo "Generated new $BROKEN_FILE" 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." +} +get_packages() { + local target_file + local EXACT_PKG + local PKG + local obj + einfo 'Assigning files to packages' + if [[ -r "$RAW_FILE" && -s "$RAW_FILE" ]]; then + einfo "Found existing $RAW_FILE" 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 + clean_trap "$RAW_FILE" "$OWNERS_FILE" + while read obj target_file; do + EXACT_PKG=$(get_file_owner $target_file) + if [[ $EXACT_PKG ]]; then + # Strip version information + PKG="${EXACT_PKG%%-r[[:digit:]]*}" + PKG="${PKG%-*}" + echo "$EXACT_PKG" >> "$RAW_FILE" + echo "$target_file -> $EXACT_PKG" >> "$OWNERS_FILE" + echo_v " $target_file -> $PKG" + else + ewarn " !!! $target_file not owned by any package is broken !!!" + echo "$target_file -> (none)" >> "$OWNERS_FILE" + echo_v " $target_file -> (none)" + fi + done < "$BROKEN_FILE" + einfo "Generated new $RAW_FILE and $OWNERS_FILE" + fi + # if we find '(none)' on every line, exit out + if ! grep -qvF '(none)' "$OWNERS_FILE"; then + ewarn "Found some broken files, but none of them were associated with known packages" + ewarn "Unable to proceed with automatic repairs." + ewarn "The broken files are listed in $OWNERS_FILE" + if [[ $VERBOSE ]]; then + ewarn "The broken files are:" + while read filename junk; do + ewarn " $filename" + done < "$OWNERS_FILE" fi + exit 0 # FIXME: Should we exit 1 here? + fi +} +clean_packages() { + einfo 'Cleaning list of packages to rebuild' + if [[ -r "$PKGS_FILE" && -s "$PKGS_FILE" ]]; then + einfo "Found existing $PKGS_FILE" + else + sort -u "$RAW_FILE" > "$PKGS_FILE" + einfo "Generated new $PKGS_FILE" + fi +} +assign_packages_to_ebuilds() { + local EXACT_PKG + local PKG + local SLOT + einfo 'Assigning packages to ebuilds' + if [[ -r "$EBUILDS_FILE" && -s "$EBUILDS_FILE" ]]; then + einfo "Found existing $EBUILDS_FILE" + elif [[ -r "$PKGS_FILE" && -s "$PKGS_FILE" ]]; then + clean_trap "$EBUILDS_FILE" + while read EXACT_PKG; do + # Get the slot + PKG="${EXACT_PKG%%-r[[:digit:]]*}" + PKG="${PKG%-*}" + SLOT=$( "$EBUILDS_FILE" + einfo "Generated new $EBUILDS_FILE" + else + einfo 'Nothing to rebuild.' + die 1 '(The program should have already quit, so this is a minor bug.)' fi +} +get_exact_ebuilds() { + einfo 'Assigning files to ebuilds' + if [[ -r $EBUILDS_FILE && -s $EBUILDS_FILE ]]; then + einfo "Found existing $EBUILDS_FILE" + elif [[ -r $BROKEN_FILE && -s $BROKEN_FILE ]]; then + rebuildList=" $(<"$BROKEN_FILE") " + rebuildList=(${rebuildList//[[:space:]]obj[[:space:]]/ }) + get_file_owner "${rebuildList[@]}" | sed 's/^/=/' > "$EBUILDS_FILE" + einfo "Generated new $EBUILDS_FILE" + else + einfo 'Nothing to rebuild.' + die 1 '(The program should have already quit, so this is a minor bug.)' + fi +} +list_skipped_packages() { + ewarn + ewarn 'Portage could not find any version of the following packages it could build:' + ewarn "${SKIP_LIST[@]}" + ewarn + ewarn '(Perhaps they are masked, blocked, or removed from portage.)' + ewarn 'Try to emerge them manually.' + ewarn +} +get_build_order() { + local -r OLD_EMERGE_DEFAULT_OPTS="$EMERGE_DEFAULT_OPTS" + local RAW_REBUILD_LIST + local REBUILD_GREP + local i + if [[ ! $ORDER_PKGS ]]; then + einfo 'Skipping package ordering' + return + fi + einfo 'Evaluating package order' + if [[ -r "$ORDER_FILE" && -s "$ORDER_FILE" ]]; then + einfo "Found existing $ORDER_FILE" + else + clean_trap "$ORDER_FILE" + RAW_REBUILD_LIST=$(<"$EBUILDS_FILE") + if [[ $RAW_REBUILD_LIST ]]; then + export EMERGE_DEFAULT_OPTS="--nospinner --pretend --oneshot --quiet" + RAW_REBUILD_LIST=($RAW_REBUILD_LIST) # convert into array + # If PACKAGE_NAMES is defined we're using slots, not versions + if [[ $PACKAGE_NAMES ]]; then + # Eliminate atoms that can't be built + for i in "${!RAW_REBUILD_LIST[@]}"; do + if [[ "${RAW_REBUILD_LIST[i]}" = *[A-Za-z]* ]]; then + portageq best_visible "$PORTAGE_ROOT" "${RAW_REBUILD_LIST[i]}" >/dev/null && continue + SKIP_LIST+=("${RAW_REBUILD_LIST[i]}") + fi + unset RAW_REBUILD_LIST[i] + done + # If RAW_REBUILD_LIST is empty, then we have nothing to build. + if (( ${#RAW_REBUILD_LIST[@]} == 0 )); then + if (( ${#SKIP_LIST[@]} == 0 )); then + ewarn "The list of packages to skip is empty, but there are no" + ewarn "packages listed to rebuild either. (This is a bug.)" + else + list_skipped_packages + fi + die 1 'Warning: Portage cannot rebuild any of the necessary packages.' + fi + fi + RAW_REBUILD_LIST="${RAW_REBUILD_LIST[@]}" + REBUILD_GREP=$(emerge --nodeps $RAW_REBUILD_LIST | sed 's/\[[^]]*\]//g') + if (( ${PIPESTATUS[0]} == 0 )); then + emerge --deep $RAW_REBUILD_LIST | + sed 's/\[[^]]*\]//g' | + grep -F "$REBUILD_GREP" > "$ORDER_FILE" + 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_DEFAULT_OPTS="" emerge --nospinner --pretend --oneshot --nodeps --quiet $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 + # Here we use the PIPESTATUS from the second emerge, the --deep one. + if (( ${PIPESTATUS[0]} != 0 )); then + eerror + eerror 'Warning: Failed to resolve package order.' + eerror 'Will merge in arbitrary order' + eerror + cat <<- EOF + Possible reasons: + - An ebuild is no longer in the portage tree. + - An ebuild is masked, use /etc/portage/packages.keyword + and/or /etc/portage/package.unmask to unmask it + EOF + countdown 5 + rm -f "$ORDER_FILE" + fi + export EMERGE_DEFAULT_OPTS="$OLD_EMERGE_DEFAULT_OPTS" else - EMERGE_DEFAULT_OPTS="" emerge --nospinner --pretend --oneshot --emptytree --quiet $RAW_REBUILD_LIST | sed -n 's/ //g;s/^.*\]//p' | grep "$REBUILD_GREP" >$LLIST.5_order + einfo 'Nothing to rebuild.' + die 1 '(The program should have already quit, so this is a minor bug.)' fi + fi + [[ -r "$ORDER_FILE" && -s "$ORDER_FILE" ]] && einfo "Generated new $ORDER_FILE" +} + +show_unowned_files() { + if grep -qF '(none)' "$OWNERS_FILE"; then + ewarn "Found some broken files that weren't associated with known packages" + ewarn "The broken files are:" + while read filename junk; do + [[ $junk = *none* ]] && ewarn " $filename" + done < "$OWNERS_FILE" | awk '!s[$0]++' # (omit dupes) + fi +} +## +# Setup portage and the search paths +setup_portage() { + local PORTAGE_NICENESS=$(portageq envvar PORTAGE_NICENESS) + PORTAGE_ROOT=$(portageq envvar ROOT) + + # Obey PORTAGE_NICENESS + if [[ $PORTAGE_NICENESS ]]; then + renice $PORTAGE_NICENESS $$ > /dev/null + # Since we have already set our nice value for our processes, + # reset PORTAGE_NICENESS to zero to avoid having emerge renice again. + export PORTAGE_NICENESS="0" + fi + + PORTAGE_ROOT="${PORTAGE_ROOT:-/}" +} + +## +# Setup the paths to search (and filter the ones to avoid) +setup_search_paths_and_masks() { + local configfile sdir mdir skip_me filter_SEARCH_DIRS + + einfo "Configuring search environment for $APP_NAME" + + # Update the incremental variables using /etc/profile.env, /etc/ld.so.conf, + # portage, and the environment + + # Read the incremental variables from environment and portage + # Until such time as portage supports these variables as incrementals + # The value will be what is in /etc/make.conf + SEARCH_DIRS+=" "$(unset SEARCH_DIRS; portageq envvar SEARCH_DIRS) + SEARCH_DIRS_MASK+=" "$(unset SEARCH_DIRS_MASK; portageq envvar SEARCH_DIRS_MASK) + LD_LIBRARY_MASK+=" "$(unset LD_LIBRARY_MASK; portageq envvar LD_LIBRARY_MASK) + + # Add the defaults + if [[ -d /etc/revdep-rebuild ]]; then + for configfile in /etc/revdep-rebuild/*; do + SEARCH_DIRS+=" "$(. $configfile; echo $SEARCH_DIRS) + SEARCH_DIRS_MASK+=" "$(. $configfile; echo $SEARCH_DIRS_MASK) + LD_LIBRARY_MASK+=" "$(. $configfile; echo $LD_LIBRARY_MASK) + done else - echo -n "" >$LLIST.5_order + SEARCH_DIRS+=" /bin /sbin /usr/bin /usr/sbin /lib* /usr/lib*" + SEARCH_DIRS_MASK+=" /opt/OpenOffice /usr/lib/openoffice" + LD_LIBRARY_MASK+=" libodbcinst.so libodbc.so libjava.so libjvm.so" fi - echo -e " done.\n ($LLIST.5_order)" -fi -REBUILD_LIST="$(cat $LLIST.5_order | sed s/^/=/ | tr '\n' ' ')" + # Get the ROOTPATH and PATH from /etc/profile.env + if [[ -r "/etc/profile.env" && -s "/etc/profile.env" ]]; then + SEARCH_DIRS+=" "$(. /etc/profile.env; /usr/bin/tr ':' ' ' <<< "$ROOTPATH $PATH") + fi -trap - SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM + # Get the directories from /etc/ld.so.conf + if [[ -r /etc/ld.so.conf && -s /etc/ld.so.conf ]]; then + SEARCH_DIRS+=" "$(sed '/^#/d;s/#.*$//' /etc/ld.so.conf) + fi -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]_* + # Set the final variables + SEARCH_DIRS=$(clean_var <<< "$SEARCH_DIRS") + SEARCH_DIRS_MASK=$(clean_var <<< "$SEARCH_DIRS_MASK") + LD_LIBRARY_MASK=$(clean_var <<< "$LD_LIBRARY_MASK") + # Filter masked paths from SEARCH_DIRS + for sdir in ${SEARCH_DIRS} ; do + for mdir in ${SEARCH_DIRS_MASK}; do + [[ ${sdir} == ${mdir}/* ]] && skip_me=1 && break + done + [[ -n ${skip_me} ]] || filter_SEARCH_DIRS+=" ${sdir}" + done + SEARCH_DIRS=$(clean_var <<< "${filter_SEARCH_DIRS}") + [[ $SEARCH_DIRS ]] || die 1 "No search defined -- this is a bug." +} +## +# Rebuild packages owning broken binaries +rebuild() { + if [[ -r $LIST.5_order && -s $LIST.5_order ]]; then + REBUILD_LIST=( $(<"$LIST.5_order") ) + REBUILD_LIST="${REBUILD_LIST[@]/#/=}" + else + REBUILD_LIST=$(sort -u "$EBUILDS_FILE") fi - exit 0 -fi -IS_REAL_MERGE=true -echo " $EMERGE_OPTIONS " | grep -q '\( -p \| --pretend \| -f \| --fetchonly \)' && IS_REAL_MERGE=false + trap - SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM -echo -echo -e "${GR}All prepared. Starting rebuild...${NO}" + einfo 'All prepared. Starting rebuild' + echo "emerge --oneshot ${EMERGE_OPTIONS[@]} $REBUILD_LIST" -echo "emerge --oneshot $EMERGE_OPTIONS $REBUILD_LIST" + is_real_merge && countdown 10 -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 $EMERGE_OPTIONS $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} " - 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." + # Link file descriptor #6 with stdin so --ask will work + exec 6<&0 + + # Run in background to correctly handle Ctrl-C + { + EMERGE_DEFAULT_OPTS="--oneshot ${EMERGE_OPTIONS[@]}" emerge $REBUILD_LIST <&6 + echo $? > "$STATUS_FILE" + } & + wait + + # Now restore stdin from fd #6, where it had been saved, and close fd #6 ( 6<&- ) to free it for other processes to use. + exec 0<&6 6<&- +} +## +# Finish up +cleanup() { + if (( $(<"$STATUS_FILE") != 0 )); then + ewarn + ewarn "$APP_NAME failed to emerge all packages." + ewarn 'you have the following choices:' + einfo "- If emerge failed during the build, fix the problems and re-run $APP_NAME." + einfo '- Use /etc/portage/package.keywords to unmask a newer version of the package.' + einfo " (and remove $ORDER_FILE to be evaluated again)" + einfo '- Modify the above emerge command and run it manually.' + einfo '- Compile or unmerge unsatisfied packages manually,' + einfo ' remove temporary files, and try again.' + einfo ' (you can edit package/ebuild list first)' + einfo + einfo 'To remove temporary files, please run:' + einfo "rm ${TMPDIR}/$APP_NAME/*.rr" + show_unowned_files + exit $EMERGE_STATUS + elif is_real_merge; then + trap_cmd() { + eerror "terminated. Please remove the temporary files manually:" + eerror "rm ${TMPDIR}/$APP_NAME/*.rr" + exit 1 + } + [[ "${SKIP_LIST[@]}" != "" ]] && list_skipped_packages + trap trap_cmd SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM + einfo 'Build finished correctly. Removing temporary files...' + einfo + einfo 'You can re-run revdep-rebuild to verify that all libraries and binaries' + einfo 'are fixed. If some inconsistency remains, it can be orphaned file, deep' + einfo 'dependency, binary package or specially evaluated library.' + if [[ -r "$OWNERS_FILE" && -s "$OWNERS_FILE" ]]; then + show_unowned_files + fi + [[ $KEEP_TEMP ]] || rm "${FILES[@]}" else - echo -e "${GR}Now you can remove -p (or --pretend) from arguments and re-run revdep-rebuild.${NO}" + einfo 'Now you can remove -p (or --pretend) from arguments and re-run revdep-rebuild.' fi -fi -exit 0 +} + +main "$@"