#!/usr/bin/perl -w
use strict;
use File::Basename;
use Getopt::Long qw(:config bundling permute pass_through);
use Term::ANSIColor qw(:constants);
use Term::ReadKey;
use constant {
    STATE_UNKNOWN => '--',
    STATE_MF      => 'MF',
    STATE_MB      => 'MB',
    STATE_UF      => 'UF',
    STATE_UB      => 'UB',
    STATE_CF      => 'CF',
    STATE_CB      => 'CB',
    STATE_LF      => 'LF',
    STATE_FL      => 'FL',
    STATE_LL      => 'LL',
};
$Term::ANSIColor::AUTORESET = 1;

#
# Program: /usr/bin/cfg-update
# Author: Stephan van Boven (xentric on Gentoo Forums)
#
# This program is free software. You can redistribute it and/or modify it
# under the terms of the GNU General Public License v2 as published by the
# Free Software Foundation. This program is distributed in the hope that it
# will be useful, but without ANY warranty, without even the implied warranty
# of merchantability or fitness for a particular purpose. See the GNU General
# Public License v2 for more details.
#

######################################################################################################################
#                                          GLOBAL VARIABLES AND SETTINGS                                             #
######################################################################################################################

# Setting program variables...
    my $version           = "1.11.0";
    my $progname          = basename($0);
    my $debug             = "2>/dev/null";
    my $website           = "https://github.com/rich0/cfg-update";
# Set default settings...
    my $pkg_manager       = "Portage";
    my $install_log       = "/var/log/emerge.log";
    my $install_log_configured = 0;
    my $find_string       = "::: completed emerge";
    my $pkg_db            = "/var/db/pkg";
    my $portage_hook      = "/etc/portage/bashrc";
    my $paludis_hook      = "/usr/share/paludis/hooks/install_all_pre/cfg-update.bash";
    my $merge_tool        = "/usr/bin/meld";
    my $merge_tool_name   = "meld";
    my $view_tool         = "less";
    my $xxdiff_style      = "--style Keramik";
    my $backup_path       = "/var/lib/cfg-update/backups";
    my $index_file        = "/var/lib/cfg-update/checksum.index";
    my $settings          = "/etc/cfg-update.conf";
    $settings   = $ENV{CFG_UPDATE_CONF}  if defined $ENV{CFG_UPDATE_CONF}  && $ENV{CFG_UPDATE_CONF}  ne "";
    my $enable_backups    = "yes";
    my $enable_stage1     = "yes";
    my $enable_stage2     = "yes";
    my $enable_stage3     = "yes";
    my $enable_stage4     = "yes";
    my $enable_stage5     = "yes";
    my $config_new        = "._cfg????_*";          # search string used to identify the new config files
    my $rm_new            = "\\._cfg...._";         # regular expression to strip $config_new from filename (needs double escapes!)
    my $temp_new          = "._temp-new-cfg_*";     # filename format for temporary storage of new ancestor file
    my $backup_new        = "._new-cfg_*";          # filename format for backup new config file
    my $restore_new       = "._cfg0000_*";          # filename format for restoring the new config file
    my $rm_old            = "\\._old-cfg_";         # regular expression to strip $backup_old from filename (needs double escapes!)
    my $temp_old          = "._temp-old-cfg_*";     # filename format for temporary storage of new previous config file
    my $backup_old        = "._old-cfg_*";          # filename format for backup original config file
    my $restore_old       = "*";                    # filename format for restoring the old config file
    my $merged            = "*.merge";              # filename format for the merged result
    my $rootdir           = "/root";
# Override default settings with values from configuration file...
    open(FILE, $settings) || die "Can't open file: $settings!\n";
    while (my $line = <FILE>) {
        chomp $line;
        if ($line =~ /^\s*MERGETOOL\s*=\s*/i)         { $merge_tool        = &strip($'); }
        if ($line =~ /^\s*MERGE_TOOL\s*=\s*/i)        { $merge_tool        = &strip($'); }
        if ($line =~ /^\s*ENABLE_BACKUPS\s*=\s*/i)    { $enable_backups    = &strip($'); }
        if ($line =~ /^\s*ENABLE_STAGE1\s*=\s*/i)     { $enable_stage1     = &strip($'); }
        if ($line =~ /^\s*ENABLE_STAGE2\s*=\s*/i)     { $enable_stage2     = &strip($'); }
        if ($line =~ /^\s*ENABLE_STAGE3\s*=\s*/i)     { $enable_stage3     = &strip($'); }
        if ($line =~ /^\s*ENABLE_STAGE4\s*=\s*/i)     { $enable_stage4     = &strip($'); }
        if ($line =~ /^\s*ENABLE_STAGE5\s*=\s*/i)     { $enable_stage5     = &strip($'); }
        if ($line =~ /^\s*VIEW_TOOL\s*=\s*/i)         { $view_tool         = &strip($'); }
        if ($line =~ /^\s*BACKUP_PATH\s*=\s*/i)       { $backup_path       = &strip($'); }
        if ($line =~ /^\s*INDEXFILE\s*=\s*/i)         { $index_file        = &strip($'); }
        if ($line =~ /^\s*INDEX_FILE\s*=\s*/i)        { $index_file        = &strip($'); }
        if ($line =~ /^\s*PKG_DB\s*=\s*/i)            { $pkg_db            = &strip($'); }
        if ($line =~ /^\s*INSTALL_LOG\s*=\s*/i)       { $install_log = &strip($'); $install_log_configured = 1; }
        if ($line =~ /^\s*PORTAGE_HOOK\s*=\s*/i)      { $portage_hook      = &strip($'); }
        if ($line =~ /^\s*PALUDIS_HOOK\s*=\s*/i)      { $paludis_hook      = &strip($'); }
        if ($line =~ /^\s*ROOTDIR\s*=\s*/i)           { $rootdir           = &strip($'); }
        if ($line =~ /^\s*XXDIFF_STYLE\s*=\s*/i)      { $xxdiff_style      = &strip($'); }
        if ($line =~ /^\s*CONFIG_NEW\s*=\s*/i)        { $config_new        = &strip($'); }
        if ($line =~ /^\s*RM_NEW\s*=\s*/i)            { $rm_new            = &strip($'); }
        if ($line =~ /^\s*TEMP_NEW\s*=\s*/i)          { $temp_new          = &strip($'); }
        if ($line =~ /^\s*BACKUP_NEW\s*=\s*/i)        { $backup_new        = &strip($'); }
        if ($line =~ /^\s*RESTORE_NEW\s*=\s*/i)       { $restore_new       = &strip($'); }
        if ($line =~ /^\s*RM_OLD\s*=\s*/i)            { $rm_old            = &strip($'); }
        if ($line =~ /^\s*TEMP_OLD\s*=\s*/i)          { $temp_old          = &strip($'); }
        if ($line =~ /^\s*BACKUP_OLD\s*=\s*/i)        { $backup_old        = &strip($'); }
        if ($line =~ /^\s*RESTORE_OLD\s*=\s*/i)       { $restore_old       = &strip($'); }
        if ($line =~ /^\s*MERGED\s*=\s*/i)            { $merged            = &strip($'); }
    }
    close(FILE);
# Check for trailing slash on backup_path...
    if ($backup_path =~ /\/$/) { chop($backup_path); }   # remove trailing slash if found
# Check if backup_path exists...
    if (! -e $backup_path) { `mkdir -p $backup_path`; }
# Other global variables...
    my $bar1          = "________________________________________________________________________________\n";
    my $bar2          = "--------------------------------------------------------------------------------";
    my $spacer        = "$progname $version"; $spacer =~ s/./ /g;
    my $package       = "";
    my $state         = "";
    my $vstate        = "";
    my %STATE_DESC    = (
        '--' => 'No index found   ',
        'MF' => 'Modified File    ',
        'MB' => 'Modified Binary  ',
        'UF' => 'Unmodified File  ',
        'UB' => 'Unmodified Binary',
        'CF' => 'Custom File      ',
        'CB' => 'Custom Binary    ',
        'LF' => 'Link to File     ',
        'FL' => 'File to Link     ',
        'LL' => 'Link to Link     ',
    );
    my $md5sum;
    my $md5sum_file;
    my $md5sum_index;
    my $md5sum_before;
    my $md5sum_update;
    my $md5sum_merged;
    my $md5sum_after;
    my $show_warning;
    my $threeway_update;
    my $tool_needs_gui = "no";
    my $tool_supports_2way;
    my $tool_supports_3way;
    my $tool_saves_mergefile_when_aborted;
    my $has_merge_conflict;
    my $has_ancestor;
    my $cmd;
    my @dir;
    my @maskdir;
    my @list;
    my @stage1_queue;
    my @stage2_queue;
    my @stage3_queue;
    my @stage4_queue;
    my @stage5_queue;
    my $input;
    my $key;
    my $num;
    my $count;
    my $totalcount;
    my $path;
    my $cfg_basename;
    my $path_live;        # /path/file                  <-- live protected config (/etc/foo)
    my $path_new;        # /path/._cfg0000_file        <-- Portage update marker (._cfg0000_foo)
    my $path_backup_old;        # /path/._old-cfg_file        <-- backup of previous live config
    my $path_backup_new;        # /path/._new-cfg_file        <-- backup of previous marker (3-way ancestor)
    my $path_merged;        # /path/file.merge            <-- merge result (*.merge)
    my $path_temp_old;        # /path/._temp_old-cfg_file   <-- temp copy of live config during merge
    my $path_temp_new;        # /path/._temp_new-cfg_file   <-- temp copy of marker during merge
    my $is_executable;
    my $ancestor;
    my $log_pkg   = "";
    my $index_pkg = "";
    my $tab       = "";
    my $env       = " ";

######################################################################################################################
#                                          COMMANDLINE ARGUMENT HANDLING                                            #
######################################################################################################################

# Set variables for commandline arguments...
    my $opt_i = 0;
    my $opt_f = 0;
    my $opt_l = 0;
    my $opt_u = 0;
    my $opt_b = 0;
    my $opt_r = 0;
    my $opt_a = 0;
    my $opt_m = 0;
    my $opt_d = 0;
    my $opt_p = 0;
    my $opt_v = 0;
    my $opt_t = "novalue"; # if this option is used without arguments, "novalue" will change in ""
    my $opt_help = 0;
    my $opt_ebuild = 0;
    my $opt_testsandbox = 0;
    my $opt_paludis = 0;
    my $opt_disable_portage_hook = 0;
    my $opt_disable_paludis_hook = 0;
    my $opt_optimize_backups = 0;
# Running this loop 25 times should be sufficient to extract all arguments...
    for (my $j = 0; $j < 25; ++$j) {
        GetOptions ('f|force+'                 => \$opt_f);
        GetOptions ('i|index+'                 => \$opt_i);
        GetOptions ('l|list+'                  => \$opt_l);
        GetOptions ('u|update+'                => \$opt_u);
        GetOptions ('b|backups+'               => \$opt_b);
        GetOptions ('r|restore:i'              => \$opt_r);
        GetOptions ('a|automatic-only+'        => \$opt_a);
        GetOptions ('m|manual-only+'           => \$opt_m);
        GetOptions ('d|debug+'                 => \$opt_d);
        GetOptions ('p|pretend+'               => \$opt_p);
        GetOptions ('v|verbose+'               => \$opt_v);
        GetOptions ('t|tool=s'                 => \$opt_t);
        GetOptions ('help+'                    => \$opt_help);
        GetOptions ('disable-portage-hook+'    => \$opt_disable_portage_hook);
        GetOptions ('disable-paludis-hook+'    => \$opt_disable_paludis_hook);
        GetOptions ('paludis+'                 => \$opt_paludis);
        GetOptions ('ebuild+'                  => \$opt_ebuild);
        GetOptions ('testsandbox+'             => \$opt_testsandbox);
        GetOptions ('optimize-backups+'        => \$opt_optimize_backups);
    }
# Update the mergetool and mergetoolname variable with optional commandline argument -t...
    if ($opt_t !~ /^novalue$/) {
        $merge_tool = $opt_t;
        $merge_tool_name = basename($merge_tool);
    } else {
# Or update the mergetoolname with the override value from the /etc/cfg-update.conf...
        $merge_tool_name = basename($merge_tool);
    }
# Enable stages depending on -a or -m flag...
    if (($opt_a >= 1) && ($opt_m >= 1)) {
        print "You cannot use flags -a and -m at the same time!\n";
        exit;
    }
    if (($opt_m >= 1) && ($opt_a == 0)) {
        $enable_stage1  = "no";
        $enable_stage2  = "no";
        $enable_stage3  = "yes";
        $enable_stage4  = "yes";
        $enable_stage5  = "yes";
    }
    if (($opt_a >= 1) && ($opt_m == 0)) {
        $enable_stage1  = "yes";
        $enable_stage2  = "yes";
        $enable_stage3  = "no";
        $enable_stage4  = "no";
        $enable_stage5  = "no";
    }
# If --paludis is used then set the package manager to Paludis...
    if ($opt_paludis > 0) {
        $pkg_manager = "Paludis";
        if (!$install_log_configured) { $install_log = "/var/log/paludis.log"; }
        $find_string = "finished install of package";
    }
# Else set the package manager to Portage...
    else {
        $pkg_manager = "Portage";
        if (!$install_log_configured) { $install_log = "/var/log/emerge.log"; }
        $find_string = "::: completed emerge";
    }
# If debug mode is detected show most important variables...
    if ($opt_d >= 1) { &show_debug_info; }
# Force building the checksum-index if it's missing (except when --ebuild option is used)...
    if (($opt_ebuild == 0) && (!-e "$index_file")) {
        my $uid = `id -u`;
        chomp $uid;
        if ($uid != 0) {
            print "$tab"."$index_file not found...\n";
            print "$tab"."You need root privileges to create the index...\n";
            exit;
        } else {
            &find_protected_dirs;
            &build_index;
        }
    }
# If --index is used then check/build index...
    if ($opt_i > 0) {
        &root_only unless &sandbox_mode;
        &check_index;
        exit;
    }
# Check the package manager hooks and enable them if possible (except when --ebuild option is used)...
    if ($opt_ebuild == 0) { &check_hooks; }
# Check the mergetool (except when --ebuild option is used)...
    if ($opt_ebuild == 0) {
        &check_tool;
    } else {
        # Sandbox tests use --ebuild; set capability flags without probing the host.
        $tool_supports_2way = "yes";
        $tool_supports_3way = "yes";
        $tool_saves_mergefile_when_aborted = "no";
    }

######################################################################################################################
#                                   EXECUTE SELECTED RUNMODE SUBROUTINE                                              #
######################################################################################################################

# Reject unrecognized options left in @ARGV when a runmode flag is also present (Getopt pass_through).
    if (@ARGV > 0) {
        my $runmode = ($opt_help > 0 || $opt_l > 0 || $opt_u > 0 ||
                       $opt_b > 0 || $opt_r > 0 || $opt_disable_portage_hook > 0 || $opt_disable_paludis_hook > 0 ||
                       $opt_optimize_backups > 0);
        if ($runmode) { &print_usage; exit; }
    }

# Go to runmode subroutine (optionally preceeded by checks) if a corresponding argument is found. Exit when done...
    if ($opt_help > 0)                 { &print_usage; exit; }
    if ($opt_l > 0)                    { &list_updates; exit;}
    if ($opt_u > 0)                    { &root_only unless &sandbox_mode; &update_files; exit; }
    if ($opt_b > 0)                    { &list_backups; exit; }
    if ($opt_r > 0)                    { &root_only unless &sandbox_mode; &restore_backups($opt_r); exit; }
    if ($opt_disable_portage_hook > 0) { &root_only; &disable_portage_hook; exit; }
    if ($opt_disable_paludis_hook > 0) { &root_only; &disable_paludis_hook; exit; }
    if ($opt_optimize_backups > 0)     { &root_only unless &sandbox_mode; &optimize_backups; exit; }
# Show help screen when invalid options are used...
    if ((@ARGV > 1) || ($opt_t !~ /^novalue$/)) { &print_usage; exit; }
# Exit when there's nothing left to do...
    print "$progname: missing valid options\n";
    print "Try `$progname --help' for more information.\n";
    exit;

######################################################################################################################
#                                     ALL SUBROUTINES BELOW THIS LINE                                                #
######################################################################################################################

sub state_is { #ARGS# ($state, @codes)
    my ($s, @codes) = @_;
    for my $code (@codes) {
        return 1 if $s eq $code;
    }
    return 0;
}

sub state_label { #ARGS# ($state)
    my ($s) = @_;
    return $STATE_DESC{$s} // 'Unknown state    ';
}

sub strip{ #ARGS# ("linefromfile")
    my $string = $_[0];
    $string =~ s/'//g;     # strip all single-quotes
    $string =~ s/"//g;     # strip all double-quotes
    $string =~ s/;//g;     # strip all semi-colons
    $string =~ s/\s//g;    # strip all whitespace
    $string =~ s/#.*//;    # strip comment
# Return a string without whitespace, quotes and comments
    $string;
}

sub readkey{
    if ($opt_d >= 1) { print "\n$tab"."<readkey>\n"; $tab = $tab."    "; print "$tab"; }
    if (&sandbox_mode && !-t STDIN) {
        my $line = <STDIN>;
        if (!defined $line) {
            $key = "s";
            print "$tab"."s\n";
        } else {
            chomp($key = $line);
            print "$tab"."$key\n";
        }
    } else {
        ReadMode 4; # Turn off controls keys
        $key = ReadKey 0;
        ReadMode 0; # Reset tty mode
        print "$tab"."\n";                                       #
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</readkey>\n"; }
}

sub md5sum{ #ARGS# ("file")
    if ($opt_d >= 1) { print "$tab"."<md5sum>\n"; $tab = $tab."    "; }
    if ($opt_d >= 1) { print "$tab"."  md5sum $_[0] $debug | awk '{ print \$1 }' $debug\n"; }
    chomp ($md5sum = `md5sum "$_[0]" $debug | awk '{ print \$1 }' $debug`); # sets global variable $md5sum
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</md5sum>\n"; }
}

sub sandbox_mode { # true when integration tests pass --testsandbox (with --ebuild)
    return ($opt_testsandbox && $opt_ebuild);
}

sub root_only{ #ARGS# ("text")
    if ($opt_d >= 1) { print "$tab"."<root_only>\n"; $tab = $tab."    "; }
    if ($opt_d >= 1) { print "$tab"."  id -u\n"; }
    my $text; if ($_[0]) { $text = $_[0] } else { $text = "You need root privileges for this mode..." }
    my $uid = `id -u`;
    chomp $uid;
    if ($uid != 0) {
        print "$tab".">>> "; print BOLD WHITE "$progname-$version"; print ": $text\n";
        if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</root_only>\n"; }
        exit;
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</root_only>\n"; }
}

sub check_hooks {
    if ($opt_d >= 1) { print "$tab"."<check_hooks>\n"; $tab = $tab."    "; }
# Try to enable the Paludis hook...
    if (-e "/usr/bin/cave") {
        my $hook_dir = dirname($paludis_hook);
        if (!-d "$hook_dir") {
            if ($opt_v >= 1) {
                print "$tab"."Paludis hook directory $hook_dir not found (Paludis support is best-effort)...\n";
            }
        } else {
            if (!-f "$paludis_hook") {
                &root_only("Can't enable the Paludis hook if you're not root...");
                `cp -pP /usr/lib/cfg-update/cfg-update_indexing $paludis_hook $debug`;
                `chmod +x "$paludis_hook" $debug`;
                if ($opt_d >= 1) { print "$tab"."Created Paludis hook $paludis_hook...\n"; }
            } else {
                if ($opt_d >= 1) { print "$tab"."Paludis hook is already enabled...\n"; }
                `chmod +x "$paludis_hook" $debug`;
            }
        }
    }
# Try to enable the Portage hook...
    if (-e "/usr/bin/emerge") {
        if (-e "$portage_hook") {
            local $ENV{LC_ALL}="C";
            if (`grep '^.*cfg-update.*--index' $portage_hook` =~ /cfg-update/) {
                local $ENV{LC_ALL}="C";
                if (`grep ': cfg-update.*--index' $portage_hook` =~ /cfg-update/) {
                    &root_only("Can't enable the Portage hook if you're not root...");
                    `perl -p -i -e 's/: (cfg-update.*--index)/\$1/;' $portage_hook`;
                    if ($opt_d >= 1) { print "$tab"."Enabled Portage hook in $portage_hook...\n"; }
                } else {
                    if ($opt_d >= 1) { print "$tab"."Portage hook is already enabled...\n"; }
                }
            } else {
                &root_only("Can't add the Portage hook if you're not root...");
                `echo >> $portage_hook`;
                `echo "# This hook is neccesary for automatic updating of the cfg-update index, please do not modify it!" >> $portage_hook`;
                `echo "pre_pkg_setup() {" >> $portage_hook`;
                `echo "	[[ \\\$ROOT = / ]] && cfg-update --index" >> $portage_hook`;
                `echo "}" >> $portage_hook`;
                if ($opt_d >= 1) { print "$tab"."Added Portage hook in $portage_hook...\n"; }
            }
        } else {
            &root_only("Can't create the Portage hook if you're not root...");
            `echo "# This hook is neccesary for automatic updating of the cfg-update index, please do not modify it!" >> $portage_hook`;
            `echo "pre_pkg_setup() {" >> $portage_hook`;
            `echo "	[[ \\\$ROOT = / ]] && cfg-update --index" >> $portage_hook`;
            `echo "}" >> $portage_hook`;
            if ($opt_d >= 1) { print "$tab"."Created Portage hook in $portage_hook...\n"; }
        }
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</check_hooks>\n"; }
}

sub check_index{ #RUNMODE# -i, --index
    if ($opt_d >= 1) { print "$tab"."<check_index>\n"; $tab = $tab."    "; }
# Get the first line from the index, it contains the name of the package manager and timestamp of last installed package...
    if ($opt_d >= 1) { print "$tab"."  head -n1 $index_file\n"; }
    my $first_line = `head -n1 $index_file $debug`;
    chomp $first_line;
# Get the timestamp of the last installed package from index_file, needed to determine if index is up-to-date...
    my $index_pkg_manager;
    $_ = $first_line; /:/; $index_pkg_manager = $`; # take string to left of ":", that should be the pkg_manager
    $_ = $first_line; /:/; $index_pkg = $';         # take string to right of ":", that should be the timestamp of the last installed package
    local $ENV{LC_ALL}="C";
    if (!-e $install_log) {
        print "$tab"."* $install_log not found...\n";
        print "$tab"."  This file is needed to determine if the indexing can be skipped...\n";
        $log_pkg = "$install_log not found!";
    } else {
# Get the timestamp of the last installed package from install_log, needed to determine if index is up-to-date...
        if ($opt_d >= 1) { print "$tab"."  tac $install_log | sed -n '/$find_string/{p;q;}'\n"; }
        $log_pkg = `tac $install_log | sed -n '/$find_string/{p;q;}'`;
        chomp $log_pkg;
        $_ = $log_pkg; /:/; $log_pkg = $`;     # take the timestamp to the left of first : character
    }
    if ($opt_d >= 1) { print "$tab"."  Last installed package according to logfile : $log_pkg\n"; }
# Print status of index when -d, --debug is used...
    if ($opt_d >= 1) { print "$tab"."  Last installed package according to index   : $index_pkg\n"; }
    if (($opt_d >= 1) && ($log_pkg !~ $index_pkg)) { print "$tab"."  Checksum-index needs to be updated...\n"; }
    if (($opt_d >= 1) && ($log_pkg =~ $index_pkg)) { print "$tab"."  Checksum-index is up-to-date...\n"; }
# If last installed package according to index does not match last installed package according to log, then update the index...
    if (($log_pkg !~ $index_pkg) || ($opt_f > 0)) {
        &find_updates;
        &build_index;
    } else {
        print "$tab".">>> "; print BOLD WHITE "$progname-$version"; print ": Checksum index is up-to-date ...\n";
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</check_index>\n"; }
}

sub build_index{
    if ($opt_d >= 1) { print "\n$tab"."<build_index>\n"; $tab = $tab."    "; }
    if ((@list == 0) || ($opt_f > 0)) {
        print "$tab".">>> "; print BOLD WHITE "$progname-$version"; print ": Creating checksum index...\n";
        open(FILE, ">$index_file") || die "$tab"."  Can't open $index_file\n";
        if ((!-e $install_log) || ($opt_f > 0)) {
            print FILE "$pkg_manager:0000000000\n";
        } else {
            print FILE "$pkg_manager:$log_pkg\n";
        }
        close(FILE);
        local $ENV{LC_ALL}="C";
        if ($opt_d >= 1) { print "$tab"."echo $pkg_db/*/*/CONTENTS | xargs grep \"^obj \" | cut -d\" \" -f2-3 $debug >> $index_file`\n"; }
        for (my $i = 0; $i < @dir; ++$i) {
# The following command needs to use echo and xargs because cat and grep both have limitations on the number of files they can handle
            `echo $pkg_db/*/*/CONTENTS $debug | xargs grep "^obj $dir[$i]" | cut -d" " -f2-3 $debug >> $index_file`; # checksum-index of all protected files, needed for automatic updating of unmodified configfiles
        }
    } else {
        print "$tab".">>> "; print BOLD WHITE "$progname-$version"; print ": Skipping checksum index updating...\n";
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</build_index>\n"; }
}

sub launched_tool_needs_gui { #ARGS# ($tool_path)
    return ($_[0] =~ /\/xxdiff$|^xxdiff$|\/kdiff3$|^kdiff3$|\/meld$|^meld$|\/kompare$|^kompare$|\/tkdiff$|^tkdiff$|\/gvimdiff$|^gvimdiff$/);
}

sub check_gui{
    if ($opt_d >= 1) { print "$tab"."<check_gui>\n"; $tab = $tab."    "; }
    if ($opt_d >= 1) { print "$tab"."merge_tool       = $merge_tool\n"; }
    if ($opt_d >= 1) { print "$tab"."merge_tool_name  = $merge_tool_name\n"; }
    if ($opt_d >= 1) { print "$tab"."tool_needs_gui   = $tool_needs_gui\n"; }
    if ($opt_d >= 1) { print "$tab"."xhost &>/dev/null; echo $?\n"; }
    if ((`xhost &>/dev/null; echo \$?` =~ "1") && ($tool_needs_gui =~ "yes") && ($opt_a == 0) && ($opt_p == 0)) {
        print BOLD YELLOW "$tab"."* GUI not available, unable to run the mergetool set in $settings!\n";
        print BOLD YELLOW "$tab"."  Run $progname from within an X-terminal if you want to use a GUI mergetool.\n";
        print "$tab"."  If you are getting this from within an X-terminal, you should try running\n";
        print "$tab"."  \"xhost +localhost\" as the user who started the X-server.\n";
        print "$tab"."  If you use this script on a system without an X-server you should permanently\n";
        print "$tab"."  set the MERGETOOL variable in $settings to vimdiff or sdiff.\n";
        print "$tab"."  I recommend the more advanced vimdiff on systems without an X-server.\n";
        print "$tab"."  See \"man cfg-update\" for merge tool usage instructions.\n\n";
        exit;
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</check_gui>\n"; }
}

sub check_tool{
    if ($opt_d >= 1) { print "$tab"."<check_tool>\n"; $tab = $tab."    "; }
# Check if tool exists...
    if ((!-e $merge_tool) && (!-e "/usr/bin/$merge_tool_name")) {
        print "\n";
        print "$tab"."**********************************************************************\n";
        print "$tab"."Mergetool \"$merge_tool\" not found...\n";
        print "$tab"."The recommended merge tool for $progname is meld, a nice GUI tool.\n\n";
        print BOLD YELLOW "$tab"."Please install dev-util/meld or change the MERGE_TOOL variable to\n";
        print BOLD YELLOW "$tab"."your own favorite tool. See $settings for more details!\n";
        print "$tab"."**********************************************************************\n";
        print "\n";
        print BOLD BLUE "$tab"."Switching to \"sdiff\" for now...\n";
        print "\n";
        $merge_tool = "/usr/bin/sdiff";
        $merge_tool_name = basename($merge_tool);
        if (!-e $merge_tool) {
            print "$tab"."Mergetool \"$merge_tool\" also not found...\n";
            print "$tab"."I give up!\n\n";
            exit;
        }
    } else {
        if ($opt_d >= 1) { print "$tab"."merge_tool                        = $merge_tool\n"; }
        if ($opt_d >= 1) { print "$tab"."merge_tool_name                   = $merge_tool_name\n"; }
    }
# Check if tool can handle manual 2-way merges...
    if ($merge_tool =~ /\/kdiff3$|^kdiff3$|\/xxdiff$|^xxdiff$|\/sdiff$|^sdiff$|\/imediff$|^imediff$|\/meld$|^meld$|\/kompare$|^kompare$|\/tkdiff$|^tkdiff$|\/vimdiff$|^vimdiff$|\/gvimdiff$|^gvimdiff$/) {
        $tool_supports_2way = "yes";
        if ($opt_d >= 1) { print "$tab"."tool_supports_2way                = $tool_supports_2way\n"; }
    } else {
        $tool_supports_2way = "no";
        if ($opt_d >= 1) { print "$tab"."tool_supports_2way                = $tool_supports_2way\n"; }
        $enable_stage4 = "no";
        if ($opt_d >= 1) { print "$tab"."stage4 disabled...\n"; }
    }
# Check if tool can handle manual 3-way merges...
    if ($merge_tool =~ /\/xxdiff$|^xxdiff$|\/kdiff3$|^kdiff3$|\/meld$|^meld$|\/tkdiff$|^tkdiff$|\/imediff$|^imediff$/) {
        $tool_supports_3way = "yes";
        if ($opt_d >= 1) { print "$tab"."tool_supports_3way                = $tool_supports_3way\n"; }
    } else {
        $tool_supports_3way = "no";
        if ($opt_d >= 1) { print "$tab"."tool_supports_3way                = $tool_supports_3way\n"; }
        $enable_stage3 = "no";
        if ($opt_d >= 1) { print "$tab"."stage3 disabled...\n"; }
    }
# Check if tool saves .merge file when aborted...
    if ($merge_tool_name =~ /\/kdiff3$|^kdiff3$|\/xxdiff$|^xxdiff$|\/imediff$|^imediff$|\/meld$|^meld$|\/tkdiff$|^tkdiff$/) {
        $tool_saves_mergefile_when_aborted = "yes";
        if ($opt_d >= 1) { print "$tab"."tool_saves_mergefile_when_aborted = $tool_saves_mergefile_when_aborted\n"; }
    } else {
        $tool_saves_mergefile_when_aborted = "no";
        if ($opt_d >= 1) { print "$tab"."tool_saves_mergefile_when_aborted = $tool_saves_mergefile_when_aborted\n"; }
    }
# Check if tool needs the GUI...
    if (&launched_tool_needs_gui($merge_tool_name)) {
        $tool_needs_gui = "yes";
        if ($opt_d >= 1) { print "$tab"."tool_needs_gui                    = $tool_needs_gui\n"; }
    } else {
        $tool_needs_gui = "no";
        if ($opt_d >= 1) { print "$tab"."tool_needs_gui                    = $tool_needs_gui\n"; }
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</check_tool>\n"; }
}

sub launch_tool{ #ARGS# ("pretend|execute","mergetool")
    if ($opt_d >= 1) { print "$tab"."<launch_tool>\n"; $tab = $tab."    "; }
    if (($_[0] =~ /execute/) && &launched_tool_needs_gui($_[1])) { &check_gui; }
    chomp (my $screenwidth = `stty size | awk '{ print \$2 }' $debug`); # determine width of current screen or window
    $cmd = "$_[1] $path_live $path_new $debug";                                # default command string for basic functionality with unsupported tools
    if (($_[1] =~ /\/xxdiff$|^xxdiff$/     ) && ($threeway_update =~ "yes")) { $cmd = "$_[1] $xxdiff_style --resource \'Show.PaneMergedView:true Show.Toolbar:true\' -m $path_live $path_backup_new $path_new -M $path_merged $debug"; }
    if (($_[1] =~ /\/xxdiff$|^xxdiff$/     ) && ($threeway_update =~ "no" )) { $cmd = "$_[1] $xxdiff_style --resource \'Show.PaneMergedView:true Show.Toolbar:true\' $path_live $path_new -M $path_merged $debug"; }
    if (($_[1] =~ /\/kdiff3$|^kdiff3$/     ) && ($threeway_update =~ "yes")) { $cmd = "$_[1] -m $path_live $path_new -b $path_backup_new -o $path_merged $debug"; }
    if (($_[1] =~ /\/kdiff3$|^kdiff3$/     ) && ($threeway_update =~ "no" )) { $cmd = "$_[1] $path_live $path_new -o $path_merged $debug"; }
    if  ($_[1] =~ /\/kompare$|^kompare$/   )                                 { $cmd = "$_[1] $path_new $path_live $debug"; }
    if (($_[1] =~ /\/meld$|^meld$/         ) && ($threeway_update =~ "yes")) { $cmd = "$_[1] $path_live $path_backup_new $path_new $debug"; }
    if (($_[1] =~ /\/meld$|^meld$/         ) && ($threeway_update =~ "no" )) { $cmd = "$_[1] $path_live $path_new $debug"; }
    if (($_[1] =~ /\/tkdiff$|^tkdiff$/     ) && ($threeway_update =~ "yes")) { $cmd = "$_[1] $path_live $path_new -a $path_backup_new -o $path_merged $debug"; }
    if (($_[1] =~ /\/tkdiff$|^tkdiff$/     ) && ($threeway_update =~ "no" )) { $cmd = "$_[1] $path_live $path_new -o $path_merged $debug"; }
    if  ($_[1] =~ /\/vimdiff$|^vimdiff$/   )                                 { $cmd = "$_[1] -c 'saveas $path_live' -c next -c 'setlocal nomodifiable readonly' -c prev $path_live $path_new --nofork $debug"; }
    if  ($_[1] =~ /\/gvimdiff$|^gvimdiff$/ )                                 { $cmd = "$_[1] -c 'saveas $path_live' -c next -c 'setlocal nomodifiable readonly' -c prev $path_live $path_new --nofork 2>\&1>/dev/null"; }
    if (($_[1] =~ /\/imediff$|^imediff$/   ) && ($threeway_update =~ "yes")) { $cmd = "$_[1] -a -o $path_merged $path_live $path_backup_new $path_new"; }
    if (($_[1] =~ /\/imediff$|^imediff$/   ) && ($threeway_update =~ "no" )) { $cmd = "$_[1] -a -o $path_merged $path_live $path_new"; }
    if  ($_[1] =~ /\/sdiff$|^sdiff$/       )                                 { $cmd = "$_[1] -w $screenwidth -d -o $path_merged $path_live $path_new"; }
    if  ($_[1] =~ /\/diff3$|^diff3$/       )                                 { $cmd = "$_[1] -m $path_new $path_backup_new $path_live > $path_merged $debug"; } # by specifying $path_live as the third file, the current settings will will be placed lower in the merged file. this may prevent trouble in case of unsolved merge conflicts...
    if  ($_[1] =~ /\/diff$|^diff$/         )                                 { $cmd = "$_[1] -W $screenwidth $path_live $path_new $debug"; } # viewing only...
    if  ($_[0] =~ /execute/) {
        if ($opt_d >=1) { print "$tab"."  $cmd\n"; }
        system("$cmd");                                                 # launch the tool
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</launch_tool>\n"; }
}

sub find_protected_dirs{
    if ($opt_d >= 1) { print "$tab"."<find_protected_dirs>\n"; $tab = $tab."    "; }
    my %seen;
    my $i;
    @dir = ();
    if ($pkg_manager =~ /^portage$/i) {
        $_ = "";                                                          # set variable to prevent warnings about m// and s/// on undefined values
        if ($opt_d >= 1) { print "$tab"."  portageq config_protect $debug\n"; }
        $_ = `portageq config_protect $debug`;                            # query portage for the protected dirs
        s/\"//g;                                                          # strip " chars
        $i = 0;
        until ($_ =~ /^$/) {
            /\/\S+/;
            if (-e $&) { push(@dir, $&); }                                # ignore non-existant directories but push existing dirs into array
            $_ = $';
            $i++;
        }
        my @sdir = sort {lc($a) cmp lc($b)} @dir;                         # case-insensitive alphabetical sort from @dir to @sdir
        %seen = ();
        @dir = grep { ! $seen{$_} ++ } @sdir;                             # move unique elements from @sdir back to @dir (removes doubles)
    }
    if ($pkg_manager =~ /^paludis$/i) {
        open(FILE, "$pkg_db/.cache/all_CONFIG_PROTECT") || die "  Can't open file: $pkg_db/.cache/all_CONFIG_PROTECT!\n";
        while (my $line = <FILE>) {
            chomp $line;
            if (-e $line) { push(@dir, $line); }                          # ignore non-existant directories but push existing dirs into array
        }
        close(FILE);
    }
    if (@dir == 0) { print "$tab"."No protected directories found...\n"; }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</find_protected_dirs>\n"; }
}

sub find_masked_dirs{
    if ($opt_d >= 1) { print "$tab"."<find_masked_dirs>\n"; $tab = $tab."    "; }
    my %seen;
    my $i;
    @maskdir = ();
    if ($pkg_manager =~ /^portage$/i) {
        $_ = "";                                                          # set variable to prevent warnings about m// and s/// on undefined values
        if ($opt_d >= 1) { print "$tab"."  portageq config_protect_mask $debug\n"; }
        $_ = `portageq config_protect_mask $debug`;                       # query portage for the masked dirs
        s/\"//g;                                                          # strip " chars
        $i = 0;
        until ($_ =~ /^$/) {
            /\/\S+/;
            if (-e $&) { push(@maskdir, $&); }                            # ignore non-existant directories but push existing dirs into array
            $_ = $';
            $i++;
        }
        my @sdir = sort {lc($a) cmp lc($b)} @maskdir;                     # case-insensitive alphabetical sort from @dir to @sdir
        %seen = ();
        @maskdir = grep { ! $seen{$_} ++ } @sdir;                         # move unique elements from @sdir back to @dir (removes doubles)
    }
    if ($pkg_manager =~ /^paludis$/i) {
        open(FILE, "$pkg_db/.cache/all_CONFIG_PROTECT_MASK") || die "Can't open file: $pkg_db/.cache/all_CONFIG_PROTECT_MASK!\n";
        while (my $line = <FILE>) {
            chomp $line;
            if (-e $line) { push(@maskdir, $line); }
        }
        close(FILE);
        my @sdir = sort {lc($a) cmp lc($b)} @maskdir;
        %seen = ();
        @maskdir = grep { ! $seen{$_} ++ } @sdir;
    }
    if (@maskdir == 0) { print "$tab"."No masked directories found...\n"; }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</find_masked_dirs>\n"; }
}

sub find_updates{
    if ($opt_d >= 1) { print "$tab"."<find_updates>\n"; $tab = $tab."    "; }
    &find_protected_dirs;
    @list = ();
    for (my $i = 0; $i < @dir; $i++) {
        if ($opt_d >= 1) { print "$tab"."  find $dir[$i] -name '$config_new' $debug | sort $debug\n"; }
        push(@list, `find "$dir[$i]" -name '$config_new' $debug | sort $debug`);
    }
    chomp @list;
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</find_updates>\n"; }
}

sub prepare_filenames_for_updating{ #ARGS# ("file")
    if ($opt_d >= 1) { print "$tab"."<prepare_filenames_for_updating>\n"; $tab = $tab."    "; }
    $path = dirname($_[0]);
    $path = $path."/";
    $cfg_basename = basename($_[0]);
    $cfg_basename =~ s/$rm_new//;                          # strips ._cfg0000_ from filename
    $path_live = $path.$cfg_basename;                           # /path/file                  <-- current /etc/foo
    $path_new = $_[0];                                 # /path/._cfg0000_file        <-- current /etc/._cfg0000_foo
    $path_backup_old = $backup_old;
    $path_backup_old =~ s/\*/$cfg_basename/;
    $path_backup_old = $backup_path.$path.$path_backup_old;             # /backup_path/path/._old-cfg_file   <-- previous /etc/foo
    $path_backup_new = $backup_new;
    $path_backup_new =~ s/\*/$cfg_basename/;
    $path_backup_new = $backup_path.$path.$path_backup_new;             # /backup_path/path/._new-cfg_file   <-- previous /etc/._cfg0000_foo
    $path_merged = $merged;
    $path_merged =~ s/\*/$cfg_basename/;
    $path_merged = $path.$path_merged;                          # /path/file.merge            <-- merged result
    $path_temp_old = $temp_old;
    $path_temp_old =~ s/\*/$cfg_basename/;
    $path_temp_old = $backup_path.$path.$path_temp_old;             # /backup_path/path/._temp_old-cfg_file
    $path_temp_new = $temp_new;
    $path_temp_new =~ s/\*/$cfg_basename/;
    $path_temp_new = $backup_path.$path.$path_temp_new;             # /backup_path/path/._temp_new-cfg_file
    if ($opt_d >= 1) {
        print "$tab"."  backup_path = $backup_path\n";
        print "$tab"."  path        = $path\n";
        print "$tab"."  path_live = $path_live\n";
        print "$tab"."  path_new = $path_new\n";
        print "$tab"."  path_backup_old = $path_backup_old\n";
        print "$tab"."  path_backup_new = $path_backup_new\n";
        print "$tab"."  path_merged = $path_merged\n";
        print "$tab"."  path_temp_old = $path_temp_old\n";
        print "$tab"."  path_temp_new = $path_temp_new\n";
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</prepare_filenames_for_updating>\n"; }
}

sub determine_state{ #DEPS# (requires that global variable $path_live contains a filename)
    if ($opt_d >= 1) { print "$tab"."<determine_state>\n"; $tab = $tab."    "; }
    if (-f $index_file) {
        if ($opt_d >= 1) { if (-f $path_live) { print "$tab"."  md5sum $path_live $debug | cut -d\" \" -f1 $debug\n"; }}
        local $ENV{LC_ALL}="C";
        if (-f $path_live) { chomp ($md5sum_file = `md5sum "$path_live" $debug | cut -d" " -f1 $debug`); }
        if ($opt_d >= 1) { print "$tab"."  MD5 checksum of current config file : $md5sum_file\n"; }
        if ($opt_d >= 1) { print "$tab"."  grep \"$path_live \" $index_file $debug | cut -d\" \" -f2 $debug\n"; }
        local $ENV{LC_ALL}="C";
        chomp ($md5sum_index = `grep "$path_live " "$index_file" $debug | cut -d" " -f2 $debug`);
        if ($opt_d >= 1) { print "$tab"."  MD5 checksum in the checksum-index  : $md5sum_index\n"; }
        if ($md5sum_index =~ /.+/) {
            if (length($md5sum_file) && $md5sum_index !~ $md5sum_file) {
                $state = STATE_MF;                                                  # Modified File     - checksum differs from index
                if (-B "$path_live") { $state = STATE_MB; }                             # Modified Binary   - replaced binary, auto-replace not allowed
            } else {
                $state = STATE_UF;                                                  # Unmodified File   - checksum matches index
                if (-B "$path_live") { $state = STATE_UB; }                             # Unmodified Binary - auto-replace allowed
            }
        } else {
            $state = STATE_CF;                                                      # Custom File       - not installed with Portage
            if (-B "$path_live") { $state = STATE_CB; }                                 # Custom Binary
        }
        if ((-l $path_live) && (!-l $path_new)) { $state = STATE_LF; }                     # Link to File
        if ((!-l $path_live) && (-l $path_new)) { $state = STATE_FL; }                     # File to Link
        if ((-l $path_live) && (-l $path_new)) { $state = STATE_LL; }                      # Link to Link
    } else {
        $state = STATE_UNKNOWN;                                                     # No checksum.index on disk
    }
    $vstate = state_label($state);
    if ($opt_d >= 1) { print "$tab"."  State of the current file : $vstate\n"; }
    if (-e $path_backup_new) {
        $has_ancestor = "true";
    } else {
        $has_ancestor = "false";
    }
    if ($opt_d >= 1) { print "$tab"."  Ancestor file available   : $has_ancestor\n"; }
    if (-f "$path_merged") {
        local $ENV{LC_ALL}="C";
        if (`grep \"\^<<<<<<< \\\|\\^\|\|\|\|\|\|\\\|\\^>>>>>>> \\\|\\^=======\$\" "$path_merged" $debug`) {
            $has_merge_conflict = "true";
        } else {
            $has_merge_conflict = "false";
        }
    } else {
        $has_merge_conflict = "false";
    }
    if ($opt_d >= 1) { print "$tab"."  Merge conflict detected   : $has_merge_conflict\n"; }
    if (-x $path_live) {
        $is_executable = "yes";
    } else {
        $is_executable = "no";
    }
    if ($opt_d >= 1) { print "$tab"."  Executable file           : $is_executable\n"; }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</determine_state>\n"; }
}

sub list_updates{ #RUNMODE# -l, --list
    if ($opt_d >= 1) { print "$tab"."<list_updates>\n"; $tab = $tab."    "; }
    print "$tab"."Searching for updates...\n";
    &find_updates;
    if (@list == 0) {
        if ($opt_d >= 1) {
            print "$tab"."No ._cfg0000_* files found...\n"; $tab =~ s/    //; print "$tab"."</list_updates>\n";
            exit;
        } else {
            print "$tab"."No ._cfg0000_* files found...\n";
            exit;
        }
    }
    $num = 0;
    for (my $i = 0; $i < @list; ++$i) {
        &prepare_filenames_for_updating($list[$i]);
        &determine_state;
        if (($enable_stage1 =~ /^yes$|^true$|^on$/i) && (-e $path_live) && state_is($state, STATE_UF, STATE_UB)) {
            push(@stage1_queue, $list[$i]);
            $num++; print "$tab"."$num "; if ($num < 1000) { print " "; } if ($num < 100) { print " "; } if ($num < 10)  { print " "; }
            if ($opt_v >= 1) { print "$tab"."Stage[1]  "; }
            print "$tab"."$vstate  $list[$i]\n";
        } else {
            if (($enable_stage2 =~ /^yes$|^true$|^on$/i) && (-e $path_live) && state_is($state, STATE_MF, STATE_UF) && ($has_ancestor =~ "true") && ($has_merge_conflict =~ "false")) {
                push(@stage2_queue, $list[$i]);
                $num++; print "$tab"."$num "; if ($num < 1000) { print " "; } if ($num < 100) { print " "; } if ($num < 10)  { print " "; }
                if ($opt_v >= 1) { print "$tab"."Stage[2]  "; }
                print "$tab"."$vstate  $list[$i]\n";
            } else {
                if (($enable_stage3 =~ /^yes$|^true$|^on$/i) && (-e $path_live) && state_is($state, STATE_MF, STATE_UF) && ($has_ancestor =~ "true")) {
                    push(@stage3_queue, $list[$i]);
                    $num++; print "$tab"."$num "; if ($num < 1000) { print " "; } if ($num < 100) { print " "; } if ($num < 10)  { print " "; }
                    if ($opt_v >= 1) { print "$tab"."Stage[3]  "; }
                    print "$tab"."$vstate  $list[$i]\n";
                } else {
                    if (($enable_stage4 =~ /^yes$|^true$|^on$/i) && (-e $path_live) && state_is($state, STATE_MF, STATE_UF, STATE_CF)) {
                        push(@stage4_queue, $list[$i]);
                        $num++; print "$tab"."$num "; if ($num < 1000) { print " "; } if ($num < 100) { print " "; } if ($num < 10)  { print " "; }
                        if ($opt_v >= 1) { print "$tab"."Stage[4]  "; }
                        print "$tab"."$vstate  $list[$i]\n";
                    } else {
                        if (($enable_stage5 =~ /^yes$|^true$|^on$/i) && (state_is($state, STATE_MB, STATE_UB, STATE_CB, STATE_LF, STATE_FL, STATE_LL) || (!-e $path_live))) {
                            push(@stage5_queue, $list[$i]);
                            $num++; print "$tab"."$num "; if ($num < 1000) { print " "; } if ($num < 100) { print " "; } if ($num < 10)  { print " "; }
                            if ($opt_v >= 1) { print "$tab"."Stage[5]  "; }
                            print "$tab"."$vstate  $list[$i]\n";
                        }
                    }
                }
            }
        }
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</list_updates>\n"; }
}

sub update_files{ #RUNMODE# -u, --update
    if ($opt_d >= 1) { print "$tab"."<update_files>\n"; $tab = $tab."    "; }
    print "$tab"."Searching for updates...\n\n";
    &find_updates;
    if (@list == 0) { if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_files>\n"; } print "$tab"."\n"; &done; }
    $totalcount = @list;
    if (($opt_v >= 1) || ($opt_d >= 1)) { print BOLD BLUE "$tab"."<< Scheduler >> queueing automatic updates...\n\n"; }
    &schedule_automatic_updates;
    $num = 0;
    if ($opt_m >= 1) {
        print BOLD BLUE "$tab"."<< Stage1 >> disabled with -m or --manual-only flag, skipping...\n\n";
        print BOLD BLUE "$tab"."<< Stage2 >> disabled with -m or --manual-only flag, skipping...\n\n";
    } else {
        if ($opt_p >= 1) { &update_stage1("pretend"); } else { &update_stage1("execute"); }
        if ($opt_p >= 1) { &update_stage2("pretend"); } else { &update_stage2("execute"); }
    }
    if ($opt_a >= 1) {
        print BOLD BLUE "$tab"."<< Stage3 >> disabled with -a or --automatic-only flag, skipping...\n\n";
        print BOLD BLUE "$tab"."<< Stage4 >> disabled with -a or --automatic-only flag, skipping...\n\n";
        print BOLD BLUE "$tab"."<< Stage5 >> disabled with -a or --automatic-only flag, skipping...\n\n";
    } else {
        splice(@list);
        splice(@stage1_queue);
        splice(@stage2_queue);
        splice(@stage3_queue);
        splice(@stage4_queue);
        splice(@stage5_queue);
        &find_updates;
        if (($opt_v >= 1) || ($opt_d >= 1)) { print BOLD BLUE "$tab"."<< Scheduler >> queueing manual updates, including canceled automatic updates...\n\n"; }
        &schedule_manual_updates;
        my $recount = @list;
        $num = ($totalcount - $recount);
        if ($opt_p >= 1) { &update_stage3("pretend"); } else { &update_stage3("execute"); }
        if ($opt_p >= 1) { &update_stage4("pretend"); } else { &update_stage4("execute"); }
        if ($opt_p >= 1) { &update_stage5("pretend"); } else { &update_stage5("execute"); }
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_files>\n"; }
    &done;
}

sub schedule_automatic_updates{
    if ($opt_d >= 1) { print "$tab"."<schedule_automatic_updates>\n"; $tab = $tab."    "; }
    for (my $i = 0; $i < @list; ++$i) {
        &prepare_filenames_for_updating($list[$i]);
        &determine_state;
        if (($enable_stage1 =~ /^yes$|^true$|^on$/i) && (-e $path_live) && state_is($state, STATE_UF, STATE_UB)) {
            push(@stage1_queue, $list[$i]);
        } else {
            if (($enable_stage2 =~ /^yes$|^true$|^on$/i) && (-e $path_live) && state_is($state, STATE_MF, STATE_UF) && ($has_ancestor =~ "true") && ($has_merge_conflict =~ "false")) {
                push(@stage2_queue, $list[$i]);
            }
        }
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</schedule_automatic_updates>\n"; }
}

sub schedule_manual_updates{
    if ($opt_d >= 1) { print "$tab"."<schedule_manual_updates>\n"; $tab = $tab."    "; }
    for (my $i = 0; $i < @list; ++$i) {
        &prepare_filenames_for_updating($list[$i]);
        &determine_state;
        if (($enable_stage3 =~ /^yes$|^true$|^on$/i) && (-e $path_live) && state_is($state, STATE_MF, STATE_UF) && ($has_ancestor =~ "true")) {
            push(@stage3_queue, $list[$i]);
        } else {
            if (($enable_stage4 =~ /^yes$|^true$|^on$/i) && (-e $path_live) && state_is($state, STATE_MF, STATE_UF, STATE_CF)) {
                push(@stage4_queue, $list[$i]);
            } else {
                if (($enable_stage5 =~ /^yes$|^true$|^on$/i) && (state_is($state, STATE_MB, STATE_UB, STATE_CB, STATE_LF, STATE_FL, STATE_LL) || (!-e $path_live))) {
                    push(@stage5_queue, $list[$i]);
                }
            }
        }
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</schedule_manual_updates>\n"; }
}

sub make_temp_backups{ #ARGS# ("pretend|execute")
    if ($opt_d >= 1) { print "$tab"."<make_temp_backups>\n"; $tab = $tab."    "; }
    if ($enable_backups =~ /^yes$|^true$|^on$/i) {
        if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  cp -pP \"$path_live\" \"$path_temp_old\"\n"; }
        if ($_[0] =~ /execute/) {
            $path = dirname($path_temp_old);
            if (!-d $path) {
                if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  mkdir -p \"$path\"\n"; }
                `mkdir -p "$path"`;
            }
            `cp -pP "$path_live" "$path_temp_old" $debug`;
        }
        if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  cp -pP \"$path_new\" \"$path_temp_new\"\n"; }
        if ($_[0] =~ /execute/) {
            $path = dirname($path_temp_new);
            if (!-d $path) {
                if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  mkdir -p \"$path\"\n"; }
                `mkdir -p "$path"`;
            }
            `cp -pP "$path_new" "$path_temp_new" $debug`;
        }
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</make_temp_backups>\n"; }
}

# Look up Portage VDB CONTENTS MD5 for the live config path (issue #66).
# Uses the package with the highest BUILD_TIME (last install); falls back to
# CONTENTS mtime when BUILD_TIME is missing. Does not read checksum.index.
# Returns ($status, $md5_or_reason): status is "ok" or "unavailable".
# All inputs via parameters — no package globals.
sub lookup_contents_md5_for_live { #ARGS# ($path_live, $pkg_manager, $pkg_db)
    my ($path_live, $pkg_manager, $pkg_db) = @_;
    if (!defined $path_live || $path_live eq "") {
        return ("unavailable", "empty path");
    }
    if (!defined $pkg_manager || $pkg_manager !~ /^portage$/i) {
        return ("unavailable", "not Portage");
    }
    if (!defined $pkg_db || $pkg_db eq "" || !-d $pkg_db) {
        return ("unavailable", "no pkg_db");
    }

    my $best_md5;
    my $best_time = -1;

    opendir(my $cat_dh, $pkg_db) or return ("unavailable", "cannot open pkg_db");
    while (defined(my $category = readdir($cat_dh))) {
        next if $category eq "." || $category eq "..";
        my $cat_path = "$pkg_db/$category";
        next unless -d $cat_path;
        opendir(my $pkg_dh, $cat_path) or next;
        while (defined(my $pkg = readdir($pkg_dh))) {
            next if $pkg eq "." || $pkg eq "..";
            my $pkg_path = "$cat_path/$pkg";
            my $contents = "$pkg_path/CONTENTS";
            next unless -f $contents;

            my $install_time = 0;
            if (open(my $bt, "<", "$pkg_path/BUILD_TIME")) {
                my $line = <$bt>;
                close($bt);
                if (defined $line && $line =~ /^\s*(\d+)\s*$/) {
                    $install_time = $1 + 0;
                }
            }
            if ($install_time <= 0) {
                $install_time = (stat($contents))[9] // 0;
            }

            open(my $fh, "<", $contents) or next;
            while (defined(my $line = <$fh>)) {
                # Portage: obj <path> <md5> <mtime>
                if ($line =~ /^obj\s+(\S+)\s+([0-9a-fA-F]{32})\b/) {
                    my ($obj_path, $md5) = ($1, lc($2));
                    if ($obj_path eq $path_live && $install_time >= $best_time) {
                        $best_time = $install_time;
                        $best_md5 = $md5;
                    }
                }
            }
            close($fh);
        }
        closedir($pkg_dh);
    }
    closedir($cat_dh);

    if (!defined $best_md5) {
        return ("unavailable", "no CONTENTS match");
    }
    return ("ok", $best_md5);
}

# Promote staged marker snapshot ($path_temp_new) to permanent Stage 2 ancestor
# ($path_backup_new) only when it still matches Portage CONTENTS for $path_live
# (issue #66). On mismatch: warn and skip promote. On missing VDB: fail open.
# Returns 1 if promoted (or would under pretend), 0 if skipped for mismatch.
# All inputs via parameters — no package globals. Caller still removes temp_new.
sub promote_backup_new { #ARGS# ($mode, $path_temp_new, $path_backup_new, $path_live, $pkg_manager, $pkg_db, $enable_backups, $indent)
    my ($mode, $path_temp_new, $path_backup_new, $path_live,
        $pkg_manager, $pkg_db, $enable_backups, $indent) = @_;
    $indent = "" if !defined $indent;
    $mode = "" if !defined $mode;

    if (!defined $enable_backups || $enable_backups !~ /^yes$|^true$|^on$/i) {
        return 1;
    }
    if (!defined $path_temp_new || $path_temp_new eq "" || !-e $path_temp_new) {
        return 1;
    }
    if (!defined $path_backup_new || $path_backup_new eq "") {
        return 1;
    }

    my $marker_md5 = "";
    {
        my $out = `md5sum "$path_temp_new" 2>/dev/null`;
        if (defined $out && $out =~ /^([0-9a-fA-F]{32})\b/) {
            $marker_md5 = lc($1);
        }
    }

    my ($status, $detail) = lookup_contents_md5_for_live($path_live, $pkg_manager, $pkg_db);

    if ($status eq "ok") {
        my $contents_md5 = $detail;
        if ($marker_md5 ne "" && $marker_md5 eq $contents_md5) {
            if ($mode =~ /execute/) {
                `cp -pP "$path_temp_new" "$path_backup_new" 2>/dev/null`;
            }
            return 1;
        }
        print BOLD YELLOW "$indent"."* Warning: Portage marker does not match CONTENTS MD5 for $path_live\n";
        print "$indent"."  marker MD5=$marker_md5 CONTENTS MD5=$contents_md5\n";
        print BOLD YELLOW "$indent"."* Not promoting tampered marker as Stage 2 ancestor (._new-cfg_*)\n";
        return 0;
    }

    # unavailable: fail open (maintain prior promote behavior)
    print BOLD YELLOW "$indent"."* Warning: cannot validate marker against Portage CONTENTS ($detail); promoting ancestor as usual\n";
    if ($mode =~ /execute/) {
        `cp -pP "$path_temp_new" "$path_backup_new" 2>/dev/null`;
    }
    return 1;
}

# Disposable /tmp view of one non-live merge input so accidental tool saves cannot
# corrupt permanent backups or Portage markers (issue #65; Stage 4: issue #68).
# Orthogonal to make_temp_backups (which stages permanent backup promotion).
# $role is a filename tag only (e.g. "ancestor", "new"). Returns view path or "".
sub make_merge_view_temp { #ARGS# ($src_path, $role, $basename)
    if ($opt_d >= 1) { print "$tab"."<make_merge_view_temp>\n"; $tab = $tab."    "; }
    my ($src, $role, $basename) = @_;
    my $view = "";
    if (!defined $src || $src eq "" || !-e $src) {
        if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</make_merge_view_temp>\n"; }
        return $view;
    }
    my $tmpdir = $ENV{TMPDIR};
    if (!defined $tmpdir || $tmpdir eq "") { $tmpdir = "/tmp"; }
    $tmpdir =~ s|/+$||;
    if (!-d $tmpdir) {
        if ($opt_d >= 1) { print "$tab"."  mkdir -p \"$tmpdir\"\n"; }
        `mkdir -p "$tmpdir" $debug`;
    }
    my $safe_role = defined $role ? $role : "view";
    $safe_role =~ s/[^A-Za-z0-9._-]/_/g;
    if ($safe_role eq "") { $safe_role = "view"; }
    my $safe = defined $basename ? $basename : "";
    $safe =~ s/[^A-Za-z0-9._-]/_/g;
    if ($safe eq "") { $safe = "file"; }
    $view = "$tmpdir/cfg-update-$$-$safe_role-$safe";
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  cp -pP \"$src\" \"$view\"\n"; }
    `cp -pP "$src" "$view" $debug`;
    if (!-e $view) {
        if ($opt_d >= 1) { print "$tab"."  failed to create $view; using real path\n"; }
        $view = "";
    }
    if ($opt_d >= 1) {
        print "$tab"."  path_view = $view\n";
        $tab =~ s/    //; print "$tab"."</make_merge_view_temp>\n";
    }
    return $view;
}

sub cleanup_merge_view_temp { #ARGS# ($view_path)
    if ($opt_d >= 1) { print "$tab"."<cleanup_merge_view_temp>\n"; $tab = $tab."    "; }
    my ($view) = @_;
    if (defined $view && $view ne "" && -e $view) {
        if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$view\"\n"; }
        `rm -f "$view" $debug`;
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</cleanup_merge_view_temp>\n"; }
}

# Unlink zero or more disposable merge-view paths (convenience wrapper).
sub cleanup_merge_view_temps {
    my @views = @_;
    foreach my $view (@views) {
        cleanup_merge_view_temp($view);
    }
}

# Launch merge tool for Stage 3/4 with disposable /tmp views of non-live inputs.
# Stage 3: ancestor ($path_backup_new) + marker ($path_new). Stage 4: marker only
# (ancestor view is a no-op when the backup file does not exist).
# Temporarily rebinds existing $path_backup_new / $path_new for launch_tool only, then
# restores them so complete/cancel paths still use the real marker and ancestor.
sub launch_tool_with_merge_view_temps { #ARGS# ("pretend|execute","mergetool")
    if ($opt_d >= 1) { print "$tab"."<launch_tool_with_merge_view_temps>\n"; $tab = $tab."    "; }
    my ($mode, $tool) = @_;
    my $real_backup_new = $path_backup_new;
    my $real_new = $path_new;
    my $view_ancestor = "";
    my $view_new = "";
    if ($mode =~ /execute/) {
        $view_ancestor = make_merge_view_temp($path_backup_new, "ancestor", $cfg_basename);
        $view_new      = make_merge_view_temp($path_new,        "new",      $cfg_basename);
        if ($view_ancestor ne "") { $path_backup_new = $view_ancestor; }
        if ($view_new ne "") { $path_new = $view_new; }
    }
    launch_tool($mode, $tool);
    $path_backup_new = $real_backup_new;
    $path_new = $real_new;
    if ($mode =~ /execute/) {
        cleanup_merge_view_temps($view_ancestor, $view_new);
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</launch_tool_with_merge_view_temps>\n"; }
}

sub update_stage1{ #ARGS# ("pretend|execute")
    if ($opt_d >= 1) { print "$tab"."<update_stage1>\n"; $tab = $tab."    "; }
    if  ($enable_stage1 !~ /^yes$|^true$|^on$/i) {
        print BOLD BLUE "$tab"."<< Stage1 >> disabled in $settings, skipping...\n\n";
    } elsif (!-e $index_file) {
        print BOLD BLUE "$tab"."<< Stage1 >> $index_file not found, skipping...\n\n";
    } elsif (@stage1_queue == 0) {
        print BOLD BLUE "$tab"."<< Stage1 >> ".@stage1_queue." files in queue for automatic replacing, skipping...\n\n";
    } else {
        print BOLD BLUE "$tab"."<< Stage1 >> ".@stage1_queue." files in queue for automatic replacing, starting...\n\n";
        $threeway_update = "no";
        for (my $i = 0; $i < @stage1_queue; ++$i) {
            &prepare_filenames_for_updating($stage1_queue[$i]);
            &determine_state;
            $num++;
            &make_temp_backups;
            print "$tab"."($num/$totalcount)  $path_live  *$vstate\n";
            if (!-e $path_live) {
                print "$tab"."* $path_live not found, cannot update...\n";
                &update_canceled($_[0]);   # call subroutine with first ARG: "pretend" or "execute"
                if (!-e $path_new) {
                    print "$tab"."* $path_new not found, cannot update...\n";
                    &update_canceled($_[0]);   # call subroutine with first ARG: "pretend" or "execute"
                }
            } else {
                if ($opt_p >= 1) {
                    &update_canceled($_[0]);
                } else {
                    print "$tab"."  This file has not been changed and does not contain custom settings.\n";
                    &update_replace_complete($_[0]);
                }
            }
        }
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_stage1>\n"; }
}

sub update_stage2{ #ARGS# ("pretend|execute")
    if ($opt_d >= 1) { print "$tab"."<update_stage2>\n"; $tab = $tab."    "; }
    if ($enable_stage2 !~ /^yes$|^true$|^on$/i) {
        print BOLD BLUE "$tab"."<< Stage2 >> disabled in $settings, skipping...\n\n";
    } elsif (!-e "/usr/bin/diff3") {
        print BOLD BLUE "$tab"."<< Stage2 >> /usr/bin/diff3 not found, skipping...\n\n";
    } elsif (@stage2_queue == 0) {
        print BOLD BLUE "$tab"."<< Stage2 >> ".@stage2_queue." files in queue for automatic 3-way merging, skipping...\n\n";
    } else {
        print BOLD BLUE "$tab"."<< Stage2 >> ".@stage2_queue." files in queue for automatic 3-way merging, starting...\n\n";
        $threeway_update = "yes";
        for (my $i = 0; $i < @stage2_queue; ++$i) {
            &prepare_filenames_for_updating($stage2_queue[$i]);
            &determine_state;
            $num++;
            &make_temp_backups;
            print "$tab"."($num/$totalcount)  $path_live  *$vstate\n";
            if (!-e $path_new) {
                print "$tab"."* $path_new not found, cannot update...\n";
                &update_canceled($_[0]);
            } elsif (!-e $path_live) {
                print "$tab"."* $path_live not found, cannot update...\n";
                &update_canceled($_[0]);
            } else {
                &launch_tool($_[0],"/usr/bin/diff3");
                if (-e $path_merged) {
                    if ($opt_d >= 1) { print "$tab"."  grep \"\^<<<<<<< \\\|\\^\|\|\|\|\|\|\\\|\\^>>>>>>> \\\|\\^=======\$\" $path_merged $debug\n"; }
                    local $ENV{LC_ALL}="C";
                    if (!`grep \"\^<<<<<<< \\\|\\^\|\|\|\|\|\|\\\|\\^>>>>>>> \\\|\\^=======\$\" "$path_merged" $debug`) {  #  this string is double escaped due to perl handling... original string:    grep "^<<<<<<< \|^||||||\|^>>>>>>> \|^=======$"
                        &update_merge_complete($_[0]);
                    } else {
                        &update_merge_conflict($_[0]);
                    }
                } else {
                    if (($opt_v >= 1) || ($opt_d >=1)) { print "$tab"."  $path_merged not found...\n"; }
                    &update_canceled($_[0]);
                }
            }
        }
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_stage2>\n"; }
}

sub update_stage3{ #ARGS# ("pretend|execute")
    if ($opt_d >= 1) { print "$tab"."<update_stage3>\n"; $tab = $tab."    "; }
    if ($tool_supports_3way =~ "no") {
        print BOLD BLUE "$tab"."<< Stage3 >> $merge_tool_name does not support 3-way merging, skipping...\n\n";
        for (my $i = 0; $i < @stage3_queue; ++$i) { push(@stage4_queue, $stage3_queue[$i]); }  # passing files in stage3 queue to stage4 queue
    } elsif ($enable_stage3 !~ /^yes$|^true$|^on$/i) {
        print BOLD BLUE "$tab"."<< Stage3 >> disabled in $settings, skipping...\n\n";
        for (my $i = 0; $i < @stage3_queue; ++$i) { push(@stage4_queue, $stage3_queue[$i]); }  # passing files in stage3 queue to stage4 queue
    } elsif (@stage3_queue == 0) {
        print BOLD BLUE "$tab"."<< Stage3 >> ".@stage3_queue." files in queue for manual 3-way merging, skipping...\n\n";
    } else {
        print BOLD BLUE "$tab"."<< Stage3 >> ".@stage3_queue." files in queue for manual 3-way merging, starting...\n\n";
        $threeway_update = "yes";
        for (my $i = 0; $i < @stage3_queue; ++$i) {
            &prepare_filenames_for_updating($stage3_queue[$i]);
            &determine_state;
            $num++;
            &make_temp_backups;
            &md5sum("$path_live"); $md5sum_before = $md5sum;
            &md5sum("$path_new"); $md5sum_update = $md5sum;
            print "$tab"."($num/$totalcount)  $path_live  *$vstate\n";
            &show_warning;
            if (!-e $path_new) {
                print "$tab"."* $path_new not found, cannot update...\n";
                &update_canceled($_[0]);
            } elsif (!-e $path_live) {
                print "$tab"."* WARNING: $path_live not found...\n";
                print "$tab"."  Press [1] - to use the ._cfg0000_* file (RECOMMENDED)\n";
                print "$tab"."  Press [2] - to remove the ._cfg0000_* file too (BAD CHOICE)\n";
                print "$tab"."  Press [s] - to skip this update (to investigate first, and try again later)\n";
                print "$tab"."  Press [q] - to quit $progname immediately\n";
                print "$tab"."  How do you want to finish this update? [1|2|s|q] ";
                $key = "";
                until ($key =~ /1|2|s|q/) {
                    if ($_[0] =~ /^execute$/) { &readkey; } else { $key = "s"; print "s\n"; }
                    if ($key =~ /1/) { &update_replace_complete($_[0]); }
                    if ($key =~ /2/) { &update_keep_complete($_[0]); }
                    if ($key =~ /s/) { &update_canceled($_[0]); }
                    if ($key =~ /q/) { &update_canceled($_[0]); if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_stage3>\n"; } &done; }
                    if ($key !~ /1|2|s|q/) { print "* invalid key... try again: " }
                }
            } else {
                if ($merge_tool_name =~ /^diff$/) {
                    print "$tab"."  Press [v] - to view differences between the current file and the ._cfg0000_* file\n";
                    print "$tab"."  Press [s] - to skip this update (to investigate first, and try again later)\n";
                    print "$tab"."  Press [1] - to use the ._cfg0000_* file\n";
                    print "$tab"."  Press [2] - to remove the ._cfg0000_* file\n";
                    print "$tab"."  Press [q] - to quit $progname immediately\n";
                    print "$tab"."  View differences between the current and update file? [v|s|1|2|q] ";
                } else {
                    print "$tab"."  Press [y] - to merge the current file and the ._cfg0000_* file with $merge_tool_name\n";
                    print "$tab"."  Press [s] - to skip this update (to investigate first, and try again later)\n";
                    print "$tab"."  Press [1] - to use the ._cfg0000_* file\n";
                    print "$tab"."  Press [2] - to remove the ._cfg0000_* file\n";
                    print "$tab"."  Press [q] - to quit $progname immediately\n";
                    print "$tab"."  Merge manually with file : $path_new ? [y|s|1|2|q] ";
                }
                $key = "";
                until ($key =~ /y|s|1|2|q/) {
                    if ($_[0] =~ /^execute$/) { &readkey; } else { $key = "s"; print "s\n"; }
                    if ($key =~ /q/) { &update_canceled($_[0]); if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_stage3>\n"; } &done; }
                    if ($key =~ /s/) { &update_canceled($_[0]); }
                    if ($key =~ /1/) { &update_replace_complete($_[0]); $key="s"; }
                    if ($key =~ /2/) { &update_keep_complete($_[0]); $key="s"; }
                    if ($key =~ /v|y/) {
                        &tool_intro($merge_tool_name);
                        # Stage 3 only: pass /tmp copies of ancestor + marker so accidental
                        # saves cannot corrupt permanent backups or Portage markers (issue #65).
                        &launch_tool_with_merge_view_temps($_[0],$merge_tool);
                        if (-e $path_merged) {
                            if ($tool_saves_mergefile_when_aborted =~ "no") {
                                print "$tab"."  Interactive merging completed... (or aborted)\n";
                                print "$tab"."  Press [1] - to replace the current file with the merged result\n";
                                print "$tab"."  Press [2] - to keep the current file and remove the ._cfg0000_* file\n";
                                print "$tab"."  Press [u] - to undo and re-try merging the files later\n";
                                print "$tab"."  Press [s] - to skip this update (to investigate first, and try again later)\n";
                                print "$tab"."  Press [q] - to quit $progname immediately\n";
                                print "$tab"."  Do you want to complete this update? [1|2|u|s|q] ";
                                $key = "";
                                until ($key =~ /1|2|u|s|q/) {
                                    if ($_[0] =~ /^execute$/) { &readkey; } else { $key = "n"; print "n\n"; }
                                    if ($key =~ /s/) { &update_canceled($_[0]); }
                                    if ($key =~ /q/) { &update_canceled($_[0]); if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_stage3>\n"; } &done; }
                                    if ($key =~ /1/) { &update_merge_complete($_[0]); $key="s"; }
                                    if ($key =~ /2/) { &update_keep_complete($_[0]); $key="s"; }
                                    if ($key =~ /u/) { &update_retry($_[0]); $i--; $num--; $key="s"; }
                                    if ($key !~ /1|2|u|s|q/) { print "* invalid key... try again: " }
                                }
                            } else {
                                if ($opt_d >= 1) { print "$tab"."  grep \"\^<<<<<<< \\\|\\^\|\|\|\|\|\|\\\|\\^>>>>>>> \\\|\\^=======\$\" $path_merged $debug\n"; }
                                local $ENV{LC_ALL}="C";
                                if (!`grep \"\^<<<<<<< \\\|\\^\|\|\|\|\|\|\\\|\\^>>>>>>> \\\|\\^=======\$\" "$path_merged" $debug`) {  #  this string is double escaped due to perl handling... original string:    grep "^<<<<<<< \|^||||||\|^>>>>>>> \|^=======$"
                                    &update_merge_complete($_[0]);
                                } else {
                                    &update_canceled($_[0]);   # this happends when stage2 leaves a .merge file with a merge-conflict in it and you cancel the manual update in stage3 too
                                }
                            }
                        } else {
                            print "$tab"."  Merged result file $path_merged not found...\n";
                            &md5sum("$path_live"); $md5sum_after = $md5sum;
                            if ($md5sum_before =~ $md5sum_after) {
                                print "$tab"."  No changes detected...\n";
                                print "$tab"."  Press [1] - to replace the current file with the ._cfg0000_* file\n";
                                print "$tab"."  Press [2] - to keep the current file and remove the ._cfg0000_* file\n";
                                print "$tab"."  Press [u] - to undo and re-try merging the files later\n";
                                print "$tab"."  Press [s] - to skip this update (to investigate first, and try again later)\n";
                                print "$tab"."  Press [q] - to quit $progname immediately\n";
                                print "$tab"."  How do you want to finish this update? [1|2|u|s|q] ";
                                $key = "";
                                until ($key =~ /1|2|u|s|q/) {
                                    if ($_[0] =~ /^execute$/) { &readkey; } else { $key = "s"; print "s\n"; }
                                    if ($key =~ /s/) { &update_canceled($_[0]); }
                                    if ($key =~ /q/) { &update_canceled($_[0]); if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_stage3>\n"; } &done; }
                                    if ($key =~ /1/) { &update_replace_complete($_[0]); $key="s"; }
                                    if ($key =~ /2/) { &update_keep_complete($_[0]); $key="s"; }
                                    if ($key =~ /u/) { &update_retry($_[0]); $i--; $num--; $key="s"; }
                                    if ($key !~ /1|2|u|s|q/) { print "* invalid key... try again: " }
                                }
                            } else {
                                print "$tab"."  Merged result has been saved in $path_live\n";
                                &update_keep_complete($_[0]);
                            }
                        }
                    }
                    if ($key !~ /y|s|q/) { print "* invalid key... try again: " }
                }
            }
        }
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_stage3>\n"; }
}

sub update_stage4{ #ARGS# ("pretend|execute")
    if ($opt_d >= 1) { print "$tab"."<update_stage4>\n"; $tab = $tab."    "; }
    if  ($enable_stage4 !~ /^yes$|^true$|^on$/i) {
        print BOLD BLUE "$tab"."<< Stage4 >> disabled in $settings, skipping...\n\n";
    } elsif (@stage4_queue == 0) {
        print BOLD BLUE "$tab"."<< Stage4 >> ".@stage4_queue." files in queue for manual 2-way merging, skipping...\n\n";
    } else {
        print BOLD BLUE "$tab"."<< Stage4 >> ".@stage4_queue." files in queue for manual 2-way merging, starting...\n\n";
        if ($merge_tool_name =~ /^diff3$/) {
            print "$tab"."* diff3 cannot be used for this stage, changing to sdiff\n\n";
            if (&sandbox_mode && $ENV{CFG_UPDATE_TEST_SANDBOX} && -x "$ENV{CFG_UPDATE_TEST_SANDBOX}/bin/sdiff") {
                $merge_tool = "$ENV{CFG_UPDATE_TEST_SANDBOX}/bin/sdiff";
            } else {
                $merge_tool = "/usr/bin/sdiff";
            }
            $merge_tool_name = basename($merge_tool);
        }
        $threeway_update = "no";
        for (my $i = 0; $i < @stage4_queue; ++$i) {
            &prepare_filenames_for_updating($stage4_queue[$i]);
            &determine_state;
            $num++;
            &make_temp_backups;
            &md5sum("$path_live"); $md5sum_before = $md5sum;
            &md5sum("$path_new"); $md5sum_update = $md5sum;
            print "$tab"."($num/$totalcount)  $path_live  *$vstate\n";
            &show_warning;
            if (!-e $path_new) {
                print "$tab"."* $path_new not found, cannot update...\n";
                &update_canceled($_[0]);
            } elsif (!-e $path_live) {
                print "$tab"."* WARNING: $path_live not found...\n";
                print "$tab"."  Press [1] - to use the ._cfg0000_* file (RECOMMENDED)\n";
                print "$tab"."  Press [2] - to remove the ._cfg0000_* file too (BAD CHOICE)\n";
                print "$tab"."  Press [s] - to skip this update (to investigate first, and try again later)\n";
                print "$tab"."  Press [q] - to quit $progname immediately\n";
                print "$tab"."  How do you want to finish this update? [1|2|s|q] ";
                $key = "";
                until ($key =~ /1|2|s|q/) {
                    if ($_[0] =~ /^execute$/) { &readkey; } else { $key = "s"; print "s\n"; }
                    if ($key =~ /1/) { &update_replace_complete($_[0]); }
                    if ($key =~ /2/) { &update_keep_complete($_[0]); }
                    if ($key =~ /s/) { &update_canceled($_[0]); }
                    if ($key =~ /q/) { &update_canceled($_[0]); if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_stage4>\n"; } &done; }
                    if ($key !~ /1|2|s|q/) { print "* invalid key... try again: " }
                }
            } else {
                if ($merge_tool_name =~ /^diff$/) {
                    print "$tab"."  Press [v] - to view differences between the current and ._cfg0000_* file\n";
                    print "$tab"."  Press [s] - to skip this update (to investigate first, and try again later)\n";
                    print "$tab"."  Press [1] - to use the ._cfg0000_* file\n";
                    print "$tab"."  Press [2] - to remove the ._cfg0000_* file\n";
                    print "$tab"."  Press [q] - to quit $progname immediately\n";
                    print "$tab"."  View differences between the current and update file? [v|s|1|2|q] ";
                } else {
                    print "$tab"."  Press [y] - to merge the current file and the ._cfg0000_* file with $merge_tool_name\n";
                    print "$tab"."  Press [s] - to skip this update (to investigate first, and try again later)\n";
                    print "$tab"."  Press [1] - to use the ._cfg0000_* file\n";
                    print "$tab"."  Press [2] - to remove the ._cfg0000_* file\n";
                    print "$tab"."  Press [q] - to quit $progname immediately\n";
                    print "$tab"."  Merge manually with file : $path_new ? [y|s|1|2|q] ";
                }
                $key = "";
                until ($key =~ /y|s|1|2|q/) {
                    if ($_[0] =~ /^execute$/) { &readkey; } else { $key = "s"; print "s\n"; }
                    if ($key =~ /q/) { &update_canceled($_[0]); if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_stage4>\n"; } &done; }
                    if ($key =~ /s/) { &update_canceled($_[0]); }
                    if ($key =~ /1/) { &update_replace_complete($_[0]); $key="s"; }
                    if ($key =~ /2/) { &update_keep_complete($_[0]); $key="s"; }
                    if ($key =~ /v|y/) {
                        &tool_intro($merge_tool_name);
                        if ($merge_tool_name =~ /^diff$|^sdiff$/) { print "$tab"."$bar2\n"; }
                        &launch_tool_with_merge_view_temps($_[0],$merge_tool);
                        if ($merge_tool_name =~ /^diff$|^sdiff$/) { print "$tab"."$bar2\n"; print "$tab"."  To scroll up and down use [Shift]+[PgUp] and [Shift]+[PgDn]\n"; }
                        if (-e $path_merged) {
                            if ($tool_saves_mergefile_when_aborted =~ "no") {
                                print "$tab"."  Interactive merging completed... (or aborted)\n";
                                print "$tab"."  Press [v] - to view the merged result (to check if merging was successful)\n";
                                print "$tab"."  Press [1] - to replace the current file with the merged result\n";
                                print "$tab"."  Press [2] - to keep the current file and remove the ._cfg0000_* file\n";
                                print "$tab"."  Press [u] - to undo and re-try merging the files later\n";
                                print "$tab"."  Press [s] - to skip this update (to investigate first, and try again later)\n";
                                print "$tab"."  Press [q] - to quit $progname immediately\n";
                                print "$tab"."  Do you want to complete this update? [v|1|2|u|s|q] ";
                                $key = "";
                                until ($key =~ /1|2|u|s|q/) {
                                    if ($_[0] =~ /^execute$/) { &readkey; } else { $key = "n"; print "n\n"; }
                                    if ($key =~ /s/) { &update_canceled($_[0]); }
                                    if ($key =~ /q/) { &update_canceled($_[0]); if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_stage4>\n"; } &done; }
                                    if ($key =~ /v/) {
                                        print "--------------------------------------------------------------------------------\n";
                                        print "$tab"."  $view_tool $path_merged\n";
                                        print "$tab"."  TIP: you can change the viewer to an editor in $settings\n";
                                        print "$tab"."       VIEW_TOOL = \"nano -w\" or VIEW_TOOL = \"vi\"\n";
                                        system("$view_tool $path_merged");
                                        print "--------------------------------------------------------------------------------\n";
                                        print "$tab"."  Press [v] - to view the merged result (to check if merging was successful)\n";
                                        print "$tab"."  Press [1] - to replace the current file with the merged result\n";
                                        print "$tab"."  Press [2] - to keep the current file and remove the ._cfg0000_* file\n";
                                        print "$tab"."  Press [u] - to undo and re-try merging the files later\n";
                                        print "$tab"."  Press [s] - to skip this update (to investigate first, and try again later)\n";
                                        print "$tab"."  Press [q] - to quit $progname immediately\n";
                                        print "$tab"."  Do you want to complete this update? [v|1|2|u|s|q] ";
                                    }
                                    if ($key =~ /1/) { &update_merge_complete($_[0]); $key="s"; }
                                    if ($key =~ /2/) { &update_keep_complete($_[0]); $key="s"; }
                                    if ($key =~ /u/) { &update_retry($_[0]); $i--; $num--; $key="s"; }
                                    if ($key !~ /v|1|2|u|s|q/) { print "* invalid key... try again: " }
                                }
                            } else {
                                &update_merge_complete($_[0]);
                            }
                        } else {
                            print "$tab"."  $path_merged not found...\n";
                            &md5sum("$path_live"); $md5sum_after = $md5sum;
                            if ($md5sum_before =~ $md5sum_after) {
                                print "$tab"."  No changes detected...\n";
                                print "$tab"."  Press [1] - to replace the current file with the ._cfg0000_* file\n";
                                print "$tab"."  Press [2] - to keep the current file and remove the ._cfg0000_* file\n";
                                print "$tab"."  Press [u] - to undo and re-try merging the files later\n";
                                print "$tab"."  Press [s] - to skip this update (to investigate first, and try again later)\n";
                                print "$tab"."  Press [q] - to quit $progname immediately\n";
                                print "$tab"."  How do you want to finish this update? [1|2|u|s|q] ";
                                $key = "";
                                until ($key =~ /1|2|u|s|q/) {
                                    if ($_[0] =~ /^execute$/) { &readkey; } else { $key = "s"; print "s\n"; }
                                    if ($key =~ /s/) { &update_canceled($_[0]); }
                                    if ($key =~ /q/) { &update_canceled($_[0]); if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_stage4>\n"; } &done; }
                                    if ($key =~ /1/) { &update_replace_complete($_[0]); $key="s"; }
                                    if ($key =~ /2/) { &update_keep_complete($_[0]); $key="s"; }
                                    if ($key =~ /u/) { &update_retry($_[0]); $i--; $num--; $key="s"; }
                                    if ($key !~ /1|2|u|s|q/) { print "* invalid key... try again: " }
                                }
                            } else {
                                print "$tab"."  Merged result has been saved in $path_live\n";
                                &update_keep_complete($_[0]);
                            }
                        }
                    }
                    if ($key !~ /y|s|q/) { print "* invalid key... try again: " }
                }
            }
        }
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_stage4>\n"; }
}

sub update_stage5{ #ARGS# ("pretend|execute")
    if ($opt_d >= 1) { print "$tab"."<update_stage5>\n"; $tab = $tab."    "; }
    if  ($enable_stage5 !~ /^yes$|^true$|^on$/i) {
        print BOLD BLUE "$tab"."<< Stage5 >> disabled in $settings, skipping...\n\n";
    } elsif (@stage5_queue == 0) {
        print BOLD BLUE "$tab"."<< Stage5 >> ".@stage5_queue." files in queue for manual updating, skipping...\n\n";
    } else {
        print BOLD BLUE "$tab"."<< Stage5 >> ".@stage5_queue." files in queue for manual updating, starting...\n\n";
        $threeway_update = "no";
        for (my $i = 0; $i < @stage5_queue; ++$i) {
            &prepare_filenames_for_updating($stage5_queue[$i]);
            &determine_state;
            $num++;
            &make_temp_backups;
            print "$tab"."($num/$totalcount)  $path_live  *$vstate\n";
            &show_warning;
            if (!-e $path_new) {
                print "$tab"."* $path_new not found, cannot update...\n";
                &update_canceled($_[0]);
            } elsif (!-e $path_live) {
                print "$tab"."* WARNING: $path_live not found...\n";
                print "$tab"."  Press [1] - to use the ._cfg0000_* file (RECOMMENDED)\n";
                print "$tab"."  Press [2] - to remove the ._cfg0000_* file too (BAD CHOICE)\n";
                print "$tab"."  Press [s] - to skip this update (to investigate first, and try again later)\n";
                print "$tab"."  Press [q] - to quit $progname immediately\n";
                print "$tab"."  How do you want to finish this update? [1|2|s|q] ";
                $key = "";
                until ($key =~ /1|2|s|q/) {
                    if ($_[0] =~ /^execute$/) { &readkey; } else { $key = "s"; print "s\n"; }
                    if ($key =~ /1/) { &update_replace_complete($_[0]); }
                    if ($key =~ /2/) { &update_keep_complete($_[0]); }
                    if ($key =~ /s/) { &update_canceled($_[0]); }
                    if ($key =~ /q/) { &update_canceled($_[0]); if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_stage5>\n"; } &done; }
                    if ($key !~ /1|2|s|q/) { print "* invalid key... try again: " }
                }
            } else {
                if (!-B "$path_live") {
                    print "$tab"."  This update cannot be done with the diff/merge tool...\n";
                    print "$tab"."  Press [v] - to view the differences with diff\n";
                    print "$tab"."  Press [1] - to replace the current file with the ._cfg0000_* file\n";
                    print "$tab"."  Press [2] - to keep the current file and remove the ._cfg0000_* file\n";
                    print "$tab"."  Press [s] - to skip this update (to investigate first, and try again later)\n";
                    print "$tab"."  Press [q] - to quit $progname immediately\n";
                    print "$tab"."  How do you want to finish this update? [v|1|2|s|q] ";
                    $key = "";
                    until ($key =~ /1|2|s|q/) {
                        if ($_[0] =~ /^execute$/) { &readkey; } else { $key = "s"; print "s\n"; }
                        if ($key =~ /v/) {
                            print "--------------------------------------------------------------------------------\n";
                            &launch_tool($_[0],"/usr/bin/diff");
                            print "--------------------------------------------------------------------------------\n";
                            print "$tab"."  Press [v] - to view the differences with diff\n";
                            print "$tab"."  Press [1] - to replace the current file with the ._cfg0000_* file\n";
                            print "$tab"."  Press [2] - to keep the current file and remove the ._cfg0000_* file\n";
                            print "$tab"."  Press [s] - to skip this update (to investigate first, and try again later)\n";
                            print "$tab"."  Press [q] - to quit $progname immediately\n";
                            print "$tab"."  How do you want to finish this update? [v|1|2|s|q] ";
                        }
                        if ($key =~ /1/) { &update_replace_complete($_[0]); }
                        if ($key =~ /2/) { &update_keep_complete($_[0]); }
                        if ($key =~ /s/) { &update_canceled($_[0]); }
                        if ($key =~ /q/) { &update_canceled($_[0]); if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_stage5>\n"; } &done; }
                        if ($key !~ /v|1|2|s|q/) { print "* invalid key... try again: " }
                    }
                } else {
                    print "$tab"."  This update cannot be done with the diff/merge tool...\n";
                    print "$tab"."  Press [1] - to replace the current file with the ._cfg0000_* file\n";
                    print "$tab"."  Press [2] - to keep the current file and remove the ._cfg0000_* file\n";
                    print "$tab"."  Press [s] - to skip this update (to investigate first, and try again later)\n";
                    print "$tab"."  Press [q] - to quit $progname immediately\n";
                    print "$tab"."  How do you want to finish this update? [1|2|s|q] ";
                    $key = "";
                    until ($key =~ /1|2|s|q/) {
                        if ($_[0] =~ /^execute$/) { &readkey; } else { $key = "s"; print "s\n"; }
                        if ($key =~ /1/) { &update_replace_complete($_[0]); }
                        if ($key =~ /2/) { &update_keep_complete($_[0]); }
                        if ($key =~ /s/) { &update_canceled($_[0]); }
                        if ($key =~ /q/) { &update_canceled($_[0]); if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_stage5>\n"; } &done; }
                        if ($key !~ /1|2|s|q/) { print "* invalid key... try again: " }
                    }
                }
            }
        }
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_stage5>\n"; }
}

sub update_retry{ #ARGS# ("pretend|execute")
    if ($opt_d >= 1) { print "$tab"."<update_retry>\n"; $tab = $tab."    "; }
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_merged\"\n"; }
    if ($_[0] =~ /execute/) { `rm -f "$path_merged" $debug`; }
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_temp_old\"\n"; }
    if ($_[0] =~ /execute/) { `rm -f "$path_temp_old" $debug`; }
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_temp_new\"\n"; }
    if ($_[0] =~ /execute/) { `rm -f "$path_temp_new" $debug`; }
    if ($_[0] =~ /pretend/) {
        print BOLD GREEN "$tab"."Skipping update...";
        print " (-p, --pretend flag found)\n\n";
    } else {
        print BOLD RED "$tab"."Update canceled for retry...\n\n";
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_retry>\n"; }
}

sub update_canceled{ #ARGS# ("pretend|execute")
    if ($opt_d >= 1) { print "$tab"."<update_canceled>\n"; $tab = $tab."    "; }
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_merged\"\n"; }
    if ($_[0] =~ /execute/) { `rm -f "$path_merged" $debug`; }
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_temp_old\"\n"; }
    if ($_[0] =~ /execute/) { `rm -f "$path_temp_old" $debug`; }
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_temp_new\"\n"; }
    if ($_[0] =~ /execute/) { `rm -f "$path_temp_new" $debug`; }
    if ($_[0] =~ /pretend/) {
        print BOLD GREEN "$tab"."Skipping update...";
        print " (-p, --pretend flag found)\n\n";
    } else {
        print BOLD RED "$tab"."Update canceled...\n\n";
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_canceled>\n"; }
}

sub update_merge_conflict{ #ARGS# ("pretend|execute")
    if ($opt_d >= 1) { print "$tab"."<update_merge_conflict>\n"; $tab = $tab."    "; }
    print "$tab"."* Merge conflict(s) found, this file will be re-scheduled for manual updating.\n";
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_merged\"\n"; }
    if ($_[0] =~ /execute/) { `rm -f "$path_merged" $debug`; }
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_temp_old\"\n"; }
    if ($_[0] =~ /execute/) { `rm -f "$path_temp_old" $debug`; }
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_temp_new\"\n"; }
    if ($_[0] =~ /execute/) { `rm -f "$path_temp_new" $debug`; }
    print BOLD RED "$tab"."Update canceled...\n\n";
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_merge_conflict>\n"; }
}

sub update_merge_complete{ #ARGS# ("pretend|execute")
    # using copy A B followed by remove A instead of move A B because the move command doesn't handle links properly
    if ($opt_d >= 1) { print "$tab"."<update_merge_complete>\n"; $tab = $tab."    "; }
#    print "$tab"."  Replacing current config file with $path_merged\n";
    print "$tab"."  Replacing it with $path_merged\n";
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  cp -pP \"$path_merged\" \"$path_live\"\n"; }
    if ($_[0] =~ /execute/) { `cp -pP "$path_merged" "$path_live" $debug`; }
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_merged\"\n"; }
    if ($_[0] =~ /execute/) { `rm -f "$path_merged" $debug`; }
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_new\"\n"; }
    if ($_[0] =~ /execute/) { `rm -f "$path_new" $debug`; }
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  cp -pP \"$path_temp_old\" \"$path_backup_old\"\n"; }
    if ($_[0] =~ /execute/) { `cp -pP "$path_temp_old" "$path_backup_old" $debug`; }
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_temp_old\"\n"; }
    if ($_[0] =~ /execute/) { `rm -f "$path_temp_old" $debug`; }
    # Validate marker vs Portage CONTENTS before writing Stage 2 ancestor (issue #66).
    promote_backup_new($_[0], $path_temp_new, $path_backup_new, $path_live,
                       $pkg_manager, $pkg_db, $enable_backups, $tab);
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_temp_new\"\n"; }
    if ($_[0] =~ /execute/) { `rm -f "$path_temp_new" $debug`; }
    if (($opt_v >= 1) || ($opt_d >= 1)) { if ($is_executable =~ "yes") { print "$tab"."  chmod +x $path_live\n"; }}
    if ($_[0] =~ /execute/) { if ($is_executable =~ "yes") { `chmod +x "$path_live" $debug`; }}
    print BOLD GREEN "$tab"."Update complete...\n\n";
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_merge_complete>\n"; }
}

sub update_replace_complete{ #ARGS# ("pretend|execute")
    # using copy A B followed by remove A instead of move A B because the move command doesn't handle links properly
    if ($opt_d >= 1) { print "$tab"."<update_replace_complete>\n"; $tab = $tab."    "; }
    print "$tab"."  Replacing it with $path_new\n";
    if (-l $path_live) {
        if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_live\"\n"; }
        if ($_[0] =~ /execute/) { `rm -f "$path_live" $debug`; } # remove original file first if it's a symlink, otherwise the contents of new file is copied into link target!
    }
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  cp -pP \"$path_new\" \"$path_live\"\n"; }
    if ($_[0] =~ /execute/) { `cp -pP "$path_new" "$path_live" $debug`; }
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_new\"\n"; }
    if ($_[0] =~ /execute/) { `rm -f "$path_new" $debug`; }
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  cp -pP \"$path_temp_old\" \"$path_backup_old\"\n"; }
    if ($_[0] =~ /execute/) { `cp -pP "$path_temp_old" "$path_backup_old" $debug`; }
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_temp_old\"\n"; }
    if ($_[0] =~ /execute/) { `rm -f "$path_temp_old" $debug`; }
    # Validate marker vs Portage CONTENTS before writing Stage 2 ancestor (issue #66).
    promote_backup_new($_[0], $path_temp_new, $path_backup_new, $path_live,
                       $pkg_manager, $pkg_db, $enable_backups, $tab);
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_temp_new\"\n"; }
    if ($_[0] =~ /execute/) { `rm -f "$path_temp_new" $debug`; }
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_merged\" (should not exist!)\n"; }
    if ($_[0] =~ /execute/) { `rm -f "$path_merged" $debug`; }
    if (($opt_v >= 1) || ($opt_d >= 1)) { if ($is_executable =~ "yes") { print "$tab"."  chmod +x $path_live\n"; }}
    if ($_[0] =~ /execute/) { if ($is_executable =~ "yes") { `chmod +x "$path_live" $debug`; }}
    print BOLD GREEN "$tab"."Update complete...\n\n";
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_replace_complete>\n"; }
}

sub update_keep_complete{ #ARGS# ("pretend|execute")
    # using copy A B followed by remove A instead of move A B because the move command doesn't handle links properly
    if ($opt_d >= 1) { print "$tab"."<update_keep_complete>\n"; $tab = $tab."    "; }
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_new\"\n"; }
    if ($_[0] =~ /execute/) { `rm -f "$path_new" $debug`; }
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  cp -pP \"$path_temp_old\" \"$path_backup_old\"\n"; }
    if ($_[0] =~ /execute/) { `cp -pP "$path_temp_old" "$path_backup_old" $debug`; }
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_temp_old\"\n"; }
    if ($_[0] =~ /execute/) { `rm -f "$path_temp_old" $debug`; }
    # Validate marker vs Portage CONTENTS before writing Stage 2 ancestor (issue #66).
    promote_backup_new($_[0], $path_temp_new, $path_backup_new, $path_live,
                       $pkg_manager, $pkg_db, $enable_backups, $tab);
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_temp_new\"\n"; }
    if ($_[0] =~ /execute/) { `rm -f "$path_temp_new" $debug`; }
    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_merged\" (should not exist)\n"; }
    if ($_[0] =~ /execute/) { `rm -f "$path_merged" $debug`; }
    if (($opt_v >= 1) || ($opt_d >= 1)) { if ($is_executable =~ "yes") { print "$tab"."  chmod +x $path_live\n"; }}
    if ($_[0] =~ /execute/) { if ($is_executable =~ "yes") { `chmod +x "$path_live" $debug`; }}
    print BOLD GREEN "$tab"."Update complete...\n\n";
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</update_keep_complete>\n"; }
}

sub done{
    if ($opt_d >= 1) { print "$tab"."<done>\n"; $tab = $tab."    "; }
    &find_updates;
    $totalcount = @list;
    if (@list == 0) {
        print "$tab"."All files have been updated...\n\n";
    } else {
        if ($opt_p == 0) {
            print @list." updates remaining, run cfg-update again until all files are updated!\n\n";
        } else {
            print @list." updates remaining, run cfg-update -u to update your files...\n\n";
        }
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</done>\n"; }
    exit;
}

sub find_backups{
    if ($opt_d >= 1) { print "$tab"."<find_backups>\n"; $tab = $tab."    "; }
    &find_protected_dirs;
    @list = ();
    for (my $i = 0; $i < @dir; ++$i) {
        if ($opt_d >= 1) { print "$tab"."  find $backup_path$dir[$i] -name '$backup_old' $debug\n"; }
        push(@list, `find $backup_path$dir[$i] -name '$backup_old' $debug`);
    }
    chomp @list;
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</find_backups>\n"; }
}

sub prepare_filenames_for_restoring{ #ARGS# ("file")
    if ($opt_d >= 1) { print "$tab"."<prepare_filenames_for_restoring>\n"; $tab = $tab."    "; }
    $path = dirname($_[0]);
    $path =~ s/$backup_path//;
    $path = $path."/";
    $cfg_basename = basename($_[0]);
    $cfg_basename =~ s/$rm_old//;                          # strips ._old-cfg_ from filename
    $path_live = $restore_old;
    $path_live =~ s/\*/$cfg_basename/;
    $path_live = $path.$path_live;                          # /path/file                  <-- live config to restore
    $path_new = $restore_new;
    $path_new =~ s/\*/$cfg_basename/;
    $path_new = $path.$path_new;                          # /path/._cfg0000_file        <-- ._cfg marker to restore
    if (-e $path_new) { $path_new =~ s/0000/----/; }      # or /path/._cfg----_file if a newer update exists
    $path_backup_old = $backup_old;
    $path_backup_old =~ s/\*/$cfg_basename/;
    $path_backup_old = $backup_path.$path.$path_backup_old;             # /backup_path/path/._old-cfg_file
    $path_backup_new = $backup_new;
    $path_backup_new =~ s/\*/$cfg_basename/;
    $path_backup_new = $backup_path.$path.$path_backup_new;             # /backup_path/path/._new-cfg_file
    $path_merged = $merged;
    $path_merged =~ s/\*/$cfg_basename/;
    $path_merged = $path.$path_merged;                          # /path/file.merge
    $path_temp_old = "undefined-filename";
    $path_temp_new = "undefined-filename";
    if ($opt_d >= 1) {
        print "$tab"."  backup_path = $backup_path\n";
        print "$tab"."  path        = $path\n";
        print "$tab"."  path_live = $path_live\n";
        print "$tab"."  path_new = $path_new\n";
        print "$tab"."  path_backup_old = $path_backup_old\n";
        print "$tab"."  path_backup_new = $path_backup_new\n";
        print "$tab"."  path_merged = $path_merged\n";
        print "$tab"."  path_temp_old = $path_temp_old\n";
        print "$tab"."  path_temp_new = $path_temp_new\n";
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</prepare_filenames_for_restoring>\n"; }
}

sub list_backups{ #RUNMODE# -b, --backup
    if ($opt_d >= 1) { print "$tab"."<list_backups>\n"; $tab = $tab."    "; }
    print "$tab"."Searching for backups...\n";
    &find_backups;
    if (@list == 0) { print "$tab"."No ($backup_old) files found...\n"; }
    for (my $i = 0; $i < @list; ++$i) {
        &prepare_filenames_for_restoring($list[$i]);
        $num = $i+1;
        if ($num < 1000) { $num = "$num "; }
        if ($num < 100)  { $num = "$num "; }
        if ($num < 10)   { $num = "$num "; }
        if ($opt_v == 0)  {
            my $date = `ls --full-time "$path_backup_old" | awk '{print \$6}' $debug`;
            chomp $date;
            print "$tab"."$num  $date  $path_live\n";
        }
        if (($opt_v >= 1) || ($opt_d >= 1)) {
            print "$tab"."\n";
            &md5sum("$path_backup_old");
            print "$tab"."$num restore $md5sum $path_backup_old\n";
            &md5sum("$path_live");
            print "$tab"."$num to      $md5sum $path_live\n";
            &md5sum("$path_backup_new");
            print "$tab"."$num restore $md5sum $path_backup_new\n";
            print "$tab"."$num to      $md5sum $path_new\n";
        }
    }
    print "\n";
    print "$tab"."TIP: You can use grep to filter this list on date/dirname/filename:\n";
    print "$tab"."     cfg-update -b | grep \"2005-08\"\n";
    print "$tab"."     cfg-update -b | grep \"/etc/conf.d\"\n";
    print "$tab"."     cfg-update -b | grep \"inittab\"\n";
    print "\n";
    print "$tab"."     Just take the number of the file that you want to restore and\n";
    print "$tab"."     use it in conjunction with the -r, --restore option:\n";
    print "$tab"."     cfg-update -r 77\n";
    print "\n";
    print "$tab"."     Then simply retry updating the restored file with:\n";
    print "$tab"."     cfg-update -m -u\n";
    print "\n";
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</list_backups>\n"; }
}

sub restore_backups{ #RUNMODE# -r, --restore
    if ($opt_d >= 1) { print "$tab"."<restore_backups>\n"; $tab = $tab."    "; }
    if ($opt_p >= 1) { print "$tab"."-p, --pretend not supported for restoring backups!\n"; exit; }
    print "$tab"."Searching for backups...\n";
    &find_backups;
    print "$tab"."\n";
    if (@list == 0) { print "$tab"."No ($backup_old) files found...\n\n"; }
    $totalcount = @list;
    for (my $i = 0; $i < @list; $i++) {
        if ($opt_r != 0) { $i = $opt_r-1; }     # take -r argument [number] => single-file-restore-mode
        &prepare_filenames_for_restoring($list[$i]);
        $num = $i + 1;
        print "$tab"."($num/$totalcount)\n";
        print "$tab"."  $path_live\n";
        print "$tab"."  $path_new\n";
        if ($opt_v >= 1) {
            print "$tab"."  [y]es       restore the backup of the old and new config file\n";
            print "$tab"."  [n]o        skip this file and go to the next backup file\n";
            print "$tab"."  [q]uit      quit immediately\n";
        }
        print "$tab"."  Restore files ? [y|n|q|?] ";
        $key = " ";
        until ($key =~ /y|n/) {
            &readkey;
            if ($key =~ /q/) {
                print BOLD RED "$tab"."Restore canceled...\n";
                print "\n";
                exit;
            }
            if ($key =~ /n/) {
                print BOLD RED "$tab"."Restore canceled...\n";
                print "\n";
            }
            if ($key =~ /\?/) {
                print "$tab"."  [y]es       restore the backup of the old and new config file\n";
                print "$tab"."  [n]o        skip this file and go to the next backup file\n";
                print "$tab"."  [q]uit      quit immediately\n";
                print "$tab"."  Restore files ? [y|n|q|?] ";
            }
            if ($key =~ /y/) {
                # using copy A B followed by remove A instead of move A B because the move command doesn't handle links properly
                print "$tab"."  Restoring both the original and update file so you can retry updating...\n";
                if (-l $path_live) {
                    if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_live\"\n"; }
                    `rm -f "$path_live" $debug`; # remove original file if it's a symlink
                }
                if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  cp -pP \"$path_backup_old\" \"$path_live\"\n"; }
                `cp -pP "$path_backup_old" "$path_live" $debug`;
                if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_backup_old\"\n"; }
                `rm -f "$path_backup_old" $debug`;
                if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  cp -pP \"$path_backup_new\" \"$path_new\"\n"; }
                `cp -pP "$path_backup_new" "$path_new" $debug`;
                if (($opt_v >= 1) || ($opt_d >= 1)) { print "$tab"."  rm -f \"$path_backup_new\"\n"; }
                `rm -f "$path_backup_new" $debug`;
                print BOLD GREEN "$tab"."Restore complete...\n";
                print "\n";
            }
            if ($key !~ /y|n|q|\?/) { print "* invalid key... try again: " }
        }
        if ($opt_r != 0) {        # exit when -n argument not 0 => single-file-rstore-mode
            if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</restore_backups>\n"; }
            exit;
        }
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</restore_backups>\n"; }
}

sub optimize_backups{ #RUNMODE# --optimize-backups
    if ($opt_d >= 1) { print "$tab"."<optimize_backups>\n"; $tab = $tab."    "; }
# Create fake backups of the previous ._cfg0000_ file to maximize chances for automatic updating
    &find_protected_dirs;
    @list = ();
    for (my $i = 0; $i < @dir; ++$i) {
        if ($opt_d >= 1) { print "$tab"."  find $dir[$i] $debug\n"; }
        push(@list, `find $dir[$i] $debug`);
    }
    chomp @list;
    if (@list == 0) { print "$tab"."No protected files found... you should check CONFIG_PROTECT!\n\n"; }
    $totalcount = @list;
    for (my $i = 0; $i < @list; $i++) {
        $num = $i + 1;
        $path = dirname($list[$i]);
        $path = $path."/";
        $cfg_basename = basename($list[$i]);
        $path_live = $path.$cfg_basename;                           # /path/file                  <-- live protected file
        $path_new = $backup_path.$path."._new-cfg_".$cfg_basename; # /backup_path/path/._new-cfg_file
        # OVERLOAD: $path_new is the would-be backup-new path here, not the Portage
        # ._cfg0000_* marker used elsewhere; see issue #42.
        $path_backup_new = "";
        $path_merged = "";
        &determine_state;
        my $percentage = int($num/$totalcount*100);
        if (-e "$path_new") {
            if ($percentage < 100) { print " "; } if ($percentage < 10)  { print " "; }
            print "$tab"."$percentage% - Skip file $path_live\n";
            if ($opt_d >= 1) { print "$tab"."[$state] skip  $path_live (exists in repository)\n"; }
        } else {
            if (state_is($state, STATE_UF, STATE_UB)) {
                $path = dirname($path_new);
                if (!-e "$path") {
                    if ($percentage < 100) { print " "; } if ($percentage < 10)  { print " "; }
                    print "$tab"."$percentage% - Make dir  $path\n";
                    if ($opt_d >= 1) { print "$tab"."mkdir -p $path\n"; }
                    `mkdir -p $path`;
                }
                if ($percentage < 100) { print " "; } if ($percentage < 10)  { print " "; }
                print "$tab"."$percentage% - Make file $path_new\n";
                if ($opt_d >= 1) { print "$tab"."[$state] cp -p $path_live $path_new\n"; }
                `cp -p "$path_live" "$path_new"`;
            } else {
                if ($percentage < 100) { print " "; } if ($percentage < 10)  { print " "; }
                print "$tab"."$percentage% - Skip file $path_live\n";
                if ($opt_d >= 1) { print "$tab"."[$state] skip  $path_live (not unmodified)\n"; }
            }
        }
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</optimize_backups>\n"; }
}

sub disable_portage_hook { #RUNMODE# --disable-portage-hook
    if ($opt_d >= 1) { print "$tab"."<disable_portage_hook>\n"; $tab = $tab."    "; }
    if (-e "$portage_hook") {
        local $ENV{LC_ALL}="C";
        if (`grep '^.*cfg-update.*--index' $portage_hook` =~ /cfg-update/) {
            local $ENV{LC_ALL}="C";
            if (`grep ': cfg-update.*--index' $portage_hook` =~ /cfg-update/) {
                if ($opt_ebuild == 0) { print "$tab"."  Portage hook is already disabled...\n"; }
            } else {
                &root_only("Can't disable the Portage hook if you're not root...");
                `perl -p -i -e 's/cfg-update.*--index/: \$&/;' $portage_hook`;
                if ($opt_ebuild == 0) { print "$tab"."  Disabled Portage hook in $portage_hook...\n"; }
            }
        } else {
            if ($opt_ebuild == 0) { print "$tab"."  No hook found in $portage_hook...\n"; }
        }
    } else {
        if ($opt_ebuild == 0) { print "$tab"."  $portage_hook does not exist, no need to disable the hook...\n"; }
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</disable_portage_hook>\n"; }
}

sub disable_paludis_hook { #RUNMODE# --disable-paludis-hook
    if ($opt_d >= 1) { print "$tab"."<disable_paludis_hook>\n"; $tab = $tab."    "; }
    if (-e "$paludis_hook") {
        if ($opt_d >= 1) { print "$tab"."rm -rf $paludis_hook $debug\n"; }
        `rm -rf $paludis_hook $debug`;
        if ($opt_ebuild == 0) { print "$tab"."Paludis hook $paludis_hook\nDisabled...\n"; }
    } else {
        if ($opt_ebuild == 0) { print "$tab"."Paludis hook $paludis_hook\nDoes not exist, nothing to disable...\n"; }
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</disable_paludis_hook>\n"; }
}

sub tool_intro{ #ARGS# ("mergetoolname")
    if ($opt_d >= 1) { print "$tab"."<tool_intro>\n"; $tab = $tab."    "; }
# 80 chars            ________________________________________________________________________________
    if ($_[0] =~ /^diff$/) {
        print "$tab"."  $_[0] can not be used for manual updating, it only displays differences.\n";
        print "$tab"."  $_[0] can not show the differences between binary files.\n";
        print "$tab"."  When $_[0] is done, $progname will automatically skip the update.\n";
    } elsif ($_[0] =~ /^sdiff$/) {
        print "$tab"."  In $_[0] you select differences from the left or the right file and when\n";
        print "$tab"."  all differences are dealt with, $_[0] exits by itself and the result\n";
        print "$tab"."  will be saved as $merged...\n";
        print "$tab"."  Some usage tips for sdiff:\n";
        print "$tab"."     press [enter] to view all merge/edit options\n";
        print "$tab"."     press [l],[enter] to select the left line(s) from the current file\n";
        print "$tab"."     press [r],[enter] to select the right line(s) from the new file\n";
        print "$tab"."  The right (new-file) side is a temporary copy; accidental edits there\n";
        print "$tab"."  are discarded. Save the merge output only.\n";
        print "$tab"."  When $_[0] is done, $progname will ask if you want to complete or cancel\n";
        print "$tab"."  the update...\n";
    } elsif ($_[0] =~ /^diff3$/) {
        print "$tab"."  $_[0] will try to do a 3-way merge, if a merge-conflict is found $progname\n";
        print "$tab"."  will cancel the update and re-schedule it for manual merging.\n";
        print "$tab"."  If no merge-conflicts are found $progname will automatically\n";
        print "$tab"."  complete the update for you...\n";
    } elsif ($_[0] =~ /^xxdiff$/) {
        print "$tab"."  In $_[0] you select the lines that you want to keep by simply\n";
        print "$tab"."  clicking on the colored lines. They will appear in the merge-pane.\n";
        print "$tab"."  When done, click the M-button to save the result, then exit $_[0]!\n";
        print "$tab"."  The new-file pane is a temporary copy (in 3-way mode the ancestor pane\n";
        print "$tab"."  is too); accidental edits there are discarded. Save the merge output only.\n";
        print "$tab"."  When you exit $_[0], $progname will finish the update.\n";
    } elsif ($_[0] =~ /^kdiff3$/) {
        print "$tab"."  In $_[0] you select the lines that you want to keep.\n";
        print "$tab"."  When done, click the save button and exit $_[0]!\n";
        print "$tab"."  The new-file pane is a temporary copy (in 3-way mode the ancestor pane\n";
        print "$tab"."  is too); accidental edits there are discarded. Save the merge output only.\n";
        print "$tab"."  When you exit $_[0], $progname will finish the update...\n";
    } elsif ($_[0] =~ /^kompare$/) {
        print "$tab"."  In $_[0] you select the lines that you want to keep.\n";
        print "$tab"."  When done, save the merged result over the current config file by\n";
        print "$tab"."  closing $_[0] and clicking the Save button in the popup window!\n";
        print "$tab"."  When you exit $_[0], $progname will finish the update...\n";
    } elsif ($_[0] =~ /^vimdiff$/) {
        print "$tab"."  In $_[0] you can only edit and save the left window (forced by $progname)\n";
        print "$tab"."  so make all changes in the window on the left side!\n";
        print "$tab"."     press [ctrl][w][w] to switch between left and right windows\n";
        print "$tab"."     press [d][o] to obtain the diff from the right window (when in left window)\n";
        print "$tab"."     press [d][p] to put the diff in the left window (when in right window)\n";
        print "$tab"."     press [z][o] to unfold a piece of text without differences\n";
        print "$tab"."     press [i] to go into edit mode (when in left window)\n";
        print "$tab"."     press [esc] return to command mode (when in edit mode)\n";
        print "$tab"."     press [:][q][a][!] to close $_[0] without saving\n";
        print "$tab"."     press [:][w][q][a] to save changes and close $_[0]\n";
        print "$tab"."  The right (new-file) window is a temporary copy; edit the left (live) window only.\n";
        print "$tab"."  When you exit $_[0], $progname will finish the update...\n";
    } elsif ($_[0] =~ /^meld$/) {
        print "$tab"."  In $_[0] you select the lines that you want to keep.\n";
        print "$tab"."  When done, save the merged result over the current configfile by\n";
        print "$tab"."  right-clicking on the left pane and chosing \"Save\"!\n";
        print "$tab"."  The new-file pane is a temporary copy (in 3-way mode the middle ancestor\n";
        print "$tab"."  pane is too); accidental edits there are discarded.\n";
        print "$tab"."  When you exit $_[0], $progname will finish the update...\n";
    } elsif ($_[0] =~ /^tkdiff$/) {
        print "$tab"."  In $_[0] you select the lines that you want to keep.\n";
        print "$tab"."  When done, save the merged result with the \"Save & Exit\" button!\n";
        print "$tab"."  The new-file pane is a temporary copy (in 3-way mode the ancestor pane\n";
        print "$tab"."  is too); accidental edits there are discarded. Save the merge output only.\n";
        print "$tab"."  When you exit $_[0], $progname will finish the update...\n";
    } elsif ($_[0] =~ /^imediff$/) {
        print "$tab"."  In $_[0] you select the lines that you want to keep.\n";
        print "$tab"."  When done, the merged result is written to the *.merge output file.\n";
        print "$tab"."  The new-file input is a temporary copy (in 3-way mode the ancestor input\n";
        print "$tab"."  is too); accidental edits there are discarded.\n";
        print "$tab"."  When you exit $_[0], $progname will finish the update...\n";
    } else {
        print "$tab"."  In $_[0] you select the lines that you want to keep.\n";
        print "$tab"."  When done, try saving the merged result as *.merge\n";
        print "$tab"."  or save the merged result over the current config file.\n";
        print "$tab"."  When you exit $_[0], $progname will finish the update...\n";
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</tool_intro>\n"; }
}

sub show_warning{
    if ($opt_d >= 1) { print "$tab"."<show_warning>\n"; $tab = $tab."    "; }
# 80 chars            ________________________________________________________________________________
    if (state_is($state, STATE_UNKNOWN)) {
        print "$tab"."* YOU SHOULD CHOOSE [y] TO UPDATE MANUALLY! CFG-UPDATE CANNOT\n";
        print "$tab"."* DETERMINE IF IT IS SAFE TO REPLACE THIS FILE WITH THE NEW VERSION.\n";
    }
    if (state_is($state, STATE_MF)) {
        print "$tab"."* YOU ARE ABOUT TO UPDATE A MODIFIED FILE WHICH PROBABLY CONTAINS\n";
        print "$tab"."* CUSTOM SETTINGS. YOU ARE FORCED TO UPDATE MANUALLY!\n";
    }
    if (state_is($state, STATE_MB)) {
        print "$tab"."* YOU ARE ABOUT TO UPDATE A MODIFIED BINARY. YOU SHOULD ASK YOURSELF\n";
        print "$tab"."* WHO CHANGED IT AND WHY? WHAT DOES IT DO? THIS FILE COULD EVEN BE\n";
        print "$tab"."* REPLACED BY A VIRUS OR ROOTKIT! YOU SHOULD INVESTIGATE THIS...\n";
        print "$tab"."* IGNORE THIS WARNING IF YOU RETRY UPDATING AFTER RESTORING A BACKUP!\n";
    }
    if (state_is($state, STATE_UF)) {
        print "$tab"."* YOU CAN SAFELY REPLACE THIS UNMODIFIED FILE WITH THE NEW VERSION.\n";
    }
    if (state_is($state, STATE_UB)) {
        print "$tab"."* YOU CAN SAFELY REPLACE THIS UNMODIFIED BINARY WITH THE NEW VERSION.\n";
    }
    if (state_is($state, STATE_CF)) {
        print "$tab"."* YOU ARE ABOUT TO UPDATE A CUSTOM FILE. YOU SHOULD ASK YOURSELF\n";
        print "$tab"."* WHERE DOES IT COME FROM AND WHAT HAPPENDS WHEN YOU REPLACE IT?\n";
        print "$tab"."* PORTAGE DID NOT INSTALL THE CURRENT FILE, MOST LIKELY IT HAS BEEN\n";
        print "$tab"."* CREATED AFTER INSTALLATION AND IT MAY CONTAIN CUSTOM SETTINGS.\n";
    }
    if (state_is($state, STATE_CB)) {
        print "$tab"."* YOU ARE ABOUT TO UPDATE A CUSTOM BINARY. YOU SHOULD ASK YOURSELF\n";
        print "$tab"."* WHERE DOES IT COME FROM AND WHAT HAPPENDS WHEN YOU REPLACE IT?\n";
        print "$tab"."* PORTAGE DID NOT INSTALL THE CURRENT BINARY.\n";
    }
    if (state_is($state, STATE_LF)) {
        print "$tab"."* OPTIONALLY REPLACE A LINK WITH A FILE. YOU BETTER CHECK BOTH THE\n";
        print "$tab"."* LINK AND THE FILE FIRST. THIS USUALLY MEANS BASELAYOUT CHANGES.\n";
    }
    if (state_is($state, STATE_FL)) {
        print "$tab"."* OPTIONALLY REPLACE A FILE WITH A LINK. YOU BETTER CHECK BOTH THE\n";
        print "$tab"."* FILE AND THE LINK FIRST. THIS USUALLY MEANS BASELAYOUT CHANGES.\n";
    }
    if (state_is($state, STATE_LL)) {
        print "$tab"."* OPTIONALLY REPLACE A LINK WITH A LINK. YOU BETTER CHECK BOTH LINKS\n";
        print "$tab"."* FIRST. THIS USUALLY MEANS BASELAYOUT CHANGES.\n";
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</show_warning>\n"; }
}

sub print_usage{ #RUNMODE# -?, --help (or when no arguments or when unusable arguments are used)
    if ($opt_d >= 1) { print "$tab"."<print_usage>\n"; $tab = $tab."    "; }
    if ($opt_t !~ /^novalue$/) {
        &tool_intro($merge_tool_name);
    } else {
# 80 chars            ________________________________________________________________________________
        print "$tab"."\n";
        print "$tab"."USAGE     $progname [runmode] [options]              (version $version)\n";
        print "$tab"."\n";
        print "$tab"."RUNMODES\n";
        print "$tab"."  -l, --list\n";
        print "$tab"."          List updates (._cfg0000_* files) including modification state\n";
        print "$tab"."          Examples: $progname -l         : list pending updates\n";
        print "$tab"."                    $progname -lv        : list with verbose stage info\n";
        print "$tab"."\n";
        print "$tab"."  -u, --update\n";
        print "$tab"."          Update the protected configuration files\n";
        print "$tab"."          Examples: $progname -u         : update interactively\n";
        print "$tab"."                    $progname -up        : update pretend-only\n";
        print "$tab"."                    $progname -ua        : update automatic-only\n";
        print "$tab"."                    $progname -um        : update manual-only\n";
        print "$tab"."\n";
        print "$tab"."  -b, --backups\n";
        print "$tab"."          List backup files, with numbers for use with the -r option\n";
        print "$tab"."          The backups are stored in $backup_path\n";
        print "$tab"."          Examples: $progname -b         : list all backups\n";
        print "$tab"."\n";
        print "$tab"."  -r [x], --restore [x]\n";
        print "$tab"."          Restore a backup by using a number [x] from the -b output\n";
        print "$tab"."          Examples: $progname -r10       : restore backup 10\n";
        print "$tab"."\n";
        print "$tab"."OPTIONS\n";
        print "$tab"."  -a, --automatic-only\n";
        print "$tab"."          Skips all manual updates (for use with cronjobs)\n";
        print "$tab"."\n";
        print "$tab"."  -m, --manual-only\n";
        print "$tab"."          Skips all automatic updates\n";
        print "$tab"."\n";
        print "$tab"."  -t [tool], --tool [tool]\n";
        print "$tab"."          Set mergetool, overrides the default setting in $settings\n";
        print "$tab"."          GUI tools: meld, kdiff3, xxdiff, tkdiff, gvimdiff\n";
        print "$tab"."          CLI tools: vimdiff, sdiff, imediff\n";
        print "$tab"."\n";
        print "$tab"."  -p, --pretend\n";
        print "$tab"."          Pretend, simulate the update session without changing anything\n";
        print "$tab"."\n";
        print "$tab"."  -v, --verbose\n";
        print "$tab"."          Verbose output, shows all file operations and unhides STDERR messages\n";
        print "$tab"."\n";
        print "$tab"."  -d, --debug\n";
        print "$tab"."          Debugging mode, unhides STDERR messages and shows subroutine tags\n";
        print "$tab"."\n";
        print "$tab"."SPECIAL OPTIONS\n";
        print "$tab"."  -i, --index (--paludis)\n";
        print "$tab"."          Creates or updates the checksum-index if neccesary.\n";
        print "$tab"."          The index is neccesary for determining which files can be updated\n";
        print "$tab"."          automatically. The index needs to be updated before installing new\n";
        print "$tab"."          packages. A hook will be created in $portage_hook so you don't\n";
        print "$tab"."          have to do this manually. When Paludis (cave) is installed, $progname\n";
        print "$tab"."          will add a hook to $paludis_hook (best-effort, lightly tested).\n";
        print "$tab"."          The Portage hook will execute: $progname --index\n";
        print "$tab"."          The Paludis hook will execute: $progname --index --paludis\n";
        print "$tab"."\n";
        print "$tab"."  --optimize-backups\n";
        print "$tab"."          Backups can be used for automatic 3-way merging. This option creates\n";
        print "$tab"."          extra backups to maximize the chances of future automatic updates\n";
        print "$tab"."\n";
        print "$tab"."SETTINGS (from $settings)\n";
        print "$tab"."  Stage1  >>  Automatic replacing     - ";
        if ($enable_stage1 =~ /^yes$|^true$|^on$/i) { print "enabled\n"; } else { print "disabled\n"; }
        print "$tab"."  Stage2  >>  Automatic 3-way merging - ";
        if ($enable_stage2 =~ /^yes$|^true$|^on$/i) { print "enabled\n"; } else { print "disabled\n"; }
        print "$tab"."  Stage3  >>  Manual 3-way merging    - ";
        if ($tool_supports_3way =~ "yes") {
            if ($enable_stage3 =~ /^yes$|^true$|^on$/i) {
                print "enabled\n";
            } else {
                print "disabled\n";
            }
        } else {
            print "disabled (not supported by $merge_tool_name)\n";
        }
        print "$tab"."  Stage4  >>  Manual 2-way merging    - ";
        if ($tool_supports_2way =~ "yes") {
            if ($enable_stage4 =~ /^yes$|^true$|^on$/i) {
                print "enabled\n";
            } else {
                print "disabled\n";
            }
        } else {
            print "disabled (not supported by $merge_tool_name)\n";
        }
        print "$tab"."  Stage5  >>  Manual replacing        - ";
        if ($enable_stage5 =~ /^yes$|^true$|^on$/i) { print "enabled\n"; } else { print "disabled\n"; }
        print "$tab"."\n";
        print "$tab"."USAGE INFORMATION\n";
        print "$tab"."  Start (as root) with \"$progname -l\" to list all the ._cfg0000_* files,\n";
        print "$tab"."  followed by \"$progname -u\" to update the current config files one by one.\n";
        print "$tab"."  You can also use the --pretend mode with \"$progname -u -p\" to see how\n";
        print "$tab"."  $progname will handle the files without actually updating them.\n";
        print "$tab"."  Take a look in the manpage for more usage examples...\n";
        print "$tab"."\n";
        print "$tab"."MERGETOOL INFORMATION\n";
        print "$tab"."  $merge_tool";
        if ($tool_supports_3way =~ "yes") {
            print "  (manual 3-way merging is supported)\n";
        } else {
            print "  (manual 3-way merging is not supported)\n";
        }
        &tool_intro($merge_tool_name);
        print "$tab"."\n";
        print "$tab"."For more info, type \"man cfg-update\"\n";
        print "$tab"."or visit $website\n\n";
    }
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</print_usage>\n"; }
}

sub show_debug_info {
    if ($opt_d >= 1) { print "$tab"."<show_debug_info>\n"; $tab = $tab."    "; }
        $debug = "";                # removes "2>/dev/null" from all shell-commands so any error message will be shown
        print "$tab"."version           = $version\n";
        print "$tab"."merge_tool_name   = $merge_tool_name\n";
        print "$tab"."merge_tool        = $merge_tool\n";
        print "$tab"."xxdiff_style      = $xxdiff_style\n";
        print "$tab"."index_file        = $index_file\n";
        print "$tab"."portage_hook      = $portage_hook\n";
        print "$tab"."paludis_hook      = $paludis_hook\n";
        print "$tab"."pkg_manager       = $pkg_manager\n";
        print "$tab"."pkg_db            = $pkg_db\n";
        print "$tab"."install_log       = $install_log\n";
        print "$tab"."find_string       = $find_string\n";
        print "$tab"."backup_path       = $backup_path\n";
        print "$tab"."enable_backups    = $enable_backups\n";
        print "$tab"."enable_stage1     = $enable_stage1\n";
        print "$tab"."enable_stage2     = $enable_stage2\n";
        print "$tab"."enable_stage3     = $enable_stage3\n";
        print "$tab"."enable_stage4     = $enable_stage4\n";
        print "$tab"."enable_stage5     = $enable_stage5\n";
        print "$tab"."config_new        = $config_new\n";
        print "$tab"."rm_new            = $rm_new\n";
        print "$tab"."temp_new          = $temp_new\n";
        print "$tab"."backup_new        = $backup_new\n";
        print "$tab"."restore_new       = $restore_new\n";
        print "$tab"."rm_old            = $rm_old\n";
        print "$tab"."temp_old          = $temp_old\n";
        print "$tab"."backup_old        = $backup_old\n";
        print "$tab"."restore_old       = $restore_old\n";
        print "$tab"."merged            = $merged\n";
        print "$tab"."opt_i = $opt_i\n";
        print "$tab"."opt_f = $opt_f\n";
        print "$tab"."opt_l = $opt_l\n";
        print "$tab"."opt_u = $opt_u\n";
        print "$tab"."opt_b = $opt_b\n";
        print "$tab"."opt_r = $opt_r\n";
        print "$tab"."opt_a = $opt_a\n";
        print "$tab"."opt_m = $opt_m\n";
        print "$tab"."opt_d = $opt_d\n";
        print "$tab"."opt_p = $opt_p\n";
        print "$tab"."opt_v = $opt_v\n";
        print "$tab"."opt_t = $opt_t\n";
        print "$tab"."opt_help                 = $opt_help\n";
        print "$tab"."opt_ebuild               = $opt_ebuild\n";
        print "$tab"."opt_testsandbox          = $opt_testsandbox\n";
        print "$tab"."opt_disable_portage_hook = $opt_disable_portage_hook\n";
        print "$tab"."opt_disable_paludis_hook = $opt_disable_paludis_hook\n";
        print "$tab"."opt_optimize_backups     = $opt_optimize_backups\n";
    if ($opt_d >= 1) { $tab =~ s/    //; print "$tab"."</show_debug_info>\n"; }
}
