The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl

# $Id: urpmi 271299 2010-11-21 15:54:30Z peroyvind $

#- Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 MandrakeSoft SA
#- Copyright (C) 2005-2010 Mandriva SA
#-
#- 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;
use urpm;
use urpm::args;
use urpm::msg;
use urpm::media;
use urpm::select;
use urpm::util qw(cat_ difference2 find member partition untaint);
use urpm::main_loop;

#- default options.
our $update = 0;
our $media = '';
our $searchmedia;
our $excludemedia = '';
our $sortmedia = '';
our $allow_medium_change = 0;
our $auto_select = 0;
our $auto_update = 0;
our $no_install = 0;
our $no_remove = 0;
our $install_src = 0;
our $clean = 0;
our $noclean = 0;
our $force = 0;
our $parallel = '';
our $env = '';
our $test = 0;
our $all = 0;
our $use_provides = 1;
our $logfile = '';
our $restricted = 0;
our $forcekey = 0;

my @files;
my @src_files;
my @names;
my @src_names;

$ENV{PATH} = "/sbin:/usr/sbin:/bin:/usr/bin";
delete @ENV{qw(ENV BASH_ENV IFS CDPATH)};
$ENV{HOME} ||= "/root";
$ENV{USER} ||= "root";

sub usage () {
    print urpm::args::copyright('urpmi', [ '1999-2010', 'Mandriva' ], [ '2011-2013', 'Mageia' ])
   . N("  --help         - print this help message.
") . N("  --media        - use only the given media, separated by comma.
") . N("  --excludemedia - do not use the given media, separated by comma.
") . N("  --update       - use only update media.
") . N("  --searchmedia  - use only the given media to search requested packages.
") . N("  --sortmedia    - sort media according to substrings separated by comma.
") . N("  --synthesis    - use the given synthesis instead of urpmi db.
") . N("  --auto         - non-interactive mode, assume default answers to questions.
") . N("  --auto-select  - automatically select packages to upgrade the system.
") . N("  --auto-update  - update media then upgrade the system.
") . N("    --no-md5sum    - disable MD5SUM file checking.
") . N("    --force-key    - force update of gpg key.
") . N("  --auto-orphans - remove orphans without asking
") . N("  --no-suggests  - do not auto select \"suggested\" packages.
") . N("  --no-uninstall - never ask to uninstall a package, abort the installation.
") . N("  --no-install   - don't install packages (only download)
") . N("  --keep         - keep existing packages if possible, reject requested
                   packages that lead to removals.
") . N("  --split-level  - split in small transaction if more than given packages
                   are going to be installed or upgraded,
                   default is %d.
", urpm::default_options()->{'split-level'})
   . N("  --split-length - small transaction length, default is %d.
", urpm::default_options()->{'split-length'})
  . N("  --fuzzy, -y    - impose fuzzy search.
") . N("  --buildrequires - install the buildrequires of the packages
") . N("  --install-src  - install only source package (no binaries).
") . N("  --clean        - remove rpm from cache before anything else.
") . N("  --noclean      - don't clean rpms from cache.
") . N("  --justdb       - update only the rpm db, not the filesystem.
") . N("  --downgrade    - downgrade a package from the version currently installed
                   to the previously highest version
") . N("  --replacepkgs  - force installing packages which are already installed.
") . 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("  --allow-suggests - auto select \"suggested\" packages.
") . N("  --parallel     - distributed urpmi across machines of alias.
") . N("  --root         - use another root for rpm installation.
") . N("  --urpmi-root   - use another root for urpmi db & rpm installation.
") . N("  --use-distrib  - configure urpmi on the fly from a distrib tree, useful
                   to install a chroot with --root option.
") . N("  --metalink     - generate and use a local metalink.
") . N("  --download-all - download all needed packages before trying to install them
") . N("  --downloader   - program to use to retrieve distant files. 
                   known programs: %s
", join(', ', urpm::download::ftp_http_downloaders()))
   . N("  --curl-options - additional options to pass to curl
") . N("  --rsync-options- additional options to pass to rsync
") . N("  --wget-options - additional options to pass to wget
") . N("  --prozilla-options - additional options to pass to prozilla
") . N("  --aria2-options - additional options to pass to aria2
") . N("  --limit-rate   - limit the download speed.
") . N("  --resume       - resume transfer of partially-downloaded files
                   (--no-resume disables it, default is disabled).
") . 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("  --verify-rpm   - verify rpm signature before installation
                   (--no-verify-rpm disables it, default is enabled).
") . N("  --test         - only verify if the installation can be achieved correctly.
") . N("  --excludepath  - exclude path separated by comma.
") . N("  --excludedocs  - exclude doc files.
") . N("  --ignoresize   - don't verify disk space before installation.
") . N("  --ignorearch   - allow to install rpms for unmatched architectures.
") . N("  --noscripts    - do not execute package scriptlet(s)
") . N("  --replacefiles - ignore file conflicts
") . N("  --skip         - packages which installation should be skipped
") . N("  --prefer       - packages which should be preferred
") . N("  --more-choices - when several packages are found, propose more choices
                   than the default.
") . N("  --nolock       - don't lock rpm db.
") . N("  --strict-arch  - upgrade only packages with the same architecture.
") . 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("  --quiet, -q    - quiet mode.
") . N("  --verbose, -v  - verbose mode.
") . N("  --debug        - very verbose mode.
") . "\n" . N("  names or rpm files given on command line will be installed.
");
    exit(1);
}

# Parse command line
my $command_line = join " ", @ARGV;
my @ARGVcopy; # keep a copy, in case we have to restart

# Expand *.urpmi arguments
if (member('--restricted', @ARGV)) {
    @ARGVcopy = @ARGV;
} else {
    foreach my $a (@ARGV) {
	if ($a =~ /\.urpmi$/) {
	    open my $fh, '<', $a or do { warn "Can't open $a: $!\n"; next };
	    push @ARGVcopy, map { chomp; $_ } <$fh>;
	    close $fh;
	} else {
	    push @ARGVcopy, $a;
	}
    }
    @ARGV = @ARGVcopy;
}

my $urpm = urpm->new_parse_cmdline or exit(1);

if (@ARGV && $auto_select) {
    print STDERR N("Error: can't use --auto-select along with package list.\n");
    exit 1;
}

# Verify that arguments were given
unless (@ARGV || $auto_select || $clean) {
    if ($options{bug}) {
	print STDERR N("Error: To generate a bug report, specify the usual command-line arguments
along with --bug.\n");
	exit 1;
    }
    usage();
}

my @spec_files;
# Process the rest of the arguments
foreach (@ARGV) {
    if (/\.(?:rpm|spec)$/) {
	if (/\.src\.rpm$/) {
	    push @src_files, $_;
	} elsif (/\.spec$/) {
	    push @spec_files, $_;
	} else {
	    push @files, untaint($_);
	}
	next;
    }
    push @names, $_;
}

if ($options{buildrequires}) {
    push @src_names, @names;
    @names = ();
}

#- use install_src to promote all names as src package.
if ($install_src) {
    @files and $urpm->{fatal}(1, N("You can't install binary rpm files when using --install-src"));
    @spec_files and $urpm->{fatal}(1, N("You can't install spec files"));
    push @src_names, @names;
    @names = ();
    #- allow to use --install-src as a non-root user
    $options{nolock} = 1;
} elsif (@spec_files) {
    if (!$options{buildrequires}) {
	$urpm->{error}(N("defaulting to --buildrequires"));
	$options{buildrequires} = 1;
    }
    push @src_files, @spec_files;
} elsif (@src_files && !$options{buildrequires}) {
    $urpm->{error}(N("please use --buildrequires or --install-src, defaulting to --buildrequires"));
    $options{buildrequires} = 1;
}

#- rurpmi checks
if ($restricted) {
    urpm::error_restricted($urpm) if @files;
    #- force some options
    foreach (qw(keep verify-rpm)) { $urpm->{options}{$_} = 1 }
    #- forbid some other options
    urpm::error_restricted($urpm) if $urpm->{root} || $options{usedistrib} || $force || $env || $parallel || $options{synthesis} || $auto_update || $options{auto_orphans};
    foreach (qw(allow-nodeps allow-force curl-options rsync-options wget-options prozilla-options noscripts)) {
	urpm::error_restricted($urpm) if $urpm->{options}{$_};
    }
}

#- prepare bug report.
my $bug = $options{bug};
if ($bug) {
    mkdir $bug or $urpm->{fatal}(8, (-d $bug
	    ? N("Directory [%s] already exists, please use another directory for bug report or delete it", $bug)
	    : N("Unable to create directory [%s] for bug report", $bug)));
    #- copy all synthesis file used, along with configuration of urpmi
    my @list_files = grep { -e $_ } $urpm->{skiplist}, $urpm->{instlist}, 
                                    $urpm->{prefer_list}, $urpm->{prefer_vendor_list}, '/root/.rpmdrake';
    system("cp", "-af", @list_files, $urpm->{config}, $bug)
	and die N("Copying failed");
    #- log everything for bug report.
    $logfile = "$bug/urpmi.log";
}

if ($env) {
    urpm::set_env($urpm, $env);
} else {
    if ($< != 0 && !$options{debug__do_not_install}) {
	#- need to be root if binary rpms are to be installed
	$auto_select || @names || @files and $urpm->{fatal}(1, N("Only superuser is allowed to install packages"));
    }
}

unless ($bug || $install_src || $env || $urpm->{options}{'allow-force'} || $urpm->{root}) {
    require urpm::sys;
    urpm::sys::check_fs_writable() or $urpm->{fatal}(1, N("Error: %s appears to be mounted read-only.
Use --allow-force to force operation.", $urpm::sys::mountpoint));
}

unless ($bug || $env || $test) {
    sys_log("called with: $command_line");
}

my ($pid_out, $pid_err);
if ($logfile && !$INC{"Devel/Trace.pm"}) {
    bug_log(scalar localtime(), " urpmi called with $command_line\n");

    open(my $SAVEOUT, ">&STDOUT"); select $SAVEOUT; $| = 1;
    open(my $SAVEERR, ">&STDERR"); select $SAVEERR; $| = 1;

    #- fork twice to copy stdout and stderr to $logfile
    unless ($pid_out = open STDOUT, "|-") {
	select $SAVEOUT; $| = 1;
	$/ = \1;
	binmode STDIN, ':raw'; #- since we read character by character, perl must not try to recognise utf8 strings since it really can't
	while (my $s = <STDIN>) {
	    open my $fh, ">>$logfile";
	    print $SAVEOUT $s;
	    print $fh $s;
	}
	exit 0;
    }
    unless ($pid_err = open STDERR, "|-") {
	select $SAVEERR; $| = 1;
	$/ = \1;
	binmode STDIN, ':raw'; #- since we read character by character, perl must not try to recognise utf8 strings since it really can't
	while (my $s = <STDIN>) {
	    open my $fh, ">>$logfile";
	    print $SAVEERR $s;
	    print $fh $s;
	}
	exit 0;
    }

    #- log to SAVEERR instead of STDERR
    unless ($bug) {
	$urpm->{fatal} = sub { printf $SAVEERR "%s\n", $_[1]; exit($_[0]) };
	$urpm->{error} = sub { printf $SAVEERR "%s\n", $_[0] };
	$urpm->{log}   = sub { printf $SAVEOUT "%s\n", $_[0] };
    }
}

#- make unbuffered
select STDERR; $| = 1;
select STDOUT; $| = 1;

if ($options{previous_priority_upgrade}) {
    # we were just restarted
    # so, no need to update the media again
    $auto_update = 0;
    # temporary hack : if we were using an old version of URPM (eg: when
    # upgrading from 2006), file handles might have leaked, so close them (with
    # some heuristics.)
    require urpm::sys;
    urpm::sys::fix_fd_leak();
    # also, clean up rpm db log files, because rpm might have been upgraded
    urpm::sys::clean_rpmdb_shared_regions('') if !$urpm->{root};
}

my $urpmi_lock = !$env && !$options{nolock} && urpm::lock::urpmi_db($urpm, '', wait => $options{wait_lock});

#- should we ignore arch compatibility
if ($urpm->{options}{ignorearch}) { urpm::shunt_ignorearch() }

if ($urpm->{root}) {
    $urpm->{options}{'priority-upgrade'} = '' if !$ENV{TESTING_priority_upgrade};
}
if ($auto_update && !$bug && !$env) {
    $urpm->{options}{'auto-update'} = 1;
}

urpm::media::configure($urpm,
    excludemedia => $excludemedia,
    media => $media,
    parallel => $parallel,
    searchmedia => $searchmedia,
    cmdline_skiplist => $options{skip},
    sortmedia => $sortmedia,
    synthesis => $options{synthesis},
    update => $update,
    usedistrib => $options{usedistrib},
    probe_with => $options{probe_with},
    download_callback => \&urpm::download::sync_logger,
    nomd5sum => $options{nomd5sum},
);

if ($bug) {
    require urpm::bug_report;
    urpm::bug_report::rpmdb_to_synthesis($urpm, "$bug/rpmdb.cz");
}

urpm::select::set_priority_upgrade_option($urpm, $options{previous_priority_upgrade});

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

#- finish bug environment creation.
if ($bug) {
    urpm::bug_report::write_urpmdb($urpm, $bug);
    urpm::bug_report::copy_requested($urpm, $bug, \%requested);
}

my $rpm_lock = !$env && !$options{nolock} && urpm::lock::rpm_db($urpm, 'exclusive', wait => $options{wait_lock});

#- search the packages according to the selection given by the user.
my $search_result = '';
if (@names) {
    $search_result = urpm::select::search_packages($urpm,
	\%requested, \@names,
	all => $all,
	use_provides => $use_provides,
	fuzzy => $urpm->{options}{fuzzy},
	no_substring => $urpm->{options}{auto}, # do not allow substring match if we can't prompt the user
    ) || $force or exit 1;

    if (%requested) {
	$urpm->{log}("found package(s): " . join(" ", map { scalar $urpm->{depslist}[$_]->fullname } 
						      map { split /\|/ } keys %requested));
    }
}
if (@src_names) {
    $search_result = urpm::select::search_packages($urpm, \%requested, \@src_names,
	all => $all,
	use_provides => $use_provides,
	fuzzy => $urpm->{options}{fuzzy},
	src => 1,
    ) || $force or exit 1;
}

# callback called to ask user to choose between virtual packages
# - $choices is the sorted list of choices
# - $prefered is a subset of @$choices (it can be empty)
sub ask_choice {
    my ($urpm, $_db, $_state, $choices, $virtual_pkg_name, $prefered) = @_;

    my @choices;
    if ($prefered && @$prefered) {
	@choices = @$choices;
    } else {
	($prefered, my $other) = urpm::select::get_preferred($urpm, $choices, $options{prefer});
	@choices = (@$prefered, @$other);
    }
    my $prefer = @$prefered && join(',', grep { member($choices[$_-1], @$prefered) } 1 .. @choices);

    my (@l) = map {
	my ($name, $summary) = (scalar($_->fullname), translate($_->summary));
	$_->flag_installed ?
          ($_->summary ?
             #-PO: here format is "<package_name>: <summary> (to upgrade)"
             N("%s: %s (to upgrade)", $name, $summary) :
           #-PO: here format is "<package_name> (to upgrade)"
           N("%s (to upgrade)", $name)) :

             $_->flag_upgrade ? ($_->summary ?
                                   #-PO: here format is "<package_name>: <summary> (to install)"
                                   N("%s: %s (to install)", $name, $summary) :
                                 #-PO: here format is "<package_name> (to install)"
                                 N("%s (to install)", $name)) : $name;
    } @choices;

    my $n = 1; #- default value.

    if (@l > 1 && !$urpm->{options}{auto}) {
	print N("In order to satisfy the '%s' dependency, one of the following packages is needed:", $virtual_pkg_name), "\n";
	my $i = 0;
	foreach (@l) { print " " . ++$i . "- $_\n" }
	$n = message_input(N("What is your choice? (1-%d) ", $i), default => $prefer, range_min => 0, range => $i);
	defined($n) && $n ne "0" or exit 1; # abort.
	if ($n =~ /\D/) {
	    my @nn = map { $choices[$_ - 1] } grep { !/\D/ } split /[, \t]+/, $n;
	    @nn or exit 1;
	    return @nn;
	}
    }
    $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;
if ($options{replacepkgs}) {
    urpm::select::select_replacepkgs($urpm, $state, \%requested);
} else {
    $restart_itself = urpm::select::resolve_dependencies($urpm,
    $state,
    \%requested,
    rpmdb => $env && "$env/rpmdb.cz",
    auto_select => $auto_select,
    callback_choices => \&ask_choice,
    install_src => $install_src,
    keep => $urpm->{options}{keep},
    nodeps => $urpm->{options}{'allow-nodeps'} || $urpm->{options}{'allow-force'},
    no_suggests => $urpm->{options}{'no-suggests'},
    priority_upgrade => $test || $env ? '' : $urpm->{options}{'priority-upgrade'},
    );
}

{
    my $msg = urpm::select::translate_already_installed($state);
    $msg and print "$msg\n";
}

my @unselected_uninstalled = @{$state->{unselected_uninstalled} || []};
if (@unselected_uninstalled) {
    my $list = join "\n", map { $_->name . '-' . $_->version . '-' . $_->release  } @unselected_uninstalled;
    my $msg = @unselected_uninstalled == 1 ?
    	N("The following package cannot be installed because it depends on packages
that are older than the installed ones:\n%s",$list)
	: N("The following packages can't be installed because they depend on packages
that are older than the installed ones:\n%s", $list);
    if ($urpm->{options}{auto}) {
	print "$msg\n";
    } else {
	my $noexpr = N("Nn");
	$msg .= N("\nContinue installation anyway?");
	$force || message_input($msg . N(" (Y/n) "), boolean => 1) !~ /[$noexpr]/ or exit 17;
    }
    # Whatever option we selected, the overall installation should fail if some packages are unselected
    $urpm::postponed_msg .= $msg . "\n";
    $urpm::postponed_code = 17;
}

my @ask_unselect = urpm::select::unselected_packages($state);
if (@ask_unselect) {
    my $list = urpm::select::translate_why_unselected($urpm, $state, @ask_unselect);
    my $msg = @ask_unselect == 1 ?
    	N("A requested package cannot be installed:\n%s",$list)
	: N("Some requested packages cannot be installed:\n%s", $list);
    if ($urpm->{options}{auto}) {
	print "$msg\n";
    } else {
	my $noexpr = N("Nn");
	$msg .= N("\nContinue installation anyway?");
	$force || message_input($msg . N(" (Y/n) "), boolean => 1) !~ /[$noexpr]/ or exit 17;
    }
    # Whatever option we selected, the overall installation should fail if some packages are unselected
    $urpm::postponed_msg .= $msg . "\n";
    $urpm::postponed_code = 17;
}

if (my @conflicting_pkgs_msgs = 
      $urpm->{options}{'allow-force'} ? () : urpm::select::removed_packages_msgs($urpm, $state)) {
    {
	my $db = urpm::db_open_or_die_($urpm);
	urpm::select::find_removed_from_basesystem($urpm, $db, $state, sub {
	    my ($urpm, @pkgs) = @_;
	    foreach (@pkgs) {
		$urpm->{error}(N("removing package %s will break your system", $_));
	    }
	    @pkgs and $no_remove = 1;
	});
    }
    if ($no_remove && !$force) {
	my $list = join("\n", @conflicting_pkgs_msgs);
	my $msg = @conflicting_pkgs_msgs == 1 ?
	N("The installation cannot continue because the following package
has to be removed for others to be upgraded:\n%s\n", $list)
	: N("The installation cannot continue because the following packages
have to be removed for others to be upgraded:\n%s\n", $list);
	print "$msg\n";
	exit 17;
    }

    my $msg = urpm::select::conflicting_packages_msg_(\@conflicting_pkgs_msgs);
    if ($test) {
	$msg = "$msg\n" . N("(test only, removal will not be actually done)");
    }
    if ($urpm->{options}{auto}) {
	print "$msg\n";
    } else {
	$force || urpm::msg::ask_yes_or_no($msg) or exit 17;
    }
}

#- check if there is at least one package to install that
#- has not been given by the user.
my $ask_user = $env || $search_result eq 'substring';

my @to_install = @{$urpm->{depslist}}[sort { $a <=> $b } keys %{$state->{selected}}]; # sorted by medium for format_selected_packages
{
    my @binary = grep { $_->arch ne 'src' } @to_install;
    if ($install_src) {
	if (@binary && $install_src && !$env) {
	    my $list = join(' ', sort map { $_->name } @binary);
	    $urpm->{fatal}(1, N("You must first call urpmi with --buildrequires to install the following dependencies:\n%s\n", $list));
	}
    } else {
	@to_install = @binary;
    }
}

if (@to_install && $options{auto_orphans}) {
    urpm::orphans::compute_future_unrequested_orphans($urpm, $state);
    if (my @orphans = map { scalar $_->fullname } @{$state->{orphans_to_remove}}) {
	print P("The following orphan package will be removed.",
		"The following orphan packages will be removed.", scalar(@orphans))
	  . "\n" . urpm::orphans::add_leading_spaces(join("\n", @orphans) . "\n");
    }
}

#- this cleans up the list of potential orphan packages:
#-     - if a package is explicitly requested on the command line, then
#-       we assume the user doesn't want this package to be auto orphaned
#-       so we remove it from installed-through-deps
#-     - this also takes care of removing packages from 
#-       installed-through-deps if the package was first installed as a 
#-       dep of another package, then removed and then explicitly installed
urpm::orphans::mark_as_requested($urpm, $state, $test);

foreach my $pkg (@to_install) {
    #- 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 dependency).
    $ask_user ||= !$pkg->flag_requested || $auto_select || $parallel;
}
$urpm->{nb_install} = @to_install;

sub warn_msg {
    my ($msg) = @_;
    $urpm->{print}(N("WARNING: %s option is in use. Some strange problems may happen", $msg));
}

warn_msg("--allow-force")  if $urpm->{options}{'allow-force'};
warn_msg("--allow-nodeps") if $urpm->{options}{'allow-nodeps'};
warn_msg("--force") if $urpm->{options}{force};
warn_msg("--keep")  if $urpm->{options}{keep};

if (!$urpm->{options}{auto} && $ask_user && $urpm->{nb_install} || $env && !$options{debug__do_not_install}) {
    my $msg = $urpm->{nb_install} == 1 ? N("To satisfy dependencies, the following package is going to be installed:") 
		: N("To satisfy dependencies, the following packages are going to be installed:");
    if ($test) {
	$msg = "$msg\n" . N("(test only, installation will not be actually done)");
    }
    my ($size, $filesize) = $urpm->selected_size_filesize($state);

    my @to_install_formatted = urpm::msg::format_line_selected_packages($urpm, $state, \@to_install);
    my $msg2 = $size >= 0 ? 
      N("%s of additional disk space will be used.", formatXiB($size)) :
      N("%s of disk space will be freed.", formatXiB(-$size));
    my $msg2_ = $filesize ? N("%s of packages will be retrieved.", formatXiB($filesize)) . "\n" : '';
    my $msg3 = P("Proceed with the installation of one package?",
    		"Proceed with the installation of the %d packages?",
		$urpm->{nb_install}, $urpm->{nb_install});
    my $p = join("\n", $msg, @to_install_formatted, $msg2, $msg2_ . $msg3);
    if ($env && !$options{debug__do_not_install}) {
	print "$p\n";
	exit 0; #- exit now for specific environment.
    }
    my $noexpr = N("Nn");
    $force || message_input($p . N(" (Y/n) "), boolean => 1) !~ /[$noexpr]/ or exit 17;
}

my $exit_code = urpm::main_loop::run($urpm, $state, 
				     int(@names || @src_names || @files || @src_files), 
				     \@ask_unselect, {
    (!$urpm->{options}{auto} || $allow_medium_change ? (copy_removable =>  sub {
	my $msg = N("Please insert the medium named \"%s\"", $_[0]);
	if (eval { require Hal::Cdroms; 1 }) {
	    print "$msg\n";
	    Hal::Cdroms->new->wait_for_insert;
	    1;
	} else {
	    my $msg2 = N("Press Enter when mounted...");
	    defined message_input("$msg\n$msg2 ");
	}
    }) : ()),
    trans_log => \&urpm::download::sync_logger,
    bad_signature => sub {
	    my ($msg, $msg2) = @_;
	    #- rurpmi always abort here
	    if ($urpm->{options}{auto} || $restricted) {
		print "$msg\n";
		0;
	    } else {
		$force || urpm::msg::ask_yes_or_no("$msg$msg2");
	    }
    },                           
    ask_yes_or_no => sub {
	my ($_title, $msg) = @_; # graphical title
	$force || urpm::msg::ask_yes_or_no($msg);
    },
    need_restart => sub {
	my ($need_restart_formatted) = @_;
	print "$_\n" foreach values %$need_restart_formatted;
    },
    message => sub {
	my ($_title, $msg) = @_; # graphical title
        print $msg;
    }
});

if ($exit_code == 0 && $auto_select && !$options{auto_orphans} && !$restart_itself) {
    if (urpm::orphans::check_unrequested_orphans_after_auto_select($urpm)) {
	if (my $msg = urpm::orphans::get_now_orphans_msg($urpm)) {
	    print "\n", $msg;
	}
    }
}

unless ($env || $options{nolock}) {
    $urpmi_lock->unlock;
    $rpm_lock->unlock if $rpm_lock;
}

unless ($env) {
    #- try to umount removable device which may have been mounted.
    urpm::removable::try_umounting_removables($urpm);
}

# Merge postponed exit code to the result of package installation.
$exit_code ||= $urpm::postponed_code;

#- restart urpmi if needed, keep command line for that.
if ($restart_itself && !$exit_code) {
    print N("restarting urpmi"), "\n";
    #- it seems to work correctly with exec instead of system, provided
    #- STDOUT or STDERR are not closed before (else no output at all).
    #- added --previous-priority-upgrade to allow checking if yet if
    #-   priority-upgrade list has changed. and make sure we don't uselessly restart
    #- renamed bug report dir as /restarted to avoid exit because it already exists
    #- This permits to have in a same dir bug reports before and after the restart
    @ARGV = @ARGVcopy;
    my @arg = ($ARGV[0], map {
	    $ARGV[$_] . ($ARGV[$_ - 1] eq '--bug' ? "/restarted" : "");
	} (1 .. $#ARGV));
    @arg = ('--previous-priority-upgrade=' . $urpm->{options}{'priority-upgrade'}, 
	    grep { !/^--no-priority-upgrade$|--previous-priority-upgrade=/ } @arg);
    exec $0, @arg;
}

#- 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;
}

# Show postponed message before exiting
print $urpm::postponed_msg if $urpm::postponed_code != 0;

exit($exit_code);