#!/usr/bin/perl

#- Copyright (C) 1999,2001 MandrakeSoft (pixel@linux-mandrake.com)
#-
#- This program is free software; you can redistribute it and/or modify
#- it under the terms of the GNU General Public License as published by
#- the Free Software Foundation; either version 2, or (at your option)
#- any later version.
#-
#- 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 for more details.
#-
#- You should have received a copy of the GNU General Public License
#- along with this program; if not, write to the Free Software
#- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

#use strict qw(subs vars refs);
use urpm;

#- contains informations to parse installed system.
my $urpm = new urpm;

#- default options.
my $update = 0;
my $media = '';
my $excludemedia = '';
my $sortmedia = '';
my $synthesis = '';
my $auto = 0;
my $allow_medium_change = 0;
my $auto_select = 0;
my $keep = 0;
my $no_remove = 0;
my $split_level = 20;
my $split_length = 1;
my $force = 0;
my $parallel = '';
my $sync;
my $X = 0;
my $WID = 0;
my $all = 0;
my $rpm_opt = "vh";
my $use_provides = 1;
my $src = 0;
my $install_src = 0;
my $clean = 0;
my $noclean = 0;
my $verbose = 0;
my $skip = '';
my $root = '';
my $bug = '';
my $env = '';
my $log = '';
my $test = 0;

my $uid;
my @files;
my @src_files;
my @names;
my @src_names;

$ENV{PATH} = "/sbin:/usr/sbin:/bin:/usr/bin:/usr/X11R6/bin";
delete @ENV{qw(ENV BASH_ENV IFS CDPATH)};
($<, $uid) = ($>, $<);
$ENV{HOME} ||= "/root";
$ENV{USER} ||= "root";
    
sub usage {
    print STDERR N("urpmi version %s
Copyright (C) 1999, 2000, 2001, 2002 MandrakeSoft.
This is free software and may be redistributed under the terms of the GNU GPL.

usage:
", $urpm::VERSION) . N("  --help         - print this help message.
") . N("  --update       - use only update media.
") . N("  --media        - use only the given media, separated by comma.
") . N("  --excludemedia - do not use the given media, separated by comma.
") . N("  --sortmedia    - sort media according to substrings separated by comma.
") . N("  --synthesis    - use the given synthesis instead of urpmi db.
") . N("  --auto         - automatically select a package in choices.
") . N("  --auto-select  - automatically select packages to upgrade the system.
") . N("  --no-uninstall - never ask to uninstall a package, abort the installation.
") . N("  --keep         - keep existing packages if possible, reject requested
                   packages that leads to remove.
") . N("  --split-level  - split in small transaction if more than given packages
                   are going to be installed or upgraded,
                   default is %d.
", $split_level) . N("  --split-length - small transaction length, default is %d.
", $split_length) . N("  --fuzzy        - impose fuzzy search (same as -y).
") . N("  --src          - next package is a source package (same as -s).
") . N("  --install-src  - install only source package (no binaries).
") . N("  --clean        - remove rpm from cache before anything else.
") . N("  --noclean      - keep rpm not used in cache.
") . N("  --force        - force invocation even if some packages do not exist.
") . N("  --allow-nodeps - allow asking user to install packages without
                   dependencies checking.
") . N("  --allow-force  - allow asking user to install packages without
                   dependencies checking and integrity.
") . N("  --parallel     - distributed urpmi accross machines of alias.
") . N("  --wget         - use wget to retrieve distant files.
") . N("  --curl         - use curl to retrieve distant files.
") . N("  --limit-rate   - limit the download speed.
") . N("  --proxy        - use specified HTTP proxy, the port number is assumed
                   to be 1080 by default (format is <proxyhost[:port]>).
") . N("  --proxy-user   - specify user and password to use for proxy
                   authentication (format is <user:password>).
") . N("  --bug          - output a bug report in directory indicated by
                   next arg.
") . N("  --env          - use specific environment (typically a bug
                   report).
") . N("  --X            - use X interface.
") . N("  --best-output  - choose best interface according to the environment:
                   X or text mode.
") . N("  --verify-rpm   - verify rpm signature before installation
                   (--no-verify-rpm disable it, default is enabled).
") . N("  --test         - verify if the installation can be achieved correctly.
") . N("  --excludepath  - exclude path separated by comma.
") . N("  --excludedocs  - exclude docs files.
") . N("  -a             - select all matches on command line.
") . N("  -p             - allow search in provides to find package.
") . N("  -P             - do not search in provides to find package.
") . N("  -y             - impose fuzzy search (same as --fuzzy).
") . N("  -s             - next package is a source package (same as --src).
") . N("  -q             - quiet mode.
") . N("  -v             - verbose mode.
") . "\n" . N("  names or rpm files given on command line will be installed.
");
    exit(0);
}

sub to_utf8 { Locale::gettext::iconv($_[0], undef, "UTF-8") }
sub gmessage {
    my ($msg, %params) = @_;
    my $ok = to_utf8(N("Ok"));
    my $cancel = to_utf8(N("Cancel"));
    my $buttons = $params{ok_only} ? "$ok:0" : "$ok:0,$cancel:2";
    $msg = to_utf8($msg);
    `gmessage -default "$ok" -buttons "$buttons" "$msg"`;
}

#- parse arguments list.
my @nextargv;
my $command_line = join " ", @ARGV;
@ARGV or usage;
my @argv = @ARGV;
while (defined($_ = shift @argv)) {
    /^--help$/ and do { usage; next };
    /^--no-locales$/ and do { undef *N; undef *urpm::N; *N = *urpm::N = sub { sprintf(shift @_, @_) }; next };
    /^--update$/ and do { $update = 1; next };
    /^--media$/ and do { push @nextargv, \$media; next };
    /^--exclude-?media$/ and do { push @nextargv, \$excludemedia; next };
    /^--sort-?media$/ and do { push @nextargv, \$sortmedia; next };
    /^--mediums$/ and do { push @nextargv, \$media; next };
    /^--synthesis$/ and do { push @nextargv, \$synthesis; next };
    /^--auto$/ and do { $auto = 1; next };
    /^--allow-medium-change$/ and do { $allow_medium_change = 1; next };
    /^--auto-select$/ and do { $auto_select = 1; next };
    /^--no-(remove|uninstall)$/ and do { $no_remove = 1; next };
    /^--keep$/ and do { $keep = 1; next };
    /^--split-level$/ and do { push @nextargv, \$urpm->{options}{'split-level'}; next };
    /^--split-length$/ and do { push @nextargv, \$urpm->{options}{'split-length'}; next };
    /^--(no-)?fuzzy$/ and do { $urpm->{options}{fuzzy} = !$1; next };
    /^--src$/ and do { $src = 1; next };
    /^--install-src$/ and do { $install_src = 1; next };
    /^--clean$/ and do { $clean = 1; $noclean = 0; next };
    /^--noclean$/ and do { $clean = $urpm->{options}{'pre-clean'} = $urpm->{options}{'post-clean'} = 0; $noclean = 1; next };
    /^--(no-)?pre-clean$/ and do { $urpm->{options}{'pre-clean'} = !$1; next };
    /^--(no-)?post-clean$/ and do { $urpm->{options}{'post-clean'} = !$1; next };
    /^--force$/ and do { $force = 1; next };
    /^--allow-nodeps$/ and do { $urpm->{options}{'allow-nodeps'} = 1; next };
    /^--allow-force$/ and do { $urpm->{options}{'allow-force'} = 1; next };
    /^--parallel$/ and do { push @nextargv, \$parallel; next };
    /^--wget$/ and do { $sync = sub { my $options = shift @_;
				      if (ref $options) { $options->{prefer} = 'wget' }
				      else { $options = { dir => $options, prefer => 'wget' } }
				      urpm::sync_webfetch($options, @_) }; next };
    /^--curl$/ and do { $sync = \&urpm::sync_webfetch; next };
    /^--limit-rate$/ and do { push @nextargv, \$urpm->{options}{'limit-rate'}; next };
    /^--proxy$/ and do {
	my ($proxy, $port) = ($_ = shift @argv) =~ m,^(?:http://)?([^:]+(:\d+)?)/*$, or
	    die N("bad proxy declaration on command line\n");
	$proxy .= ":1080" unless $port;
	$urpm->{proxy}{http_proxy} = "http://$proxy";
	next;
    };
    /^--proxy-user$/ and do {
	($_ = shift @argv) =~ /(.+):(.+)/, or
	    die N("bad proxy declaration on command line\n");
	@{$urpm->{proxy}}{qw(user proxy)} = ($1, $2);
	next;
    };
    /^--bug$/ and do { push @nextargv, \$bug; next };
    /^--env$/ and do { push @nextargv, \$env; next };
    /^--X$/ and do { $X = 1; next };
    /^--WID=(.*)$/ and do { $WID = $1; next };
    /^--WID$/ and do { push @nextargv, \$WID; next };
    /^--best-output$/ and do { $X ||= $ENV{DISPLAY} && system('/usr/X11R6/bin/xtest', '') == 0;
			       next };
    /^--(no-)?verify-rpm$/ and do { $urpm->{options}{'verify-rpm'} = !$1; next };
    /^--(no-)?test$/ and do { $test = !$1; next };
    /^--comment$/ and do { push @nextargv, undef; next };
    /^--skip$/ and do { push @nextargv, \$skip; next };
    /^--root$/ and do { push @nextargv, \$root; next };
    /^--exclude-?path$/ and do { $urpm->{options}{excludepath} = undef; push @nextargv, \$urpm->{options}{excludepath}; next };
    /^--exclude-?docs$/ and do { $urpm->{options}{excludedocs} = 1; next };
    /^-(.*)$/ and do { foreach (split //, $1) {
	/[\?h]/ and do { usage; next };
	/a/ and do { $all = 1; next };
	/c/ and do { next };
	/m/ and do { next };
	/M/ and do { next }; #- nop
	/q/ and do { --$verbose; $rpm_opt = ""; next };
	/p/ and do { $use_provides = 1; next };
	/P/ and do { $use_provides = 0; next };
	/y/ and do { $urpm->{options}{fuzzy} = 1; next };
	/s/ and do { $src = 1; next };
	/v/ and do { ++$verbose; $rpm_opt = "vh"; next };
	/z/ and do { $urpm->{options}{compress} = 1; next };
	die N("urpmi: unknown option \"-%s\", check usage with --help\n", $1) } next };
    @nextargv and do { my $r = shift @nextargv; $r and $$r = $_; next };
    if (/\.rpm$/) {
	if (/\.src\.rpm$/) {
	    push @src_files, $_;
	} else {
	    push @files, untaint($_);
	}
	next;
    }
    if ($src) {
	push @src_names, $_;
    } else {
	push @names, $_;
    }
    $src = 0; #- reset switch for next package.
}
#- use install_src to promote all names as src package.
if ($install_src) {
    @files and $urpm->{fatal}(1, N("What can be done with binary rpm files when using --install-src"));
    push @src_names, @names;
    @names = ();
}

#- prepare bug report.
if ($bug) {
    mkdir $bug or $urpm->{fatal}(8, N("Unable to create directory [%s] for bug report", $bug));
    #- copy all synthesis file used, along with configuration of urpmi
    system("cp", "-af", $urpm->{skiplist}, $urpm->{instlist}, $bug);
    #- allow log file.
    $log = "$bug/urpmi.log";
}

if ($env) {
    print STDERR N("using specific environment on %s\n", $env);
    $log = "$env/urpmi_env.log";
    unlink $log;
    #- setting new environment.
    $urpm->{config} = "$env/urpmi.cfg";
    $urpm->{skiplist} = "$env/skip.list";
    $urpm->{instlist} = "$env/inst.list";
    $urpm->{statedir} = $env;
} else {
    if ($uid > 0) {
	#- only src files are installable using urpmi.
	!($install_src || $root) and @names || @files and $urpm->{fatal}(1, N("Only superuser is allowed to install packages"));
    } else {
	#- allow log if not defined.
	$log ||= "/var/log/urpmi.log";
    }
}

my ($pid_out, $pid_err);
if ($log) {
    #- log only at this point in case of query usage.
    log_it(scalar localtime, " urpmi called with $command_line\n");

    open SAVEOUT, ">&STDOUT"; select SAVEOUT; $| = 1;
    open SAVEERR, ">&STDERR"; select SAVEERR; $| = 1;
    unless ($pid_out = open STDOUT, "|-") {
	my $buf_r;
	while (<STDIN>) {
	    open F, ">>$log"; select F; $| = 1;
	    select SAVEOUT; $| = 1;
	    $/ = \1;
	    print SAVEOUT $_;
	    print F $_;
	    close F;
	}
	exit 0;
    }
    unless ($pid_err = open STDERR, "|-") {
	my $buf_r;
	while (<STDIN>) {
	    open F, ">>$log"; select F; $| = 1;
	    select SAVEERR; $| = 1;
	    $/ = \1;
	    print SAVEERR $_;
	    print F $_;
	    close F;
	}
	exit 0;
    }
    select STDERR; $| = 1;     # make unbuffered
    select STDOUT; $| = 1;     # make unbuffered
} else {
    open SAVEOUT, ">&STDOUT"; select SAVEOUT; $| = 1;
    open SAVEERR, ">&STDERR"; select SAVEERR; $| = 1;
}

#- use specific sync routine.
$sync and $urpm->{sync} = $sync;

#- remove verbose if not asked.
unless ($bug) {
    $urpm->{fatal} = sub { printf SAVEERR "%s\n", $_[1]; exit($_[0]) };
    $urpm->{error} = sub { printf SAVEERR "%s\n", $_[0] };
    $urpm->{log} = sub { printf SAVEERR "%s\n", $_[0] };
}
$verbose > 0 or $urpm->{log} = sub {};

unless ($env) {
    $urpm->exlock_rpm_db;
    $urpm->shlock_urpmi_db;
}
$urpm->configure(nocheck_access => $env || $uid > 0,
		 media => $media,
		 excludemedia => $excludemedia,
		 sortmedia => $sortmedia,
		 synthesis => $synthesis,
		 update => $update,
		 skip => $skip,
		 root => $root,
		 bug => $bug,
		 parallel => $parallel,
		);
#- get back activated default values of boolean options.
foreach (qw(post-clean verify-rpm)) {
    exists $urpm->{options}{$_} or $urpm->{options}{$_} = 1;
}
exists $urpm->{options}{'split-level'} or $urpm->{options}{'split-level'} = $split_level;
exists $urpm->{options}{'split-length'} or $urpm->{options}{'split-length'} = $split_length;
exists $urpm->{options}{'priority-ugrade'} or $urpm->{options}{'priority-upgrade'} = 'rpm,perl-URPM,urpmi';

my $state = {};
my %requested = $urpm->register_rpms(@files, @src_files);

#- finish bug environment creation.
if ($bug) {
    require URPM::Build;
    foreach (@{$urpm->{media}}) {
	#- take care of virtual medium this way.
	$_->{hdlist} ||= "hdlist.$_->{name}.cz";
	delete $_->{virtual};
	#- now build directly synthesis file, this is by far the simplest method.
	if (defined $_->{start} && defined $_->{end}) {
	    $urpm->build_synthesis(start => $_->{start}, end => $_->{end}, synthesis => "$bug/synthesis.$_->{hdlist}");
	    $urpm->{log}(N("built hdlist synthesis file for medium \"%s\"", $_->{name}));
	}
    }
    #- fake configuration written to convert virtual media on the fly.
    $urpm->{config} = "$bug/urpmi.cfg";
    $urpm->write_config;
    #- handle local packages, copy them directly in bug environment.
    foreach (keys %requested) {
	$urpm->{source}{$_} and system "cp", "-af", $urpm->{source}{$_}, $bug;
    }
}

#- search the packages according the selection given by the user,
#- basesystem is added to the list so if it need to be upgraded,
#- all its dependency will be updated too.
#- make sure basesystem exists before.
if (@names) {
    $urpm->search_packages(\%requested, [ @names ],
			   all => $all,
			   use_provides => $use_provides,
			   fuzzy => $urpm->{options}{fuzzy})
      or $force or exit 1;
}
if (@src_names) {
    $urpm->search_packages(\%requested, [ @src_names ],
			   all => $all,
			   use_provides => $use_provides,
			   fuzzy => $urpm->{options}{fuzzy},
			   src => 1)
      or $force or exit 1;
}

#- filter to add in packages selected required packages.
sub ask_choice {
    my ($urpm, $db, $state, $choices) = @_;
    my $n = 1; #- default value.
    my (@l) = map { scalar $_->fullname } @$choices;
    my $from;

    if (@l > 1 && !$auto) {
	my $msg = (defined $from ?
		   N("One of the following packages is needed to install %s:", $from) :
		   N("One of the following packages is needed:"));
	if ($X) {
	    `gchooser "$msg" @l`;
	    $n = $? >> 8 || die;
	} else {
	    message($msg);
	    my $i = 0; foreach (@l) { message(" " . ++$i . "- $_") }
	    $n = message_input(N("What is your choice? (1-%d) ", $i), undef, range => $i);
	    defined $n or exit 1;
	}
    }

    $choices->[$n - 1];
};

#- do the resolution of dependencies between requested package (and auto selection if any).
#- handle parallel option if any.
#- return value is true if program should be restarted (in order to take care of important
#- packages being upgraded (problably urpmi and perl-URPM, but maybe rpm too, and glibc also ?).
my $restart_itself = $urpm->resolve_dependencies($state, \%requested,
						 rpmdb => $env && "$env/rpmdb.cz",
						 auto_select => $auto_select,
						 callback_choices => \&ask_choice,
						 install_src => $install_src,
						 keep => $keep,
						 nodeps => $urpm->{options}{'allow-nodeps'} || $urpm->{options}{'allow-force'},
						 priority_upgrade => !$env && $urpm->{options}{'priority-upgrade'},
						);

my @ask_unselect = $urpm->unselected_packages($state);
if (@ask_unselect) {
    my $list = join "\n", $urpm->translate_why_unselected($state, sort @ask_unselect);
    my $msg = N("Some package requested cannot be installed:\n%s", $list);
    if ($auto) {
	message($msg, 'noX');
    } else {
	if ($X) {
	    gmessage("$msg\n".N("do you agree ?"));
	    $? and exit 0;
	} else {
	    $noexpr = N("Nn");
	    $yesexpr = N("Yy");
	    message_input($msg . N(" (Y/n) "), $force && $yesexpr, boolean => 1) =~ /[$noexpr]/ and exit 0;
	}
    }
}

my @ask_remove = $urpm->{options}{'allow-force'} ? @{[]} : $urpm->removed_packages($state);
if (@ask_remove) {
    my $list = join "\n", $urpm->translate_why_removed($state, sort @ask_remove);

    if ($no_remove && !$force) {
	my $msg = N("The installation cannot continue because the following packages
have to be removed for others to be upgraded:\n%s\n", $list);
	message($msg);
	exit 0;
    }

    my $msg = N("The following packages have to be removed for others to be upgraded:\n%s", $list);
    if ($auto) {
	message($msg, 'noX');
    } else {
	if ($X) {
	    gmessage("$msg\n".N("do you agree ?"));
	    $? and exit 0;
	} else {
	    $noexpr = N("Nn");
	    $yesexpr = N("Yy");
	    message_input($msg . N(" (y/N) "), $force && $yesexpr, boolean => 1) =~ /[$yesexpr]/ or exit 0;
	}
    }
}

#- package to install as a array of strings.
my @to_install;

#- check if there is at least one package to install that
#- has not been given by the user.
my $ask_user = $env;
my $sum = 0;
my @root_only;

foreach my $pkg (sort { $a->name cmp $b->name } @{$urpm->{depslist}}[keys %{$state->{selected}}]) {
    #- reflect change in flag usage, now requested is set whatever a package is selected or not,
    #- but required is always set (so a required but not requested is a pure dependencies).
    $ask_user ||= !$pkg->flag_requested || $auto_select || $parallel;

    my $fullname = $pkg->fullname;
    if (!$env && $install_src && $pkg->arch ne 'src') {
	push @root_only, $fullname;
    } elsif ($install_src || $pkg->arch ne 'src') {
	$sum += $pkg->size;
	push @to_install, $fullname;
    }
}
if ($env) {
    my $msg = N("To satisfy dependencies, the following packages are going to be installed (%d MB)", toMb($sum));
    my $p = join "\n", @to_install;
    print STDERR "$msg:\n$p\n";
    exit 0; #- exit now for specific environment.
}
if (@root_only) {
    print STDERR N("You need to be root to install the following dependencies:\n%s\n", join ' ', @root_only);
    exit 1;
} elsif (!$auto && ($ask_user || $X) && @to_install) {
    my $msg = N("To satisfy dependencies, the following packages are going to be installed (%d MB)", toMb($sum));
    my $msg2 = N("Is this OK?");
    my $p = join "\n", @to_install;
    if ($X) {
	gmessage("$msg:\n$p\n\n$msg2");
	$? and exit 0;
    } else {
	$noexpr = N("Nn");
	$yesexpr = N("Yy");
	message_input("$msg:\n$p\n$msg2" . N(" (Y/n) "), $force && $yesexpr, boolean => 1) =~ /[$noexpr]/ and exit 0;
    }
}

#- if not root, the list become invisible and no download will be possible.
my ($local_sources, $list) = $urpm->get_source_packages($state->{selected},
							clean_all => $clean,
							clean_other => !$noclean && $urpm->{options}{'pre-clean'});
unless ($local_sources || $list) {
    $urpm->{fatal}(3, N("unable to get source packages, aborting"));
}

if ($X) {
    require gurpm;
    gurpm::init(N("Package installation..."), N("Initializing..."));
}

my %sources = %$local_sources;
my %error_sources;

$urpm->copy_packages_of_removable_media($list, \%sources,
					verbose => $verbose > 0,
					force_local => 1,
					ask_for_medium => (!$auto || $allow_medium_change) && sub {
					    my $msg = N("Please insert the medium named \"%s\" on device [%s]", @_);
					    my $msg2 = N("Press Enter when ready...");
					    if ($X) {
						$msg =~ s/"/\\"/g;
						gmessage($msg);
						!$?;
					    } else {
						defined message_input("$msg\n$msg2 ");
					    }
					});

#- now create transaction just before installation, this will save user impression of slowness.
#- split of transaction should be disabled if --test is used.
$urpm->create_transaction($state,
			  nodeps => $urpm->{options}{'allow-nodeps'} || $urpm->{options}{'allow-force'},
			  split_level => $urpm->{options}{'split-level'},
			  split_length => !$test && $urpm->{options}{'split-length'});

my ($ok, $nok) = (0, 0);
my @errors;

foreach my $set (@{$state->{transaction} || []}) {
    my (@transaction_list, %transaction_sources);

    #- put a blank line to separate with previous transaction or user question.
    message("", 'noX');

    #- prepare transaction...
    $urpm->prepare_transaction($set, $list, \%sources, \@transaction_list, \%transaction_sources);

    #- first, filter out what is really needed to download for this small transaction.
    $urpm->download_packages_of_distant_media(\@transaction_list, \%transaction_sources, \%error_sources,
					      verbose => $verbose > 0,
					      limit_rate => $urpm->{options}{'limit-rate'},
					      compress => $urpm->{options}{compress},
					      force_local => 1,
					      callback => sub {
						  my ($mode, $file, $percent, $total, $eta, $speed) = @_;
						  if ($X) {
						      if ($mode eq 'start') {
							  $file =~ s|/*\s*$||; $file =~ s|.*/||;
							  gurpm::label(N("Downloading package `%s'...", $file));
						      } elsif ($mode eq 'progress') {
							  gurpm::progress($percent/100);
						      } elsif ($mode eq 'end') {
							  gurpm::progress(1);
						      }
						  } else {
						      if ($mode eq 'start') {
							  print STDERR "    $file\n"; #- allow pass-protected url to be logged.
						      } elsif ($mode eq 'progress') {
							  my $text;
							  if (defined $total && defined $eta) {
							      $text = N("        %s%% of %s completed, ETA = %s, speed = %s",
									$percent, $total, $eta, $speed);
							  } else {
							      $text = N("        %s%% completed, speed = %s",
									$percent, $speed);
							  }
							  print SAVEERR $text, " " x (79 - length($text)), "\r";
						      } elsif ($mode eq 'end') {
							  print SAVEERR " " x 79, "\r";
						      }
						  }
					      },
					     );
    my %transaction_sources_install = %{$urpm->extract_packages_to_install(\%transaction_sources) || {}};
    
    if ($urpm->{options}{'verify-rpm'} && !$force) {
	my @bad_signatures = $urpm->check_sources_signatures(\%transaction_sources_install, \%transaction_sources, translate => 1);
	
	if (@bad_signatures) {
	    my $msg = N("The following packages have bad signatures");
	    my $msg2 = N("Do you want to continue installation ?");
	    my $p = join "\n", @bad_signatures;
	    if ($auto) {
		message("$msg:\n$p\n", 'noX');
		exit 1;
	    } else {
		if ($X) {
		    gmessage("$msg:\n$p\n\n$msg2");
		    $? and exit 1;
		} else {
		    $noexpr = N("Nn");
		    $yesexpr = N("Yy");
		    message_input("$msg:\n$p\n$msg2" . N(" (y/N) "), $force && $yesexpr, boolean => 1) =~ /[$yesexpr]/ or exit 1;
		}
	    }
	}
    }

    #- check for local files.
    if (my @missing = grep { m|^/| && ! -e $_ } values %transaction_sources_install, values %transaction_sources) {
	message(N("Installation failed, some files are missing:\n%s\nYou may want to update your urpmi database",
		  join "\n", map { "    $_" } @missing));
	++$nok;
	next;
    }

    #- install source package only (whatever the user is root or not, but use rpm for that).
    if ($install_src) {
	if (my @l = grep { /\.src\.rpm$/ } values %transaction_sources_install, values %transaction_sources) {
	    system("rpm", "-i$rpm_opt", @l, ($root ? ("--root", $root) : @{[]}));
	    $? and message(N("Installation failed")), ++$nok;
	}
	next;
    }

    #- clean to remove any src package now.
    foreach (\%transaction_sources_install, \%transaction_sources) {
	foreach my $id (keys %$_) {
	    my $pkg = $urpm->{depslist}[$id] or next;
	    $pkg->arch eq 'src' and delete $_->{$id};
	}
    }

    if (%transaction_sources_install || %transaction_sources) {
	if ($parallel) {
	    message(N("distributing %s", join(' ', values %transaction_sources_install, values %transaction_sources)), 'noX');
	    #- no remove are handle here, automatically done by each distant node.
	    $urpm->{log}("starting distributed install");
	    $urpm->parallel_install([ keys %{$state->{rejected} || {}} ], \%transaction_sources_install, \%transaction_sources,
				    test => $test,
				    excludepath => $urpm->{options}{excludepath}, excludedocs => $urpm->{options}{excludedocs});
	} else {
	    %transaction_sources_install || %transaction_sources and
		message(N("installing %s", join(' ', values %transaction_sources_install, values %transaction_sources)), 'noX');
	    @{!$urpm->{options}{'allow-force'} && $set->{remove} || []} and
		message(N("removing %s", join(' ', @{!$urpm->{options}{'allow-force'} && $set->{remove} || []})), 'noX');
	    log_it(scalar localtime, " ", join(' ', values %transaction_sources_install, values %transaction_sources), "\n");
	    $urpm->{log}("starting installing packages");
	    my $progress_nb;
	    my $total_nb = grep { m|^/| } values %transaction_sources_install, values %transaction_sources;
	    my $callback_inst = $X && sub {
		my ($urpm, $type, $id, $subtype, $amount, $total) = @_;
		my $pkg = defined $id && $urpm->{depslist}[$id];

		if ($subtype eq 'start') {
		    if ($type eq 'trans') {
			gurpm::label(N("Preparing..."));
		    } else {
			gurpm::label(N("Installing package `%s' (%s/%s)...", $pkg->name, ++$progress_nb, $total_nb));
		    }
		} elsif ($subtype eq 'progress') {
		    gurpm::progress($amount/$total);
		}
	    };
	    my @l = $urpm->install(!$urpm->{options}{'allow-force'} && $set->{remove} || [],
				   \%transaction_sources_install, \%transaction_sources,
				   fork => @{$state->{transaction} || []} > 1, #- fork if multiple transaction
				   translate_message => 1,
				   oldpackage => $state->{oldpackage}, post_clean_cache => $urpm->{options}{'post-clean'},
				   test => $test,
				   excludepath => $urpm->{options}{excludepath}, excludedocs => $urpm->{options}{excludedocs},
				   callback_inst => $callback_inst, callback_trans => $callback_inst);
	    if (@l) {
		message(N("Installation failed") . ":\n" . join("\n",  map { "\t$_" } @l));
		if ($auto || !$urpm->{options}{'allow-nodeps'} && !$urpm->{options}{'allow-force'}) {
		    ++$nok;
		    push @errors, @l;
		} else {
		    $noexpr = N("Nn");
		    $yesexpr = N("Yy");
		    message_input(N("Try installation without checking dependencies? (y/N) "),
				  $force && $yesexpr, boolean => 1) =~ /[$yesexpr]/ or ++$nok, next;
		    $urpm->{log}("starting installing packages without deps");
		    @l = $urpm->install(!$urpm->{options}{'allow-force'} && $set->{remove} || [],
					\%transaction_sources_install, \%transaction_sources,
					fork => @{$state->{transaction} || []} > 1, #- fork if multiple transaction
					translate_message => 1, nodeps => 1,
					oldpackage => $state->{oldpackage}, post_clean_cache => $urpm->{options}{'post-clean'},
					test => $test,
					excludepath => $urpm->{options}{excludepath},
					excludedocs => $urpm->{options}{excludedocs});
		    if (@l) {
			message(N("Installation failed") . ":\n" . join("\n", map { "\t$_" } @l));
			if (!$urpm->{options}{'allow-force'}) {
			    ++$nok;
			    push @errors, @l;
			} else {
			    message_input(N("Try installation even more strongly (--force)? (y/N) "),
					  $force && $yesexpr, boolean => 1) =~ /[$yesexpr]/ or ++$nok, next;
			    $urpm->{log}("starting force installing packages without deps");
			    @l = $urpm->install(!$urpm->{options}{'allow-force'} && $set->{remove} || [],
						\%transaction_sources_install, \%transaction_sources,
						fork => @{$state->{transaction} || []} > 1, #- fork if multiple transaction
						translate_message => 1, nodeps => 1, force => 1,
						oldpackage => $state->{oldpackage},
						post_clean_cache => $urpm->{options}{'post-clean'},
						test => $test,
						excludepath => $urpm->{options}{excludepath},
						excludedocs => $urpm->{options}{excludedocs});
			    if (@l) {
				message(N("Installation failed") . ":\n" . join("\n", map { "\t$_" } @l));
				++$nok;
				push @errors, @l;
			    } else {
				++$ok;
			    }
			}
		    } else {
			++$ok;
		    }
		}
	    } else {
		++$ok;
	    }
	}
    }
}
$X and gurpm::end();

#- keep a track of error code.
my $exit_code = 0;
if (values %error_sources) {
    message(N("Installation failed, some files are missing:\n%s\nYou may want to update your urpmi database",
	      join "\n", map { "    $_" } values %error_sources));
    $exit_code = 10;
}
if ($nok) {
    $nok > 1 and message(N("%d installation transactions failed", $nok) . (@errors && ":\n" . join("\n", map { "\t$_" } @errors)));
    if ($exit_code) {
	$exit_code = $ok ? 13 : 14;
    } else {
	$exit_code = $ok ? 11 : 12;
    }
} else {
    if ($test) {
	message(N("Installation is possible"));
    } elsif (@names || @src_names || @files || @src_files || $auto_select) {
	@{$state->{transaction} || []} == 0 && @ask_unselect == 0 && $verbose >= 0 and
	  message(N("Everything already installed"), $auto);
    }
}

unless ($env) {
    $urpm->unlock_urpmi_db;
    $urpm->unlock_rpm_db;

    #- try to umount removable device which may have been mounted.
    $urpm->try_umounting_removables;
}

#- restart urpmi if needed, keep command line for that.
if ($restart_itself && !$exit_code) {
    message(N("restarting urpmi"), 'noX');
    #- it seems to work correctly with exec instead of system, provided
    #- STDOUT or STDERR are not closed before (else no output at all).
    exec "/usr/sbin/urpmi", @ARGV;
}

#- this help flushing correctly by closing this file before (piped on tee).
#- but killing them is generally better.
if ($pid_err && $pid_out) {
    kill 15, $pid_err, $pid_out;
    close STDERR;
    close STDOUT;
}

POSIX::_exit $exit_code;

sub toMb {
    my $nb = $_[0] / 1024 / 1024;
    int $nb + 0.5;
}

sub log_it {
    #- if invoked as a simple user, nothing should be logged.
    if ($log) {
	local *LOG;
	open LOG, ">>$log" or die "can't output to log file\n";
	print LOG @_;
    }
}

#- message functions.
sub message {
    my ($msg, $noX) = @_;
    if ($X && !$noX && !$auto) {
	gmessage($msg, ok_only => 1);
	$bug and log_it($msg);
    } else {
	if ($bug) {
	    print STDOUT "$msg\n";
	} else {
	    print SAVEOUT "$msg\n";
	}
    }
}
sub message_input {
    my ($msg, $default_input, %options) = @_;
    my $input;

    if ($X && !$default_input) { #- if a default input is given, the user doesn't have to choose (and being asked).
	gmessage($msg, ok_only => 1);
	$bug and log_it($msg);
    } else {
	my $noexpr = N("Nn");
	my $yesexpr = N("Yy");
	while (1) {
	    if ($bug) {
		print STDOUT $msg;
	    } else {
		print SAVEOUT $msg;
	    }
	    if ($default_input) {
		$bug and log_it($input);
		return $default_input;
	    }
	    $input = <STDIN>;
	    defined $input or return undef;
	    $bug and log_it($input);
	    if ($options{boolean}) {
		$input =~ /^[$noexpr$yesexpr]*$/ and last;
	    } elsif ($options{range}) {
		1 <= $input && $input <= $options{range} and last;
	    } else {
		last;
	    }
	    message(N("Sorry, bad choice, try again\n"));
	}
    }
    return $input;
}

sub untaint {
    my @r;
    foreach (@_) {
        /(.*)/;
	push @r, $1;
    }
    @r == 1 ? $r[0] : @r
}