592b8839fac4cbe9fb7cd81ab8c2eaff7ad75a14
[gentoolkit.git] / trunk / src / revdep-rebuild / revdep-rebuild
1 #!/bin/bash
2 # Copyright 1999-2007 Gentoo Foundation
3
4 # revdep-rebuild: Reverse dependency rebuilder.
5 # Original Author: Stanislav Brabec
6 # Rewrite Author: Michael A. Smith
7 # Current Maintainer: Paul Varner <fuzzyray@gentoo.org>
8
9 # TODO:
10 # - Use more /etc/init.d/functions.sh
11 # - Try to reduce the number of global vars
12
13 ##
14 # Global Variables:
15
16 # Must-be-blank variables:
17 unset GREP_OPTIONS
18
19 # Readonly variables:
20 declare -r APP_NAME="${0##*/}" # The name of this application
21 declare -r OIFS="$IFS"         # Save the IFS
22
23 # "Boolean" variables: Considered "true" if it has any value at all
24 # "True" indicates we should...
25 declare FULL_LD_PATH           # ...search across the COMPLETE_LD_LIBRARY_PATH
26 declare KEEP_TEMP              # ...not delete tempfiles from the current run
27 declare ORDER_PKGS             # ...sort the atoms in deep dependency order
28 declare PACKAGE_NAMES          # ...emerge by slot, not by versionated atom
29 declare RM_OLD_TEMPFILES       # ...remove tempfiles from prior runs
30 declare SEARCH_BROKEN          # ...search for broken libraries and binaries
31 declare VERBOSE                # ...give verbose output
32
33 # Globals that impact portage directly:
34 declare EMERGE_DEFAULT_OPTS    # String of options portage assumes to be set
35 declare EMERGE_OPTIONS         # Array of options to pass to portage
36 declare PORTAGE_NICENESS       # Renice to this value
37 declare PORTAGE_ROOT           # The root path for portage
38
39 # Customizable incremental variables:
40 # These variables can be prepended to either by setting the variable in 
41 # your environment prior to execution, or by placing an entry in
42 # /etc/make.conf.
43 #
44 # An entry of "-*" means to clear the variable from that point forward.
45 # Example: env SEARCH_DIRS="/usr/bin -*" revdep-rebuild will set SEARCH_DIRS
46 # to contain only /usr/bin
47 declare LD_LIBRARY_MASK  # Mask of specially evaluated libraries
48 declare SEARCH_DIRS      # List of dirs to search for executables and libraries
49 declare SEARCH_DIRS_MASK # List of dirs not to search
50
51 # Other globals:
52 declare OLDPROG                # Previous pass through the progress meter
53 declare EXACT_PKG              # Versionated atom to emerge
54 declare HEAD_TEXT              # Feedback string about the search
55 declare LIST                   # Prefix for temporary filenames
56 declare NOCOLOR                # Set to "true" not to output term colors
57 declare OK_TEXT                # Feedback about a search which found no errors
58 declare RC_NOCOLOR             # Hack to insure we respect NOCOLOR
59 declare REBUILD_LIST           # Array of atoms to emerge
60 declare SKIP_LIST              # Array of atoms that cannot be emerged (masked?)
61 declare SONAME                 # Soname/soname path pattern given on commandline
62 declare SONAME_SEARCH          # Value of SONAME modified to match ldd's output
63 declare WORKING_TEXT           # Feedback about the search
64 declare ENV_FILE               # A file containing environment variables
65
66 rm() {
67         local i
68         [[ $LIST && $APP_NAME ]] ||
69                 die 1 '$LIST or $APP_NAME is not defined! (This is a bug.)'
70         for i; do
71                 [[ $i = -* || $i = *.$APP_NAME* ]] || 
72                         die 1 "Oops, I'm not allowed to delete that. ($@)"
73         done
74         command rm "$@"
75 }
76 # Somewhat more portable find -executable
77 # FIXME/UNTESTED (I don't have access to all of the different versions of 
78 # find.)
79 # Usage: find PATH ARGS -- use find like normal, except use -executable instead 
80 # of various versions of -perm /+ blah blah and hacks
81 find() {
82         hash find || { die 1 'find not found!'; }
83         # We can be pretty sure find itself should be executable.
84         local testsubject="$(type -P find)"
85         if [[ $(command find "$testsubject" -executable 2> /dev/null) ]]; then
86                 unset -f find # We can just use the command find
87         elif [[ $(command find "$testsubject" -perm /u+x 2> /dev/null) ]]; then
88                 find() {
89                         a=(${@//-executable/-perm \/u+x})
90                         a=(${a[@]//-writable/-perm \/u+w})
91                         a=(${a[@]//-readable/-perm \/r+w})
92                         command find "${a[@]}"
93                 }
94         elif [[ $(command find "$testsubject" -perm +u+x 2> /dev/null) ]]; then
95                 find() {
96                         a=(${@//-executable/-perm +u+x})
97                         a=(${a[@]//-writable/-perm +u+w})
98                         a=(${a[@]//-readable/-perm +r+w})
99                         command find "${a[@]}"
100                 }
101         else # Last resort
102                 find() {
103                         a=(${@//-executable/-exec test -x '{}' \;})
104                         a=(${a[@]//-writable/-exec test -w '{}' \;})
105                         a=(${a[@]//-readable/-exec test -r '{}' \;})
106                         command find "${a[@]}"
107                 }
108         fi
109         find "$@"
110 }
111 print_usage() {
112 cat << EOF
113 Usage: $APP_NAME [OPTIONS] [--] [EMERGE_OPTIONS]
114
115 Broken reverse dependency rebuilder.
116
117   -C, --nocolor        Turn off colored output
118   -d, --debug          Print way too much information (uses bash's set -xv)
119   -e, --exact          Emerge based on exact package version
120   -h, --help           Print this usage
121   -i, --ignore         Ignore temporary files from previous runs
122   -k, --keep-temp      Do not delete temporary files on exit
123   -L, --library NAME   Emerge existing packages that use the library with NAME
124       --library=NAME   NAME can be a full path to the library or a basic
125                        regular expression (man grep)
126   -l, --no-ld-path     Do not set LD_LIBRARY_PATH
127   -o, --no-order       Do not check the build order
128                        (Saves time, but may cause breakage.)
129   -p, --pretend        Do a trial run without actually emerging anything
130                        (also passed to emerge command)
131   -P, --no-progress    Turn off the progress meter
132   -q, --quiet          Be less verbose (also passed to emerge command)
133   -v, --verbose        Be more verbose (also passed to emerge command)
134   -u, --no-util UTIL   Do not use features provided by UTIL
135       --no-util=UTIL   UTIL can be one of portage-utils or pkgcore
136                        or it can be a *quoted* space-delimited list.
137
138 Calls emerge, options after -- are ignored by $APP_NAME
139 and passed directly to emerge.
140
141 Report bugs to <http://bugs.gentoo.org>
142 EOF
143 }
144 # Usage: progress i n
145 #        i: current item
146 #        n: total number of items to process
147 progress() {
148         if [[ -t 1 ]]; then
149                 progress() {
150                         local curProg=$(( $1 * 100 / $2 ))
151                         (( curProg == OLDPROG )) && return # no change, output nothing
152                         OLDPROG="$curProg" # must be a global variable
153                         (( $1 == $2 )) && local lb=$'\n'
154                         echo -ne '\r                         \r'"[ $curProg% ] $lb"
155                 }
156                 progress $@
157         else # STDOUT is not a tty. Disable progress meter.
158                 progress() { :; }
159         fi
160 }
161 # Usage: countdown n
162 #        n: number of seconds to count
163 countdown() {
164         local i
165         for ((i=1; i<$1; i++)); do
166                 echo -ne '\a.'
167                 ((i<$1)) && sleep 1
168         done
169         echo -e '\a.'
170 }
171 # Get the name of a package owning a file on the filesystem using one of several
172 # utilities: This is a placeholder. The function is defined in get_opts()
173 get_file_owner() { :; }
174 # Replace whitespace with linebreaks, normalize repeated '/' chars, and sort -u
175 # (If any libs have whitespace in their filenames, someone needs punishment.)
176 clean_var() {
177         awk 'BEGIN         {RS="[[:space:]]"}
178              /-\*/         {exit}
179              /[^[:space:]]/ {gsub(/\/\/+/, "/"); print}' | sort -u
180 }
181 # Exit and optionally output to sterr
182 die() {
183         local status=$1
184         shift
185         eerror "$@"
186         exit $status
187 }
188 # What to do when dynamic linking is consistent
189 clean_exit() {
190         [[ $KEEP_TEMP ]] || rm $LIST.?_*
191         echo
192         einfo "$OK_TEXT... All done. "
193         exit 0
194 }
195
196 ##
197 # Check if various portage utils are allowed and installed
198 setup_get_file_owner() {
199         # portage-utils disabled until it is able to handle category names without
200         # a hyphen.  See Bug #210386
201         # if [[ $avoid_utils != *portage-utils* ]] && hash qfile 2> /dev/null; then
202         #       get_file_owner() { qfile -qvC "$@"; }
203         if [[ $avoid_utils != *pkgcore* ]] && hash pquery 2> /dev/null; then
204                 get_file_owner() { local IFS=,; pquery --nocolor --owns="$*"; }
205         # equery disabled for incompatibility with modern portage.
206         # elif [[ $avoid_utils != *equery* ]] && hash equery 2> /dev/null; then
207         #       get_file_owner() { equery -q -C b $@; }
208         else
209                 get_file_owner() {
210                         local IFS=$'\n'
211                         # ${*/%/ } adds a space to the end of each object name to prevent false
212                         # matches, for example /usr/bin/dia matching /usr/bin/dialog (bug #196460).
213                         find -L /var/db/pkg -name CONTENTS -print0 |
214                                 xargs -0 grep -Fl "${*/%/ }" |
215                                 sed 's:/var/db/pkg/\(.*\)/CONTENTS:\1:'
216                 }
217         fi
218 }
219
220 ##
221 # Normalize some EMERGE_OPTIONS
222 normalize_emerge_opts() {
223         # Normalize some EMERGE_OPTIONS
224         EMERGE_OPTIONS=(${EMERGE_OPTIONS[@]/%-p/--pretend})
225         EMERGE_OPTIONS=(${EMERGE_OPTIONS[@]/%-f/--fetchonly})
226         EMERGE_OPTIONS=(${EMERGE_OPTIONS[@]/%-f/--verbose})
227 }
228
229 ##
230 # Use the color preference from portage
231 setup_color() {
232         # This should still work if NOCOLOR is set by the -C flag or in the user's
233         # environment.
234         export NOCOLOR=$(portageq envvar NOCOLOR)
235         [[ $NOCOLOR = yes || $NOCOLOR = true ]] && export RC_NOCOLOR=yes # HACK! (grr)
236         . /etc/init.d/functions.sh
237 }
238
239 ##
240 # Die if an argument is missing.
241 die_if_missing_arg() {
242         [[ ! $2 || $2 = -* ]] && die 1 "Missing expected argument to $1"
243 }
244
245 ##
246 # Die because an option is not recognized.
247 die_invalid_option() {
248         # We cannot use eerror and einfo because this gets called before function.sh 
249         # is sourced
250         print_usage
251         echo
252         echo  "Encountered unrecognized option $1." >&2
253         echo
254         echo  "$APP_NAME no longer automatically passes unrecognized options to portage."
255         echo  "Separate emerge-only options from revdep-rebuild options with the -- flag."
256         echo
257         exit 1
258 }
259
260 ##
261 # Warn about deprecated options.
262 warn_deprecated_opt() {
263         # We cannot use eerror and einfo because this gets called before function.sh 
264         # is sourced
265         echo
266         echo "Encountered deprecated option $1." >&2
267         [[ $2 ]] && echo "Please use $2 instead." >&2
268 }
269
270 ##
271 # Get whole-word commandline options preceded by two dashes.
272 get_longopts() {
273         case $1 in
274                                                --nocolor) export NOCOLOR="yes";;
275                                               --no-color) warn_deprecated_opt "$1" "--nocolor"
276                                                           export NOCOLOR="yes";;
277                                                  --debug) set -xv;;
278                                                  --exact) unset PACKAGE_NAMES;;
279                                                   --help) print_usage
280                                                           exit 0;;
281                                                 --ignore) RM_OLD_TEMPFILES=1;;
282                                              --keep-temp) KEEP_TEMP=1;;
283                                              --library=*) # TODO: check for invalid values
284                                                           SONAME="${1#*=}"
285                                                           unset SEARCH_BROKEN;;
286                             --soname=*|--soname-regexp=*) # TODO: check for invalid values
287                                                           warn_deprecated_opt "${1%=*}" "--library"
288                                                           SONAME="${1#*=}"
289                                                           unset SEARCH_BROKEN;;
290                                                --library) # TODO: check for invalid values
291                                                           die_if_missing_arg $1 $2
292                                                           shift
293                                                           SONAME="$1"
294                                                           unset SEARCH_BROKEN;;
295                                 --soname|--soname-regexp) # TODO: check for invalid values
296                                                           warn_deprecated_opt "$1" "--library"
297                                                           die_if_missing_arg $1 $2
298                                                           shift
299                                                           SONAME="$1"
300                                                           unset SEARCH_BROKEN;;
301                                             --no-ld-path) unset FULL_LD_PATH;;
302                                               --no-order) unset ORDER_PKGS;;
303                                            --no-progress) progress() { :; };;
304                                                --pretend) EMERGE_OPTIONS+=("--pretend");;
305                                                  --quiet) echo_v() { :; }
306                                                           progress() { :; }
307                                                           quiet=1
308                                                           EMERGE_OPTIONS+=($1);;
309                                              --no-util=*) # TODO: check for invalid values
310                                                           avoid_utils="${1#*=}";;
311                                                --no-util) # TODO: check for invalid values
312                                                           die_if_missing_arg $1 $2
313                                                           shift
314                                                           avoid_utils="$1";;
315                                                --verbose) VERBOSE=1
316                                                           EMERGE_OPTIONS+=("--verbose");;
317                                          --extra-verbose) warn_deprecated_opt "$1" "--verbose"
318                                                           VERBOSE=1
319                                                           EMERGE_OPTIONS+=("--verbose");;
320                                          --package-names) # No longer used, since it is the
321                                                           # default. We accept it for
322                                                           # backwards compatibility.
323                                                           warn_deprecated_opt "$1"
324                                                           PACKAGE_NAMES=1;;
325                                                        *) die_invalid_option $1;;
326         esac
327 }
328
329 ##
330 # Get single-letter commandline options preceded by a single dash.
331 get_shortopts() {
332         local OPT OPTSTRING OPTARG OPTIND
333         while getopts ":CdehikL:loPpqu:vX" OPT; do
334                 case "$OPT" in
335                         C) # TODO: Match syntax with the rest of gentoolkit
336                            export NOCOLOR="yes";;
337                         d) set -xv;;
338                         e) unset PACKAGE_NAMES;;
339                         h) print_usage
340                            exit 0;;
341                         i) RM_OLD_TEMPFILES=1;;
342                         k) KEEP_TEMP=1;;
343                         L) # TODO: Check for invalid values
344                            SONAME="${OPTARG#*=}"
345                            unset SEARCH_BROKEN;;
346                         l) unset FULL_LD_PATH;;
347                         o) unset ORDER_PKGS;;
348                         P) progress() { :; };;
349                         p) EMERGE_OPTIONS+=("--pretend");;
350                         q) echo_v() { :; }
351                            progress() { :; }
352                            quiet=1
353                            EMERGE_OPTIONS+=("--quiet");;
354                         u) avoid_utils="$1";; # TODO: Check for invalid values
355                         v) VERBOSE=1
356                            EMERGE_OPTIONS+=("--verbose");;
357                         X) # No longer used, since it is the default.
358                            # We accept it for backwards compatibility.
359                            warn_deprecated_opt "-X"
360                            PACKAGE_NAMES=1;;
361                         *) die_invalid_option "-$OPTARG";;
362                 esac
363         done
364 }
365
366 ##
367 # Get command-line options.
368 get_opts() {
369         local avoid_utils
370         local -a args
371         echo_v() { ewarn "$@"; }
372         unset VERBOSE KEEP_TEMP EMERGE_OPTIONS RM_OLD_TEMPFILES
373         ORDER_PKGS=1
374         PACKAGE_NAMES=1
375         SONAME="not found"
376         SEARCH_BROKEN=1
377         FULL_LD_PATH=1
378         while [[ $1 ]]; do
379                 case $1 in
380                         --) shift
381                             EMERGE_OPTIONS+=("$@")
382                             break;;
383                         -*) while true; do
384                               args+=("$1")
385                               shift
386                               [[ ${1:--} = -* ]] && break
387                             done
388                             if [[ ${args[0]} = --* ]]; then
389                               get_longopts  "${args[@]}"
390                             else
391                               get_shortopts "${args[@]}"
392                             fi;;
393                          *) die_invalid_option "$1";;
394                 esac
395                 unset args
396         done
397
398         setup_get_file_owner
399         setup_color
400         normalize_emerge_opts
401
402         # If the user is not super, add --pretend to EMERGE_OPTIONS
403         if [[ ${EMERGE_OPTIONS[@]} != *--pretend* && $UID -ne 0 ]]; then
404                 ewarn "You are not superuser. Adding --pretend to emerge options."
405                 EMERGE_OPTIONS+=(--pretend)
406         fi
407 }
408
409 is_real_merge() {
410         [[ ${EMERGE_OPTIONS[@]} != *--pretend* &&
411            ${EMERGE_OPTIONS[@]} != *--fetchonly* ]]
412 }
413
414 get_opts "$@"
415
416 einfo "Configuring search environment for $APP_NAME"
417
418 # Obey PORTAGE_NICENESS
419 PORTAGE_NICENESS=$(portageq envvar PORTAGE_NICENESS)
420 if [[ $PORTAGE_NICENESS ]]; then
421         renice $PORTAGE_NICENESS $$ > /dev/null
422         # Since we have already set our nice value for our processes,
423         # reset PORTAGE_NICENESS to zero to avoid having emerge renice again.
424         export PORTAGE_NICENESS="0"
425 fi
426
427 PORTAGE_ROOT=$(portageq envvar ROOT)
428 PORTAGE_ROOT="${PORTAGE_ROOT:-/}"
429
430 # Update the incremental variables using /etc/profile.env, /etc/ld.so.conf,
431 # portage, and the environment
432
433 # Read the incremental variables from environment and portage
434 # Until such time as portage supports these variables as incrementals
435 # The value will be what is in /etc/make.conf
436 SEARCH_DIRS+=" "$(unset SEARCH_DIRS; portageq envvar SEARCH_DIRS)
437 SEARCH_DIRS_MASK+=" "$(unset SEARCH_DIRS_MASK; portageq envvar SEARCH_DIRS_MASK)
438 LD_LIBRARY_MASK+=" "$(unset LD_LIBRARY_MASK; portageq envvar LD_LIBRARY_MASK)
439
440 # Add the defaults
441 if [[ -d /etc/revdep-rebuild ]]; then
442         for ENV_FILE in /etc/revdep-rebuild/*; do
443                 SEARCH_DIRS+=" "$(. $ENV_FILE; echo $SEARCH_DIRS)
444                 SEARCH_DIRS_MASK+=" "$(. $ENV_FILE; echo $SEARCH_DIRS_MASK)
445                 LD_LIBRARY_MASK+=" "$(. $ENV_FILE; echo $LD_LIBRARY_MASK)
446         done
447         unset ENV_FILE # HACK
448 else
449         SEARCH_DIRS+=" /bin /sbin /usr/bin /usr/sbin /lib* /usr/lib*"
450         SEARCH_DIRS_MASK+=" /opt/OpenOffice /usr/lib/openoffice"
451         LD_LIBRARY_MASK+=" libodbcinst.so libodbc.so libjava.so libjvm.so"
452 fi
453
454 # Get the ROOTPATH and PATH from /etc/profile.env
455 if [[ -r "/etc/profile.env" && -s "/etc/profile.env" ]]; then
456         SEARCH_DIRS+=" "$(. /etc/profile.env; /usr/bin/tr ':' ' ' <<< "$ROOTPATH $PATH")
457 fi
458
459 # Get the directories from /etc/ld.so.conf
460 if [[ -r /etc/ld.so.conf && -s /etc/ld.so.conf ]]; then
461         SEARCH_DIRS+=" "$(sed '/^#/d;s/#.*$//' /etc/ld.so.conf)
462 fi
463
464 # Set the final variables
465 SEARCH_DIRS=$(clean_var <<< "$SEARCH_DIRS")
466 SEARCH_DIRS_MASK=$(clean_var <<< "$SEARCH_DIRS_MASK")
467 LD_LIBRARY_MASK=$(clean_var <<< "$LD_LIBRARY_MASK")
468 # Filter masked paths from SEARCH_DIRS
469 filter_SEARCH_DIRS=
470 for sdir in ${SEARCH_DIRS} ; do
471         unset skip_me
472         for mdir in ${SEARCH_DIRS_MASK} ; do
473                 [[ ${sdir} == ${mdir}/* ]] \
474                         && skip_me=1 && break
475         done
476         [[ -n ${skip_me} ]] || filter_SEARCH_DIRS+=" ${sdir}"
477 done
478 SEARCH_DIRS=$(clean_var <<< "${filter_SEARCH_DIRS}")
479 unset sdir mdir skip_me filter_SEARCH_DIRS
480 [[ $SEARCH_DIRS ]] || die 1 "No search defined -- this is a bug."
481
482 set_trap() {
483         trap "rm_temp $1" SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM
484 }
485 rm_temp() {
486         rm $1
487         die 1 $'  ...terminated. Removing incomplete '"$1."
488 }
489 get_search_env() {
490         local new_env
491         local old_env
492         # Find a place to put temporary files
493         # TODO; let the user choose where to put tempfiles
494         # gfind $HOME/ /var/tmp/ /tmp/ -writable -print -quit
495         # HACK: This is a rather noisy, but portable way to implement -quit
496         while read LIST; do
497                 break # Set LIST
498         done < <(find $HOME/ /var/tmp/ /tmp/ -writable -print)
499         [[ $LIST ]] ||
500                 die 1 "Unable to find a satisfactory location for temporary files"
501
502         LIST+=".$APP_NAME"
503         if [[ $SEARCH_BROKEN ]]; then
504                 SONAME_SEARCH="$SONAME"
505                 HEAD_TEXT="broken by a package update"
506                 OK_TEXT="Dynamic linking on your system is consistent"
507                 WORKING_TEXT="consistency"
508         else
509                 # first case is needed to test against /path/to/foo.so
510                 if [[ $SONAME = /* ]]; then
511                         # Set to "<space>$SONAME<space>"
512                         SONAME_SEARCH=" $SONAME "
513                         # Escape the "/" characters
514                         SONAME_SEARCH="${SONAME_SEARCH//\//\\/}"
515                 else
516                         # Set to "<tab>$SONAME<space>"
517                         SONAME_SEARCH=$'\t'"$SONAME "
518                 fi
519                 local uuid="${SONAME##*/}"
520                 uuid="${uuid//[[:space:]]}"
521                 LIST+="_$uuid"
522                 HEAD_TEXT="using $SONAME"
523                 OK_TEXT="There are no dynamic links to $SONAME"
524                 unset WORKING_TEXT
525         fi
526
527         # If any of our temporary files are older than 1 day, remove them all
528         if [[ ! $KEEP_TEMP ]]; then
529                 while read; do
530                         RM_OLD_TEMPFILES=1
531                         break
532                 done < <(find -L "$LIST."* -maxdepth 0 -type f -mmin +1440 -print 2>/dev/null)
533         fi
534
535         # Compare old and new environments
536         # Don't use our previous files if environment doesn't match
537         new_env=$(
538                 # We do not care if these emerge options change
539                 EMERGE_OPTIONS=(${EMERGE_OPTIONS[@]//--pretend/})
540                 EMERGE_OPTIONS=(${EMERGE_OPTIONS[@]//--fetchonly/})
541                 EMERGE_OPTIONS=(${EMERGE_OPTIONS[@]//--verbose/})
542                 cat <<- EOF
543                         SEARCH_DIRS="$SEARCH_DIRS"
544                         SEARCH_DIRS_MASK="$SEARCH_DIRS_MASK"
545                         LD_LIBRARY_MASK="$LD_LIBRARY_MASK"
546                         PORTAGE_ROOT="$PORTAGE_ROOT"
547                         EMERGE_OPTIONS="${EMERGE_OPTIONS[@]}"
548                         ORDER_PKGS="$ORDER_PKGS"
549                         FULL_LD_PATH="$FULL_LD_PATH"
550                 EOF
551         )
552         if [[ -r $LIST.0_env && -s $LIST.0_env ]]; then
553                 old_env=$(<"$LIST.0_env")
554                 if [[ $old_env != $new_env ]]; then
555                         ewarn 'Environment mismatch from previous run, deleting temporary files...'
556                         RM_OLD_TEMPFILES=1
557                 fi
558         else
559                 # No 0_env file found, silently delete any other tempfiles that may exist
560                 RM_OLD_TEMPFILES=1
561         fi
562
563         # If we should remove old tempfiles, do so
564         [[ $RM_OLD_TEMPFILES ]] && rm -f "$LIST."*
565         
566         # Save the environment in a file for next time
567         echo "$new_env" > "$LIST.0_env"
568
569         [[ $VERBOSE ]] && echo $'\n'"$APP_NAME environment:"$'\n'"$new_env"
570
571         echo
572         einfo "Checking reverse dependencies"
573         einfo "Packages containing binaries and libraries $HEAD_TEXT"
574         einfo "will be emerged."
575 }
576 get_files() {
577         einfo "Collecting system binaries and libraries"
578         if [[ -r $LIST.1_files && -s $LIST.1_files ]]; then
579                 einfo "Found existing $LIST.1_files"
580         else
581                 # Be safe and remove any extraneous temporary files
582                 rm -f $LIST.[1-9]_*
583
584                 set_trap "$LIST.1_*"
585
586                 if [[ $SEARCH_DIRS_MASK ]]; then
587                         findMask=($SEARCH_DIRS_MASK)
588                         findMask="${findMask[@]/#/-o -path }"
589                         findMask="( ${findMask#-o } ) -prune -o"
590                 fi
591                 find ${SEARCH_DIRS[@]} $findMask -type f \( -executable -o \
592                         -name '*.so' -o -name '*.so.*' -o -name '*.la' \) -print 2> /dev/null |
593                         sort -u > "$LIST.1_files" ||
594                         die $? "find failed to list binary files (This is a bug.)"
595                 einfo "Generated new $LIST.1_files"
596         fi
597 }
598 get_ldpath() {
599         local COMPLETE_LD_LIBRARY_PATH
600         [[ $SEARCH_BROKEN && $FULL_LD_PATH ]] || return
601         einfo 'Collecting complete LD_LIBRARY_PATH'
602         if [[ -r $LIST.2_ldpath && -s $LIST.2_ldpath ]] ; then
603                 einfo "Found existing $LIST.2_ldpath."
604         else
605                 set_trap "$LIST.2_ldpath"
606                 # Ensure that the "trusted" lib directories are at the start of the path
607                 COMPLETE_LD_LIBRARY_PATH=(
608                         /lib*
609                         /usr/lib*
610                         $(sed '/^#/d;s/#.*$//' < /etc/ld.so.conf)
611                         $(sed 's:/[^/]*$::' < "$LIST.1_files" | sort -ru)
612                 )
613                 IFS=':'
614                 COMPLETE_LD_LIBRARY_PATH="${COMPLETE_LD_LIBRARY_PATH[*]}"
615                 IFS="$OIFS"
616                 echo "$COMPLETE_LD_LIBRARY_PATH" > "$LIST.2_ldpath"
617                 einfo "Generated new $LIST.2_ldpath"
618         fi
619 }
620 main_checks() {
621         local target_file
622         local -a files
623         local i=0
624         local ldd_output
625         local ldd_status
626         local numFiles
627         local COMPLETE_LD_LIBRARY_PATH
628         if [[ $SEARCH_BROKEN && $FULL_LD_PATH ]]; then
629                 [[ -r $LIST.2_ldpath && -s $LIST.2_ldpath ]] || die 1 "unable to find $LIST.2_ldpath"
630                 COMPLETE_LD_LIBRARY_PATH=$(<"$LIST.2_ldpath")
631         fi
632         einfo "Checking dynamic linking $WORKING_TEXT"
633         if [[ -r $LIST.3_rebuild && -s $LIST.3_rebuild ]]; then
634                 einfo "Found existing $LIST.3_rebuild."
635         else
636                 [[ $LIST ]] || die 1 "$LIST" 'is undefined! (This is a bug.)'
637                 set_trap "$LIST.3_rebuild"
638                 set_trap "$LIST.3_ldd_errors"
639                 rm -f "$LIST.3"*
640                 files=($(<"$LIST.1_files"))
641                 numFiles="${#files[@]}"
642                 for target_file in "${files[@]}"; do
643                         if [[ $target_file != *.la ]]; then
644                                 # Note: double checking seems to be faster than single with complete path
645                                 # (special add ons are rare).
646                                 ldd_output=$(ldd "$target_file" 2>> "$LIST.3_ldd_errors" | sort -u)
647                                 ldd_status=$? # TODO: Check this for problems with sort
648                                 # HACK: if LD_LIBRARY_MASK is null or undefined grep -vF doesn't work
649                                 if grep -vF "${LD_LIBRARY_MASK:=$'\a'}" <<< "$ldd_output" |
650                                         grep -q "$SONAME_SEARCH"; then
651                                         if [[ $SEARCH_BROKEN && $FULL_LD_PATH ]]; then
652                                                 if LD_LIBRARY_PATH="$COMPLETE_LD_LIBRARY_PATH" ldd "$target_file" 2>/dev/null |
653                                                         grep -vF "$LD_LIBRARY_MASK" | grep -q "$SONAME_SEARCH"; then
654                                                         # FIXME: I hate duplicating code
655                                                         # Only build missing direct dependencies
656                                                         MISSING_LIBS=$(
657                                                                 expr='s/[[:space:]]*\([^[:space:]]*\) => not found/\1/p'
658                                                                 sed -n "$expr" <<< "$ldd_output"
659                                                         )
660                                                         REQUIRED_LIBS=$(
661                                                                 expr='s/^[[:space:]]*NEEDED[[:space:]]*\([^[:space:]]*\).*/\1/p';
662                                                                 objdump -x "$target_file" | grep NEEDED | sed "$expr" | sort -u
663                                                         )
664                                                         MISSING_LIBS=$(grep -F "$REQUIRED_LIBS" <<< "$MISSING_LIBS")
665                                                         if [[ $MISSING_LIBS ]]; then
666                                                                 echo "obj $target_file" >> "$LIST.3_rebuild"
667                                                                 echo_v "  broken $target_file (requires $MISSING_LIBS)"
668                                                         fi
669                                                 fi
670                                         else
671                                                 # FIXME: I hate duplicating code
672                                                 # Only rebuild for direct dependencies
673                                                 MISSING_LIBS=$(
674                                                         expr="/$SONAME_SEARCH/s/^[[:space:]]*\([^[:space:]]*\).*$/\1/p"
675                                                         sort -u <<< "$ldd_output" | sed -n "$expr"
676                                                 )
677                                                 REQUIRED_LIBS=$(
678                                                         expr='s/^[[:space:]]*NEEDED[[:space:]]*\([^[:space:]]*\).*/\1/p';
679                                                         objdump -x "$target_file" | grep NEEDED | sed "$expr" | sort -u
680                                                 )
681                                                 MISSING_LIBS=$(grep -F "$REQUIRED_LIBS" <<< "$MISSING_LIBS")
682                                                 if [[ $MISSING_LIBS ]]; then
683                                                         echo "obj $target_file" >> "$LIST.3_rebuild"
684                                                         if [[ $SEARCH_BROKEN ]]; then
685                                                                 echo_v "  broken $target_file (requires $MISSING_LIBS)"
686                                                         else
687                                                                 echo_v "  found $target_file"
688                                                         fi
689                                                 fi
690                                         fi
691                                 fi
692                         elif [[ $SEARCH_BROKEN ]]; then
693                                 # Look for broken .la files
694                                 for depend in $(
695                                         awk -F"[=']" '/^dependency_libs/{
696                                                 gsub("^-[^[:space:]]*", "", $3);
697                                                 gsub("[[:space:]]-[^[:space:]]*", "", $3);
698                                                 print $3
699                                         }' "$target_file"
700                                 ); do
701                                         if [[ $depend = /* && ! -e $depend ]]; then
702                                                 echo "obj $target_file" >> "$LIST.3_rebuild"
703                                                 echo_v "  broken $target_file (requires $depend)"
704                                         fi
705                                 done
706                         fi
707                         [[ $VERBOSE ]] &&
708                                 progress $((++i)) $numFiles $target_file ||
709                                 progress $((++i)) $numFiles
710                 done
711                 if [[ $SEARCH_BROKEN ]]; then
712                         # Look for missing version
713                         while read target_file; do
714                                 echo "obj $target_file" >> "$LIST.3_rebuild"
715                                 echo_v "  broken $target_file (no version information available)"
716                         done < <(
717                                 # Regexify LD_LIBRARY_MASK. Exclude it from the search.
718                                 LD_LIBRARY_MASK="${LD_LIBRARY_MASK//$'\n'/|}"
719                                 awk -v ldmask="(${LD_LIBRARY_MASK//./\\\.})" '
720                                         /no version information available/ && $0 !~ ldmask {
721                                                 gsub(/[()]/, "", $NF)
722                                                 if (seen[$NF]++)  next
723                                                 print $NF
724                                         }' "$LIST.3_ldd_errors"
725                         )
726                 fi
727                 [[ -r $LIST.3_rebuild && -s $LIST.3_rebuild ]] || clean_exit
728                 einfo "Generated new $LIST.3_rebuild"
729         fi
730 }
731 get_packages() {
732         local target_file
733         local EXACT_PKG
734         local PKG
735         local obj
736         einfo 'Assigning files to packages'
737         if [[ -r $LIST.4_packages_raw && -s $LIST.4_packages_raw ]]; then
738                 einfo "Found existing $LIST.4_packages_raw"
739         else
740                 set_trap "$LIST.4_packages*"
741                 rm -f $LIST.4*
742                 while read obj target_file; do
743                         EXACT_PKG=$(get_file_owner $target_file)
744                         if [[ $EXACT_PKG ]]; then
745                                 # Strip version information
746                                 PKG="${EXACT_PKG%%-r[[:digit:]]*}"
747                                 PKG="${PKG%-*}"
748                                 echo "$EXACT_PKG" >> $LIST.4_packages_raw
749                                 echo "$target_file -> $EXACT_PKG" >> $LIST.4_package_owners
750                                 echo_v "  $target_file -> $PKG"
751                         else
752                                 ewarn " !!! $target_file not owned by any package is broken !!!"
753                                 echo "$target_file -> (none)" >> $LIST.4_package_owners
754                                 echo_v "  $target_file -> (none)"
755                         fi
756                 done < "$LIST.3_rebuild"
757                 einfo "Generated new $LIST.4_packages_raw and $LIST.4_package_owners"
758         fi
759         # if we find '(none)' on every line, exit out
760         if ! grep -qvF '(none)' "$LIST.4_package_owners"; then
761                 ewarn "Found some broken files, but none of them were associated with known packages"
762                 ewarn "Unable to proceed with automatic repairs."
763                 ewarn "The broken files are listed in $LIST.4_package_owners"
764                 if [[ $VERBOSE ]]; then
765                         ewarn "The broken files are:"
766                         while read filename junk; do
767                                 ewarn "  $filename"
768                         done < "$LIST.4_package_owners"
769                 fi
770                 exit 0 # FIXME: Should we exit 1 here?
771         fi
772 }
773 clean_packages() {
774         einfo 'Cleaning list of packages to rebuild'
775         if [[ -r $LIST.4_packages && -s $LIST.4_packages ]]; then
776                 einfo "Found existing $LIST.4_packages"
777         else
778                 sort -u $LIST.4_packages_raw > $LIST.4_packages
779                 einfo "Generated new $LIST.4_packages"
780         fi
781 }
782 assign_packages_to_ebuilds() {
783         local EXACT_PKG
784         local PKG
785         local SLOT
786         einfo 'Assigning packages to ebuilds'
787         if [[ -r $LIST.4_ebuilds && -s $LIST.4_ebuilds ]]; then
788                 einfo "Found existing $LIST.4_ebuilds"
789         elif [[ -r $LIST.4_packages && -s $LIST.4_packages ]]; then
790                         set_trap "$LIST.4_ebuilds"
791                         while read EXACT_PKG; do
792                                 # Get the slot
793                                 PKG="${EXACT_PKG%%-r[[:digit:]]*}"
794                                 PKG="${PKG%-*}"
795                                 SLOT=$(</var/db/pkg/$EXACT_PKG/SLOT)
796                                 echo "$PKG:$SLOT"
797                         done < "$LIST.4_packages" > "$LIST.4_ebuilds"
798                         einfo "Generated new $LIST.4_ebuilds"
799         else
800                 einfo 'Nothing to rebuild.'
801                 die 1 '(The program should have already quit, so this is a minor bug.)'
802         fi
803 }
804 get_exact_ebuilds() {
805         einfo 'Assigning files to ebuilds'
806         if [[ -r $LIST.4_ebuilds && -s $LIST.4_ebuilds ]]; then
807                 einfo "Found existing $LIST.4_ebuilds"
808         elif [[ -r $LIST.3_rebuild && -s $LIST.3_rebuild ]]; then
809                 rebuildList=" $(<"$LIST.3_rebuild") "
810                 rebuildList=(${rebuildList//[[:space:]]obj[[:space:]]/ })
811                 get_file_owner "${rebuildList[@]}" | sed 's/^/=/' > "$LIST.4_ebuilds"
812                 einfo "Generated new $LIST.4_ebuilds"
813         else
814                 einfo 'Nothing to rebuild.'
815                 die 1 '(The program should have already quit, so this is a minor bug.)'
816         fi
817 }
818 list_skipped_packages() {
819         ewarn
820         ewarn 'Portage could not find any version of the following packages it could build:'
821         ewarn "${SKIP_LIST[@]}"
822         ewarn
823         ewarn '(Perhaps they are masked, blocked, or removed from portage.)'
824         ewarn 'Try to emerge them manually.'
825         ewarn
826 }
827 get_build_order() {
828         local -r OLD_EMERGE_DEFAULT_OPTS="$EMERGE_DEFAULT_OPTS"
829         local RAW_REBUILD_LIST
830         local REBUILD_GREP
831         local i
832         if [[ ! $ORDER_PKGS ]]; then
833                 einfo 'Skipping package ordering'
834                 return
835         fi
836         einfo 'Evaluating package order'
837         if [[ -r $LIST.5_order && -s $LIST.5_order ]]; then
838                 einfo "Found existing $LIST.5_order"
839         else
840                 set_trap "$LIST.5_order"
841                 RAW_REBUILD_LIST=$(<"$LIST.4_ebuilds")
842                 if [[ $RAW_REBUILD_LIST ]]; then
843                         export EMERGE_DEFAULT_OPTS="--nospinner --pretend --oneshot --quiet"
844                         RAW_REBUILD_LIST=($RAW_REBUILD_LIST) # convert into array
845                         # If PACKAGE_NAMES is defined we're using slots, not versions
846                         if [[ $PACKAGE_NAMES ]]; then
847                                 # Eliminate atoms that can't be built
848                                 for i in "${!RAW_REBUILD_LIST[@]}"; do
849                                         if [[ "${RAW_REBUILD_LIST[i]}" = *[A-Za-z]* ]]; then
850                                                 portageq best_visible "$PORTAGE_ROOT" "${RAW_REBUILD_LIST[i]}" >/dev/null && continue
851                                                 SKIP_LIST+=("${RAW_REBUILD_LIST[i]}")
852                                         fi
853                                         unset RAW_REBUILD_LIST[i]
854                                 done
855                                 # If RAW_REBUILD_LIST is empty, then we have nothing to build.
856                                 if (( ${#RAW_REBUILD_LIST[@]} == 0 )); then
857                                         if (( ${#SKIP_LIST[@]} == 0 )); then
858                                                 ewarn "The list of packages to skip is empty, but there are no"
859                                                 ewarn "packages listed to rebuild either. This is a bug."
860                                         else
861                                                 list_skipped_packages
862                                         fi
863                                         die 1 'Warning: Portage cannot rebuild any of the necessary packages.'
864                                 fi
865                         else
866                                 RAW_REBUILD_LIST=("${RAW_REBUILD_LIST[@]/#/=}")
867                         fi
868                         RAW_REBUILD_LIST="${RAW_REBUILD_LIST[@]}"
869                         REBUILD_GREP=$(emerge --nodeps $RAW_REBUILD_LIST | sed 's/\[[^]]*\]//g')
870                         if (( ${PIPESTATUS[0]} == 0 )); then
871                                 emerge --deep $RAW_REBUILD_LIST |
872                                         sed 's/\[[^]]*\]//g' |
873                                         grep -F "$REBUILD_GREP" > $LIST.5_order
874                         fi
875
876                         # Here we use the PIPESTATUS from the second emerge, the --deep one.
877                         if (( ${PIPESTATUS[0]} != 0 )); then
878                                         eerror
879                                         eerror 'Warning: Failed to resolve package order.'
880                                         eerror 'Will merge in arbitrary order'
881                                         eerror
882                                         cat <<- EOF
883                                                 Possible reasons:
884                                                 - An ebuild is no longer in the portage tree.
885                                                 - An ebuild is masked, use /etc/portage/packages.keyword
886                                                         and/or /etc/portage/package.unmask to unmask it
887                                         EOF
888                                         countdown 5
889                                         rm -f "$LIST.5_order"
890                         fi
891                         export EMERGE_DEFAULT_OPTS="$OLD_EMERGE_DEFAULT_OPTS"
892                 else
893                         einfo 'Nothing to rebuild.'
894                         die 1 '(The program should have already quit, so this is a minor bug.)'
895                 fi
896         fi
897         [[ -r $LIST.5_order && -s $LIST.5_order ]] && einfo "Generated new $LIST.5_order"
898 }
899
900 get_search_env
901 echo
902 get_files
903 echo
904 get_ldpath
905 echo
906 main_checks
907 echo
908 if [[ $PACKAGE_NAMES ]]; then
909         get_packages
910         echo
911         clean_packages
912         echo
913         assign_packages_to_ebuilds
914 else
915         get_exact_ebuilds
916 fi
917 echo
918 get_build_order
919 echo
920
921 # Clean up no longer needed environment variables
922 unset SEARCH_DIRS SEARCH_DIRS_MASK LD_LIBRARY_MASK PORTAGE_ROOT
923
924 if [[ -r $LIST.5_order && -s $LIST.5_order ]]; then
925         REBUILD_LIST=( $(<"$LIST.5_order") )
926         REBUILD_LIST="${REBUILD_LIST[@]/#/=}"
927 else
928         REBUILD_LIST=$(sort -u "$LIST.4_ebuilds")
929 fi
930
931 trap - SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM
932
933 einfo 'All prepared. Starting rebuild'
934 echo "emerge --oneshot ${EMERGE_OPTIONS[@]} $REBUILD_LIST"
935
936 is_real_merge && countdown 10
937
938 # Link file descriptor #6 with stdin so --ask will work
939 exec 6<&0
940
941 # Run in background to correctly handle Ctrl-C
942 {
943         EMERGE_DEFAULT_OPTS="--oneshot ${EMERGE_OPTIONS[@]}" emerge $REBUILD_LIST <&6
944         echo $? > $LIST.6_status
945 } &
946 wait
947
948 # Now restore stdin from fd #6, where it had been saved, and close fd #6 ( 6<&- ) to free it for other processes to use.
949 exec 0<&6 6<&-
950
951 show_unowned_files() {
952         if grep -qF '(none)' "$LIST.4_package_owners"; then
953                 ewarn "Found some broken files that weren't associated with known packages"
954                 ewarn "The broken files are:"
955                 while read filename junk; do
956                         [[ $junk = *none* ]] && ewarn "  $filename"
957                 done < "$LIST.4_package_owners" | awk '!s[$0]++' # (omit dupes)
958         fi
959 }
960
961 if (( $(<"$LIST.6_status") != 0 )); then
962         ewarn
963         ewarn "$APP_NAME failed to emerge all packages."
964         ewarn 'you have the following choices:'
965         einfo "- If emerge failed during the build, fix the problems and re-run $APP_NAME."
966         einfo '- Use /etc/portage/package.keywords to unmask a newer version of the package.'
967         einfo "  (and remove $LIST.5_order to be evaluated again)"
968         einfo '- Modify the above emerge command and run it manually.'
969         einfo '- Compile or unmerge unsatisfied packages manually,'
970         einfo '  remove temporary files, and try again.'
971         einfo '  (you can edit package/ebuild list first)'
972         einfo
973         einfo 'To remove temporary files, please run:'
974         einfo "rm $LIST*.?_*"
975         show_unowned_files
976         exit $EMERGE_STATUS
977 elif is_real_merge; then
978         trap_cmd() {
979                 eerror "terminated. Please remove the temporary files manually:"
980                 eerror "rm $LIST*.?_*"
981                 exit 1
982         }
983         (( "${#SKIP_LIST[@]}" != 0 )) && list_skipped_packages
984         trap trap_cmd SIGHUP SIGINT SIGQUIT SIGABRT SIGTERM
985         einfo 'Build finished correctly. Removing temporary files...'
986         einfo
987         einfo 'You can re-run revdep-rebuild to verify that all libraries and binaries'
988         einfo 'are fixed. If some inconsistency remains, it can be orphaned file, deep'
989         einfo 'dependency, binary package or specially evaluated library.'
990         if [[ -r "$LIST.4_package_owners" && -s "$LIST.4_package_owners" ]]; then
991         show_unowned_files
992         fi
993         [[ $KEEP_TEMP ]] || rm $LIST*.?_*
994 else
995         einfo 'Now you can remove -p (or --pretend) from arguments and re-run revdep-rebuild.'
996 fi
997