repoman: remove set literal for python-2.6 compat
[portage.git] / bin / etc-update
1 #!/bin/bash
2 # Copyright 1999-2012 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
4
5 # Author Brandon Low <lostlogic@gentoo.org>
6 # Mike Frysinger <vapier@gentoo.org>
7 #
8 # Previous version (from which I've borrowed a few bits) by:
9 # Jochem Kossen <j.kossen@home.nl>
10 # Leo Lipelis <aeoo@gentoo.org>
11 # Karl Trygve Kalleberg <karltk@gentoo.org>
12
13 cd /
14
15 type -P gsed >/dev/null && sed() { gsed "$@"; }
16
17 get_config() {
18         # the sed here does:
19         #  - strip off comments
20         #  - match lines that set item in question
21         #    - delete the "item =" part
22         #    - store the actual value into the hold space
23         #  - on the last line, restore the hold space and print it
24         # If there's more than one of the same configuration item, then
25         # the store to the hold space clobbers previous value so the last
26         # setting takes precedence.
27         local match=$1
28         eval $(sed -n -r \
29                 -e 's:[[:space:]]*#.*$::' \
30                 -e "/^[[:space:]]*${match}[[:space:]]*=/{s:^([^=]*)=[[:space:]]*([\"']{0,1})(.*)\2:\1=\2\3\2:;H}" \
31                 -e '${g;p}' \
32                 "${PORTAGE_CONFIGROOT}"etc/etc-update.conf)
33 }
34
35 cmd_var_is_valid() {
36         # return true if the first whitespace-separated token contained
37         # in "${1}" is an executable file, false otherwise
38         [[ -x $(type -P ${1%%[[:space:]]*}) ]]
39 }
40
41 diff_command() {
42         local cmd=${diff_command//%file1/$1}
43         ${cmd//%file2/$2}
44 }
45
46 # Usage: do_mv_ln [options] <src> <dst>
47 # Files have to be the last two args, and has to be
48 # files so we can handle symlinked target sanely.
49 do_mv_ln() {
50         local opts=( ${@:1:$(( $# - 2 ))} )
51         local src=${@:$(( $# - 1 )):1}
52         local dst=${@:$(( $# - 0 )):1}
53
54         if [[ -L ${dst} ]] ; then #330221
55                 local lfile=$(readlink "${dst}")
56                 [[ ${lfile} == /* ]] || lfile="${dst%/*}/${lfile}"
57                 echo " Target is a symlink; replacing ${lfile}"
58                 dst=${lfile}
59         fi
60
61         mv "${opts[@]}" "${src}" "${dst}"
62 }
63
64 scan() {
65         ${QUIET} || echo "Scanning Configuration files..."
66         rm -rf "${TMP}"/files > /dev/null 2>&1
67         mkdir "${TMP}"/files || die "Failed mkdir command!"
68         count=0
69         input=0
70         local find_opts
71         local path
72
73         for path in ${SCAN_PATHS} ; do
74                 path="${EROOT%/}${path}"
75
76                 if [[ ! -d ${path} ]] ; then
77                         [[ ! -f ${path} ]] && continue
78                         local my_basename="${path##*/}"
79                         path="${path%/*}"
80                         find_opts=( -maxdepth 1 -name "._cfg????_${my_basename}" )
81                 else
82                         # Do not traverse hidden directories such as .svn or .git.
83                         find_opts=( -name '.*' -type d -prune -o -name '._cfg????_*' )
84                 fi
85                 find_opts+=( ! -name '.*~' ! -iname '.*.bak' -print )
86
87                 if [ ! -w "${path}" ] ; then
88                         [ -e "${path}" ] || continue
89                         die "Need write access to ${path}"
90                 fi
91
92                 local file ofile b=$'\001'
93                 for file in $(find "${path}"/ "${find_opts[@]}" |
94                               sed \
95                                                 -e 's://*:/:g' \
96                                                 -e "s:\(^.*/\)\(\._cfg[0-9]*_\)\(.*$\):\1\2\3$b\1$b\2$b\3:" |
97                               sort -t"$b" -k2,2 -k4,4 -k3,3 |
98                               LC_ALL=C cut -f1 -d"$b")
99                 do
100                         local rpath rfile cfg_file live_file
101                         rpath=${file%/*}
102                         rfile=${file##*/}
103                         cfg_file="${rpath}/${rfile}"
104                         live_file="${rpath}/${rfile:10}"
105
106                         local mpath
107                         for mpath in ${CONFIG_PROTECT_MASK}; do
108                                 mpath="${EROOT%/}${mpath}"
109                                 if [[ "${rpath}" == "${mpath}"* ]] ; then
110                                         ${QUIET} || echo "Updating masked file: ${live_file}"
111                                         mv "${cfg_file}" "${live_file}"
112                                         continue 2
113                                 fi
114                         done
115                         if [[ ! -f ${file} ]] ; then
116                                 ${QUIET} || echo "Skipping non-file ${file} ..."
117                                 continue
118                         fi
119
120                         if [[ "${ofile:10}" != "${rfile:10}" ]] ||
121                            [[ ${opath} != ${rpath} ]]
122                         then
123                                 MATCHES=0
124                                 if [[ ${eu_automerge} == "yes" ]] ; then
125                                         if [[ ! -e ${cfg_file} || ! -e ${live_file} ]] ; then
126                                                 MATCHES=0
127                                         else
128                                                 diff -Bbua "${cfg_file}" "${live_file}" | \
129                                                         sed -n -r \
130                                                                 -e '/^[+-]/{/^([+-][\t ]*(#|$)|-{3} |\+{3} )/d;q1}'
131                                                 : $(( MATCHES = ($? == 0) ))
132                                         fi
133
134                                 else
135                                         diff -Nbua "${cfg_file}" "${live_file}" |
136                                                 sed -n \
137                                                         -e '/# .Header:/d' \
138                                                         -e '/^[+-][^+-]/q1'
139                                         : $(( MATCHES = ($? == 0) ))
140                                 fi
141
142                                 if [[ ${MATCHES} == 1 ]] ; then
143                                         ${QUIET} || echo "Automerging trivial changes in: ${live_file}"
144                                         do_mv_ln "${cfg_file}" "${live_file}"
145                                         continue
146                                 else
147                                         : $(( ++count ))
148                                         echo "${live_file}" > "${TMP}"/files/${count}
149                                         echo "${cfg_file}" >> "${TMP}"/files/${count}
150                                         ofile="${rfile}"
151                                         opath="${rpath}"
152                                         continue
153                                 fi
154                         fi
155
156                         if ! diff -Nbua "${cfg_file}" "${rpath}/${ofile}" |
157                                 sed -n \
158                                         -e '/# .Header:/d' \
159                                         -e '/^[+-][^+-]/q1'
160                         then
161                                 echo "${cfg_file}" >> "${TMP}"/files/${count}
162                                 ofile="${rfile}"
163                                 opath="${rpath}"
164                         else
165                                 mv "${cfg_file}" "${rpath}/${ofile}"
166                                 continue
167                         fi
168                 done
169         done
170 }
171
172 parse_automode_flag() {
173         case $1 in
174         -9)
175                 local reply
176                 read -p "Are you sure that you want to delete all updates (type YES): " reply
177                 if [[ ${reply} != "YES" ]] ; then
178                         echo "Did not get a 'YES', so ignoring request"
179                         return 1
180                 else
181                         parse_automode_flag -7
182                         export rm_opts=""
183                 fi
184                 ;;
185         -7)
186                 input=0
187                 export DELETE_ALL="yes"
188                 ;;
189         -5)
190                 parse_automode_flag -3
191                 export mv_opts=" ${mv_opts} "
192                 mv_opts="${mv_opts// -i / }"
193                 NONINTERACTIVE_MV=true
194                 ;;
195         -3)
196                 input=0
197                 export OVERWRITE_ALL="yes"
198                 ;;
199         *)
200                 return 1
201                 ;;
202         esac
203         return 0
204 }
205
206 sel_file() {
207         local -i isfirst=0
208         until [[ -f ${TMP}/files/${input} ]] || \
209               [[ ${input} == -1 ]] || \
210               [[ ${input} == -3 ]]
211         do
212                 local allfiles=( $(cd "${TMP}"/files/ && printf '%s\n' * | sort -n) )
213                 local isfirst=${allfiles[0]}
214
215                 # Optimize: no point in building the whole file list if
216                 # we're not actually going to talk to the user.
217                 if [[ ${OVERWRITE_ALL} == "yes" || ${DELETE_ALL} == "yes" ]] ; then
218                         input=0
219                 else
220                         local numfiles=${#allfiles[@]}
221                         local numwidth=${#numfiles}
222                         local file fullfile line
223                         for file in "${allfiles[@]}" ; do
224                                 fullfile="${TMP}/files/${file}"
225                                 line=$(head -n1 "${fullfile}")
226                                 printf '%*i%s %s' ${numwidth} ${file} "${PAR}" "${line}"
227                                 if [[ ${mode} == 0 ]] ; then
228                                         local numupdates=$(( $(wc -l <"${fullfile}") - 1 ))
229                                         echo " (${numupdates})"
230                                 else
231                                         echo
232                                 fi
233                         done > "${TMP}"/menuitems
234
235                         clear
236
237                         if [[ ${mode} == 0 ]] ; then
238                                 cat <<-EOF
239                                         The following is the list of files which need updating, each
240                                         configuration file is followed by a list of possible replacement files.
241                                         $(<"${TMP}"/menuitems)
242                                         Please select a file to edit by entering the corresponding number.
243                                                       (don't use -3, -5, -7 or -9 if you're unsure what to do)
244                                                       (-1 to exit) (${_3_HELP_TEXT})
245                                                                    (${_5_HELP_TEXT})
246                                                                    (${_7_HELP_TEXT})
247                                 EOF
248                                 printf "                           (${_9_HELP_TEXT}): "
249                                 input=$(read_int)
250                         else
251                                 dialog \
252                                         --title "${title}" \
253                                         --menu "Please select a file to update" \
254                                         0 0 0 $(<"${TMP}"/menuitems) \
255                                         2> "${TMP}"/input \
256                                         || die "$(<"${TMP}"/input)\n\nUser termination!" 0
257                                 input=$(<"${TMP}"/input)
258                         fi
259                         : ${input:=0}
260
261                         if [[ ${input} != 0 ]] ; then
262                                 parse_automode_flag ${input} || continue
263                         fi
264                 fi # -3 automerge
265                 if [[ ${input} == 0 ]] ; then
266                         input=${isfirst}
267                 fi
268         done
269 }
270
271 user_special() {
272         local special="${PORTAGE_CONFIGROOT}etc/etc-update.special"
273
274         if [[ -r ${special} ]] ; then
275                 if [[ -z $1 ]] ; then
276                         error "user_special() called without arguments"
277                         return 1
278                 fi
279                 local pat
280                 while read -r pat ; do
281                         echo "$1" | grep -q "${pat}" && return 0
282                 done < "${special}"
283         fi
284         return 1
285 }
286
287 read_int() {
288         # Read an integer from stdin.  Continously loops until a valid integer is
289         # read.  This is a workaround for odd behavior of bash when an attempt is
290         # made to store a value such as "1y" into an integer-only variable.
291         local my_input
292         while : ; do
293                 read my_input
294                 # failed integer conversions will break a loop unless they're enclosed
295                 # in a subshell.
296                 echo "${my_input}" | (declare -i x; read x) 2>/dev/null && break
297                 printf 'Value "%s" is not valid. Please enter an integer value: ' "${my_input}" >&2
298         done
299         echo ${my_input}
300 }
301
302 do_file() {
303         interactive_echo() { [ "${OVERWRITE_ALL}" != "yes" ] && [ "${DELETE_ALL}" != "yes" ] && echo; }
304         interactive_echo
305         local -i my_input
306         local -i linecnt
307         local fullfile="${TMP}/files/${input}"
308         local ofile=$(head -n1 "${fullfile}")
309
310         # Walk through all the pending updates for this one file.
311         linecnt=$(wc -l <"${fullfile}")
312         while (( linecnt > 1 )) ; do
313                 if (( linecnt == 2 )) ; then
314                         # Only one update ... keeps things simple.
315                         my_input=1
316                 else
317                         my_input=0
318                 fi
319
320                 # Optimize: no point in scanning the file list when we know
321                 # we're just going to consume all the ones available.
322                 if [[ ${OVERWRITE_ALL} == "yes" || ${DELETE_ALL} == "yes" ]] ; then
323                         my_input=1
324                 fi
325
326                 # Figure out which file they wish to operate on.
327                 while (( my_input <= 0 || my_input >= linecnt )) ; do
328                         local fcount=0
329                         for line in $(<"${fullfile}"); do
330                                 if (( fcount > 0 )); then
331                                         printf '%i%s %s\n' ${fcount} "${PAR}" "${line}"
332                                 fi
333                                 : $(( ++fcount ))
334                         done > "${TMP}"/menuitems
335
336                         if [[ ${mode} == 0 ]] ; then
337                                 echo "Below are the new config files for ${ofile}:"
338                                 cat "${TMP}"/menuitems
339                                 echo -n "Please select a file to process (-1 to exit this file): "
340                                 my_input=$(read_int)
341                         else
342                                 dialog \
343                                         --title "${title}" \
344                                         --menu "Please select a file to process for ${ofile}" \
345                                         0 0 0 $(<"${TMP}"/menuitems) \
346                                         2> "${TMP}"/input \
347                                         || die "$(<"${TMP}"/input)\n\nUser termination!" 0
348                                 my_input=$(<"${TMP}"/input)
349                         fi
350
351                         if [[ ${my_input} == 0 ]] ; then
352                                 # Auto select the first file.
353                                 my_input=1
354                         elif [[ ${my_input} == -1 ]] ; then
355                                 input=0
356                                 return
357                         fi
358                 done
359
360                 # First line is the old file while the rest are the config files.
361                 : $(( ++my_input ))
362                 local file=$(sed -n -e "${my_input}p" "${fullfile}")
363                 do_cfg "${file}" "${ofile}"
364
365                 sed -i -e "${my_input}d" "${fullfile}"
366
367                 : $(( --linecnt ))
368         done
369
370         interactive_echo
371         rm "${fullfile}"
372         : $(( --count ))
373 }
374
375 show_diff() {
376         clear
377         local file1=$1 file2=$2
378         if [[ ${using_editor} == 0 ]] ; then
379                 (
380                         echo "Showing differences between ${file1} and ${file2}"
381                         diff_command "${file1}" "${file2}"
382                 ) | ${pager}
383         else
384                 echo "Beginning of differences between ${file1} and ${file2}"
385                 diff_command "${file1}" "${file2}"
386                 echo "End of differences between ${file1} and ${file2}"
387         fi
388 }
389
390 do_cfg() {
391         local file=$1
392         local ofile=$2
393         local -i my_input=0
394
395         until (( my_input == -1 )) || [ ! -f "${file}" ] ; do
396                 if [[ "${OVERWRITE_ALL}" == "yes" ]] && ! user_special "${ofile}"; then
397                         my_input=1
398                 elif [[ "${DELETE_ALL}" == "yes" ]] && ! user_special "${ofile}"; then
399                         my_input=2
400                 else
401                         show_diff "${ofile}" "${file}"
402                         if [[ -L ${file} ]] ; then
403                                 cat <<-EOF
404
405                                         -------------------------------------------------------------
406                                         NOTE: File is a symlink to another file. REPLACE recommended.
407                                               The original file may simply have moved. Please review.
408                                         -------------------------------------------------------------
409
410                                 EOF
411                         fi
412                         cat <<-EOF
413
414                                 File: ${file}
415                                 1) Replace original with update
416                                 2) Delete update, keeping original as is
417                                 3) Interactively merge original with update
418                                 4) Show differences again
419                                 5) Save update as example config
420                         EOF
421                         printf 'Please select from the menu above (-1 to ignore this update): '
422                         my_input=$(read_int)
423                 fi
424
425                 case ${my_input} in
426                         1)      echo "Replacing ${ofile} with ${file}"
427                                 do_mv_ln ${mv_opts} "${file}" "${ofile}"
428                                 [ -n "${OVERWRITE_ALL}" ] && my_input=-1
429                                 continue
430                                 ;;
431                         2)      echo "Deleting ${file}"
432                                 rm ${rm_opts} "${file}"
433                                 [ -n "${DELETE_ALL}" ] && my_input=-1
434                                 continue
435                                 ;;
436                         3)      do_merge "${file}" "${ofile}"
437                                 my_input=${?}
438 #                               [ ${my_input} == 255 ] && my_input=-1
439                                 continue
440                                 ;;
441                         4)      continue
442                                 ;;
443                         5)      do_distconf "${file}" "${ofile}"
444                                 ;;
445                         *)      continue
446                                 ;;
447                 esac
448         done
449 }
450
451 do_merge() {
452         # make sure we keep the merged file in the secure tempdir
453         # so we dont leak any information contained in said file
454         # (think of case where the file has 0600 perms; during the
455         # merging process, the temp file gets umask perms!)
456
457         local file="${1}"
458         local ofile="${2}"
459         local mfile="${TMP}/${2}.merged"
460         local -i my_input=0
461         echo "${file} ${ofile} ${mfile}"
462
463         if [[ -e ${mfile} ]] ; then
464                 echo "A previous version of the merged file exists, cleaning..."
465                 rm ${rm_opts} "${mfile}"
466         fi
467
468         # since mfile will be like $TMP/path/to/original-file.merged, we
469         # need to make sure the full /path/to/ exists ahead of time
470         mkdir -p "${mfile%/*}"
471
472         until (( my_input == -1 )); do
473                 echo "Merging ${file} and ${ofile}"
474                 $(echo "${merge_command}" |
475                  sed -e "s:%merged:${mfile}:g" \
476                          -e "s:%orig:${ofile}:g" \
477                          -e "s:%new:${file}:g")
478                 until (( my_input == -1 )); do
479                         cat <<-EOF
480                                 1) Replace ${ofile} with merged file
481                                 2) Show differences between merged file and original
482                                 3) Remerge original with update
483                                 4) Edit merged file
484                                 5) Return to the previous menu
485                         EOF
486                         printf 'Please select from the menu above (-1 to exit, losing this merge): '
487                         my_input=$(read_int)
488                         case ${my_input} in
489                                 1)      echo "Replacing ${ofile} with ${mfile}"
490                                         if [[ ${USERLAND} == BSD ]] ; then
491                                                 chown "$(stat -f %Su:%Sg "${ofile}")" "${mfile}"
492                                                 chmod $(stat -f %Mp%Lp "${ofile}") "${mfile}"
493                                         else
494                                                 chown --reference="${ofile}" "${mfile}"
495                                                 chmod --reference="${ofile}" "${mfile}"
496                                         fi
497                                         do_mv_ln ${mv_opts} "${mfile}" "${ofile}"
498                                         rm ${rm_opts} "${file}"
499                                         return 255
500                                         ;;
501                                 2)      show_diff "${ofile}" "${mfile}"
502                                         continue
503                                         ;;
504                                 3)      break
505                                         ;;
506                                 4)      ${EDITOR:-nano -w} "${mfile}"
507                                         continue
508                                         ;;
509                                 5)      rm ${rm_opts} "${mfile}"
510                                         return 0
511                                         ;;
512                                 *)      continue
513                                         ;;
514                         esac
515                 done
516         done
517         rm ${rm_opts} "${mfile}"
518         return 255
519 }
520
521 do_distconf() {
522         # search for any previously saved distribution config
523         # files and number the current one accordingly
524
525         local file=$1 ofile=$2
526         local -i count
527         local suffix
528         local efile
529
530         for (( count = 0; count <= 9999; ++count )) ; do
531                 suffix=$(printf ".dist_%04i" ${count})
532                 efile="${ofile}${suffix}"
533                 if [[ ! -f ${efile} ]] ; then
534                         mv ${mv_opts} "${file}" "${efile}"
535                         break
536                 elif diff_command "${file}" "${efile}" &> /dev/null; then
537                         # replace identical copy
538                         mv "${file}" "${efile}"
539                         break
540                 fi
541         done
542 }
543
544 error() { echo "etc-update: ERROR: $*" 1>&2 ; return 1 ; }
545 die() {
546         trap SIGTERM
547         trap SIGINT
548         local msg=$1 exitcode=${2:-1}
549
550         if [ ${exitcode} -eq 0 ] ; then
551                 ${QUIET} || printf 'Exiting: %b\n' "${msg}"
552                 scan > /dev/null
553                 ! ${QUIET} && [ ${count} -gt 0 ] && echo "NOTE: ${count} updates remaining"
554         else
555                 error "${msg}"
556         fi
557
558         rm -rf "${TMP}"
559         exit ${exitcode}
560 }
561
562 _3_HELP_TEXT="-3 to auto merge all files"
563 _5_HELP_TEXT="-5 to auto-merge AND not use 'mv -i'"
564 _7_HELP_TEXT="-7 to discard all updates"
565 _9_HELP_TEXT="-9 to discard all updates AND not use 'rm -i'"
566 usage() {
567         cat <<-EOF
568         etc-update: Handle configuration file updates
569
570         Usage: etc-update [options] [paths to scan]
571
572         If no paths are specified, then \${CONFIG_PROTECT} will be used.
573
574         Options:
575           -d, --debug    Enable shell debugging
576           -h, --help     Show help and run away
577           -p, --preen    Automerge trivial changes only and quit
578           -q, --quiet    Show only essential output
579           -v, --verbose  Show settings and such along the way
580           -V, --version  Show version and trundle away
581
582           --automode <mode>
583                      ${_3_HELP_TEXT}
584                      ${_5_HELP_TEXT}
585                      ${_7_HELP_TEXT}
586                      ${_9_HELP_TEXT}
587         EOF
588
589         [[ $# -gt 1 ]] && printf "\nError: %s\n" "${*:2}" 1>&2
590
591         exit ${1:-0}
592 }
593
594 #
595 # Run the script
596 #
597
598 declare -i count=0
599 declare input=0
600 declare title="Gentoo's etc-update tool!"
601
602 PREEN=false
603 SET_X=false
604 QUIET=false
605 VERBOSE=false
606 NONINTERACTIVE_MV=false
607 while [[ -n $1 ]] ; do
608         case $1 in
609                 -d|--debug)   SET_X=true;;
610                 -h|--help)    usage;;
611                 -p|--preen)   PREEN=true;;
612                 -q|--quiet)   QUIET=true;;
613                 -v|--verbose) VERBOSE=true;;
614                 -V|--version) emerge --version; exit 0;;
615                 --automode)   parse_automode_flag $2 && shift || usage 1 "Invalid mode '$2'";;
616                 -*)           usage 1 "Invalid option '$1'";;
617                 *)            break;;
618         esac
619         shift
620 done
621 ${SET_X} && set -x
622
623 type -P portageq >/dev/null || die "missing portageq"
624 portage_vars=(
625         CONFIG_PROTECT{,_MASK}
626         PORTAGE_CONFIGROOT
627         PORTAGE_INST_{G,U}ID
628         PORTAGE_TMPDIR
629         EROOT
630         USERLAND
631         NOCOLOR
632 )
633 eval $(${PORTAGE_PYTHON:+"${PORTAGE_PYTHON}"} "$(type -P portageq)" envvar -v ${portage_vars[@]})
634 export PORTAGE_TMPDIR
635 SCAN_PATHS=${*:-${CONFIG_PROTECT}}
636
637 TMP="${PORTAGE_TMPDIR}/etc-update-$$"
638 trap "die terminated" SIGTERM
639 trap "die interrupted" SIGINT
640
641 rm -rf "${TMP}" 2>/dev/null
642 mkdir "${TMP}" || die "failed to create temp dir"
643 # make sure we have a secure directory to work in
644 chmod 0700 "${TMP}" || die "failed to set perms on temp dir"
645 chown ${PORTAGE_INST_UID:-0}:${PORTAGE_INST_GID:-0} "${TMP}" || \
646         die "failed to set ownership on temp dir"
647
648 # Get all the user settings from etc-update.conf
649 cfg_vars=(
650         clear_term
651         eu_automerge
652         rm_opts
653         mv_opts
654         pager
655         diff_command
656         using_editor
657         merge_command
658         mode
659 )
660 # default them all to ""
661 eval ${cfg_vars[@]/%/=}
662 # then extract them all from the conf in one shot
663 # (ugly var at end is due to printf appending a '|' to last item)
664 get_config "($(printf '%s|' "${cfg_vars[@]}")NOVARFOROLDMEN)"
665
666 # finally setup any specific defaults
667 : ${mode:="0"}
668 if ! cmd_var_is_valid "${pager}" ; then
669         pager=${PAGER}
670         cmd_var_is_valid "${pager}" || pager=cat
671 fi
672
673 [[ ${clear_term} == "yes" ]] || clear() { :; }
674
675 if [[ ${using_editor} == "0" ]] ; then
676         # Sanity check to make sure diff exists and works
677         echo > "${TMP}"/.diff-test-1
678         echo > "${TMP}"/.diff-test-2
679
680         if ! diff_command "${TMP}"/.diff-test-1 "${TMP}"/.diff-test-2 ; then
681                 die "'${diff_command}' does not seem to work, aborting"
682         fi
683 else
684         # NOTE: cmd_var_is_valid doesn't work with diff_command="eval emacs..."
685         # because it uses type -P.
686         if ! type ${diff_command%%[[:space:]]*} >/dev/null; then
687                 die "'${diff_command}' does not seem to work, aborting"
688         fi
689 fi
690
691 if [[ ${mode} == "0" ]] ; then
692         PAR=")"
693 else
694         PAR=""
695         if ! type dialog >/dev/null || ! dialog --help >/dev/null ; then
696                 die "mode=1 and 'dialog' not found or not executable, aborting"
697         fi
698 fi
699
700 if ${NONINTERACTIVE_MV} ; then
701         export mv_opts=" ${mv_opts} "
702         mv_opts="${mv_opts// -i / }"
703 fi
704
705 if ${VERBOSE} ; then
706         for v in ${portage_vars[@]} ${cfg_vars[@]} TMP SCAN_PATHS ; do
707                 echo "${v}=${!v}"
708         done
709 fi
710
711 scan
712
713 ${PREEN} && exit 0
714
715 until (( input == -1 )); do
716         if (( count == 0 )); then
717                 die "Nothing left to do; exiting. :)" 0
718         fi
719         sel_file
720         if (( input != -1 )); then
721                 do_file
722         fi
723 done
724
725 die "User termination!" 0