package urpm;
# $Id$
no warnings 'utf8';
use strict;
use File::Find ();
use urpm::msg;
use urpm::download;
use urpm::util;
use urpm::sys;
use urpm::cfg;
our $VERSION = '4.8.2';
our @ISA = qw(URPM);
use URPM;
use URPM::Resolve;
use POSIX;
BEGIN {
# this won't work in 5.10 when encoding::warnings will be lexical
if ($ENV{DEBUG_URPMI}) {
require encoding::warnings;
encoding::warnings->import;
}
}
#- create a new urpm object.
sub new {
my ($class) = @_;
my $self;
$self = bless {
# from URPM
depslist => [],
provides => {},
config => "/etc/urpmi/urpmi.cfg",
skiplist => "/etc/urpmi/skip.list",
instlist => "/etc/urpmi/inst.list",
statedir => "/var/lib/urpmi",
cachedir => "/var/cache/urpmi",
media => undef,
options => {},
#- sync: first argument is options hashref, others are urls to fetch.
sync => sub { $self->sync_webfetch(@_) },
fatal => sub { printf STDERR "%s\n", $_[1]; exit($_[0]) },
error => sub { printf STDERR "%s\n", $_[0] },
log => sub { printf "%s\n", $_[0] },
ui_msg => sub {
$self->{log}($_[0]);
ref $self->{ui} && ref $self->{ui}{msg} and $self->{ui}{msg}->($_[1]);
},
}, $class;
$self->set_nofatal(1);
$self;
}
#- syncing algorithms.
#- currently wget and curl methods are implemented; trying to find the best
#- (and one which will work :-)
sub sync_webfetch {
my $urpm = shift @_;
my $options = shift @_;
my %files;
#- currently ftp and http protocols are managed by curl or wget,
#- ssh and rsync protocols are managed by rsync *AND* ssh.
foreach (@_) {
/^([^:_]*)[^:]*:/ or die N("unknown protocol defined for %s", $_);
push @{$files{$1}}, $_;
}
if ($files{removable} || $files{file}) {
eval {
sync_file($options, @{$files{removable} || []}, @{$files{file} || []});
};
$urpm->{fatal}(10, $@) if $@;
delete @files{qw(removable file)};
}
if ($files{ftp} || $files{http} || $files{https}) {
my @webfetch = qw(curl wget);
my @available_webfetch = grep { -x "/usr/bin/$_" } @webfetch;
my $preferred;
#- use user default downloader if provided and available
my $option_downloader = $urpm->{options}{downloader}; #- cmd-line switch
if (!$option_downloader && $options->{media}) { #- per-media config
(my $m) = grep { $_->{name} eq $options->{media} } @{$urpm->{media}};
ref $m && $m->{downloader} and $option_downloader = $m->{downloader};
}
#- global config
!$option_downloader && exists $urpm->{global_config}{downloader}
and $option_downloader = $urpm->{global_config}{downloader};
if ($option_downloader) {
($preferred) = grep { $_ eq $option_downloader } @available_webfetch;
}
#- else first downloader of @webfetch is the default one
$preferred ||= $available_webfetch[0];
if ($preferred eq 'curl') {
sync_curl($options, @{$files{ftp} || []}, @{$files{http} || []}, @{$files{https} || []});
} elsif ($preferred eq 'wget') {
sync_wget($options, @{$files{ftp} || []}, @{$files{http} || []}, @{$files{https} || []});
} else {
die N("no webfetch found, supported webfetch are: %s\n", join(", ", @webfetch));
}
delete @files{qw(ftp http https)};
}
if ($files{rsync}) {
sync_rsync($options, @{$files{rsync} || []});
delete $files{rsync};
}
if ($files{ssh}) {
my @ssh_files;
foreach (@{$files{ssh} || []}) {
m|^ssh://([^/]*)(.*)| and push @ssh_files, "$1:$2";
}
sync_ssh($options, @ssh_files);
delete $files{ssh};
}
%files and die N("unable to handle protocol: %s", join ', ', keys %files);
}
our @PER_MEDIA_OPT = qw(
downloader
hdlist
ignore
key-ids
list
md5sum
noreconfigure
priority
removable
static
synthesis
update
verify-rpm
virtual
with_hdlist
);
#- Loads /etc/urpmi/urpmi.cfg and performs basic checks.
#- Does not handle old format: <name> <url> [with <path_hdlist>]
#- options :
#- - nocheck_access : don't check presence of hdlist and other files
sub read_config {
my ($urpm, %options) = @_;
return if $urpm->{media}; #- media already loaded
$urpm->{media} = [];
my $config = urpm::cfg::load_config($urpm->{config})
or $urpm->{fatal}(6, $urpm::cfg::err);
#- global options
if ($config->{''}) {
foreach my $opt (qw(
allow-force
allow-nodeps
auto
compress
downloader
excludedocs
excludepath
fuzzy
keep
key-ids
limit-rate
nopubkey
norebuild
post-clean
pre-clean
priority-upgrade
prohibit-remove
resume
retry
split-length
split-level
strict-arch
verify-rpm
)) {
if (defined $config->{''}{$opt} && !exists $urpm->{options}{$opt}) {
$urpm->{options}{$opt} = $config->{''}{$opt};
}
}
}
#- per-media options
foreach my $m (grep { $_ ne '' } keys %$config) {
my $medium = { name => $m, clear_url => $config->{$m}{url} };
foreach my $opt (@PER_MEDIA_OPT) {
defined $config->{$m}{$opt} and $medium->{$opt} = $config->{$m}{$opt};
}
$urpm->probe_medium($medium, %options) and push @{$urpm->{media}}, $medium;
}
eval { require urpm::ldap; urpm::ldap::load_ldap_media($urpm, %options) };
#- load default values
foreach (qw(post-clean verify-rpm)) {
exists $urpm->{options}{$_} or $urpm->{options}{$_} = 1;
}
$urpm->{media} = [ sort { $a->{priority} <=> $b->{priority} } @{$urpm->{media}} ];
#- remember if an hdlist or list file is already used
my %filelists;
foreach (@{$urpm->{media}}) {
foreach my $filetype (qw(hdlist list)) {
if ($_->{$filetype}) {
exists($filelists{$filetype}{$_->{$filetype}})
and $_->{ignore} = 1,
$urpm->{error}(
$filetype eq 'hdlist'
? N("medium \"%s\" trying to use an already used hdlist, medium ignored", $_->{name})
: N("medium \"%s\" trying to use an already used list, medium ignored", $_->{name})
);
$filelists{$filetype}{$_->{$filetype}} = undef;
}
}
}
#- check the presence of hdlist and list files if necessary.
unless ($options{nocheck_access}) {
foreach (@{$urpm->{media}}) {
$_->{ignore} and next;
-r "$urpm->{statedir}/$_->{hdlist}" || -r "$urpm->{statedir}/synthesis.$_->{hdlist}" && $_->{synthesis}
or $_->{ignore} = 1,
$urpm->{error}(N("unable to access hdlist file of \"%s\", medium ignored", $_->{name}));
$_->{list} && -r "$urpm->{statedir}/$_->{list}" || defined $_->{url}
or $_->{ignore} = 1,
$urpm->{error}(N("unable to access list file of \"%s\", medium ignored", $_->{name}));
}
}
#- read MD5 sums (usually not in urpmi.cfg but in a separate file)
my $md5sum = $urpm->open_safe("<", "$urpm->{statedir}/MD5SUM");
if ($md5sum) {
while (<$md5sum>) {
my ($md5sum, $file) = /(\S*)\s+(.*)/;
foreach (@{$urpm->{media}}) {
($_->{synthesis} ? "synthesis." : "") . $_->{hdlist} eq $file
and $_->{md5sum} = $md5sum, last;
}
}
close $md5sum;
}
#- remember global options for write_config
$urpm->{global_config} = $config->{''};
}
#- probe medium to be used, take old medium into account too.
sub probe_medium {
my ($urpm, $medium, %options) = @_;
local $_;
foreach (@{$urpm->{media}}) {
if ($_->{name} eq $medium->{name}) {
$urpm->{error}(N("trying to override existing medium \"%s\", skipping", $medium->{name}));
return;
}
}
$medium->{url} ||= $medium->{clear_url};
if ($medium->{virtual}) {
#- a virtual medium needs to have an url available without using a list file.
if ($medium->{hdlist} || $medium->{list}) {
$medium->{ignore} = 1;
$urpm->{error}(N("virtual medium \"%s\" should not have defined hdlist or list file, medium ignored",
$medium->{name}));
}
unless ($medium->{url}) {
$medium->{ignore} = 1;
$urpm->{error}(N("virtual medium \"%s\" should have a clear url, medium ignored",
$medium->{name}));
}
} else {
unless ($medium->{ignore} || $medium->{hdlist}) {
$medium->{hdlist} = "hdlist.$medium->{name}.cz";
-e "$urpm->{statedir}/$medium->{hdlist}" or $medium->{hdlist} = "hdlist.$medium->{name}.cz2";
-e "$urpm->{statedir}/$medium->{hdlist}" or
$medium->{ignore} = 1,
$urpm->{error}(N("unable to find hdlist file for \"%s\", medium ignored", $medium->{name}));
}
unless ($medium->{ignore} || $medium->{list}) {
unless (defined $medium->{url}) {
$medium->{list} = "list.$medium->{name}";
unless (-e "$urpm->{statedir}/$medium->{list}") {
$medium->{ignore} = 1,
$urpm->{error}(N("unable to find list file for \"%s\", medium ignored", $medium->{name}));
}
}
}
#- there is a little more to do at this point as url is not known, inspect directly list file for it.
unless ($medium->{url}) {
my %probe;
if (-r "$urpm->{statedir}/$medium->{list}") {
my $listfile = $urpm->open_safe("<", "$urpm->{statedir}/$medium->{list}");
if ($listfile) {
while (<$listfile>) {
#- /./ is end of url marker in list file (typically generated by a
#- find . -name "*.rpm" > list
#- for exportable list file.
m|^(.*)/\./| and $probe{$1} = undef;
m|^(.*)/[^/]*$| and $probe{$1} = undef;
}
close $listfile;
}
}
foreach (sort { length($a) <=> length($b) } keys %probe) {
if ($medium->{url}) {
if ($medium->{url} ne substr($_, 0, length($medium->{url}))) {
$medium->{ignore} or $urpm->{error}(N("inconsistent list file for \"%s\", medium ignored", $medium->{name}));
$medium->{ignore} = 1;
last;
}
} else {
$medium->{url} = $_;
}
}
unless ($options{nocheck_access}) {
unless ($medium->{url}) {
$medium->{ignore} or $urpm->{error}(N("unable to inspect list file for \"%s\", medium ignored", $medium->{name}));
$medium->{ignore} = 1;
}
}
}
}
#- probe removable device.
$urpm->probe_removable_device($medium);
#- clear URLs for trailing /es.
$medium->{url} and $medium->{url} =~ s|(.*?)/*$|$1|;
$medium->{clear_url} and $medium->{clear_url} =~ s|(.*?)/*$|$1|;
$medium;
}
#- returns the removable device name if it corresponds to an iso image, '' otherwise
sub is_iso {
my ($removable_dev) = @_;
$removable_dev && $removable_dev =~ /\.iso$/i ? $removable_dev : '';
}
#- probe device associated with a removable device.
sub probe_removable_device {
my ($urpm, $medium) = @_;
#- try to find device name in url scheme
if ($medium->{url} && $medium->{url} =~ /^removable_?([^_:]*)(?:_[^:]*)?:/) {
$medium->{removable} ||= $1 && "/dev/$1";
} else {
delete $medium->{removable};
}
#- try to find device to open/close for removable medium.
if (exists($medium->{removable})) {
if (my ($dir) = $medium->{url} =~ m!^(?:(?:file|removable)[^:]*:/)?(/.*)!) {
my %infos;
my @mntpoints = urpm::sys::find_mntpoints($dir, \%infos);
if (@mntpoints > 1) { #- return value is suitable for an hash.
$urpm->{log}(N("too many mount points for removable medium \"%s\"", $medium->{name}));
$urpm->{log}(N("taking removable device as \"%s\"", join ',', map { $infos{$_}{device} } @mntpoints));
}
if (is_iso($medium->{removable})) {
$urpm->{log}(N("Medium \"%s\" is an ISO image, will be mounted on-the-fly", $medium->{name}));
} elsif (@mntpoints) {
if ($medium->{removable} && $medium->{removable} ne $infos{$mntpoints[-1]}{device}) {
$urpm->{log}(N("using different removable device [%s] for \"%s\"",
$infos{$mntpoints[-1]}{device}, $medium->{name}));
}
$medium->{removable} = $infos{$mntpoints[-1]}{device};
} else {
$urpm->{error}(N("unable to retrieve pathname for removable medium \"%s\"", $medium->{name}));
}
} else {
$urpm->{error}(N("unable to retrieve pathname for removable medium \"%s\"", $medium->{name}));
}
}
}
#- Writes the urpmi.cfg file.
sub write_config {
my ($urpm) = @_;
#- avoid trashing exiting configuration if it wasn't loaded
$urpm->{media} or return;
my $config = {
#- global config options found in the config file, without the ones
#- set from the command-line
'' => $urpm->{global_config},
};
foreach my $medium (@{$urpm->{media}}) {
next if $medium->{external};
my $medium_name = $medium->{name};
$config->{$medium_name}{url} = $medium->{clear_url};
foreach (qw(hdlist with_hdlist list removable key-ids priority priority-upgrade update noreconfigure static ignore synthesis virtual)) {
defined $medium->{$_} and $config->{$medium_name}{$_} = $medium->{$_};
}
}
urpm::cfg::dump_config($urpm->{config}, $config)
or $urpm->{fatal}(6, N("unable to write config file [%s]", $urpm->{config}));
#- write MD5SUM file
my $md5sum = $urpm->open_safe('>', "$urpm->{statedir}/MD5SUM") or return 0;
foreach my $medium (@{$urpm->{media}}) {
$medium->{md5sum}
and print $md5sum "$medium->{md5sum} " . ($medium->{synthesis} && "synthesis.") . $medium->{hdlist} . "\n";
}
close $md5sum;
$urpm->{log}(N("wrote config file [%s]", $urpm->{config}));
#- everything should be synced now.
delete $urpm->{modified};
}
#- read urpmi.cfg file as well as necessary synthesis files
#- options :
#- callback
#- call_back_only_once
#- excludemedia
#- hdlist
#- media
#- nodepslist
#- noinstalling
#- noskipping
#- parallel
#- root
#- searchmedia
#- skip
#- sortmedia
#- update
#- usedistrib
sub configure {
my ($urpm, %options) = @_;
$urpm->clean;
$options{parallel} && $options{usedistrib} and $urpm->{fatal}(1, N("Can't use parallel mode with use-distrib mode"));
if ($options{parallel}) {
my ($parallel_options, $parallel_handler);
#- read parallel configuration
local $_;
my $parallel = $urpm->open_safe("<", "/etc/urpmi/parallel.cfg");
if ($parallel) {
while (<$parallel>) {
chomp; s/#.*$//; s/^\s*//; s/\s*$//;
/\s*([^:]*):(.*)/ or $urpm->{error}(N("unable to parse \"%s\" in file [%s]", $_, "/etc/urpmi/parallel.cfg")), next;
$1 eq $options{parallel} and $parallel_options = ($parallel_options && "\n") . $2;
}
close $parallel;
}
#- if a configuration option has been found, use it; else fatal error.
if ($parallel_options) {
foreach my $dir (grep { -d $_ } map { "$_/urpm" } @INC) {
my $dh = $urpm->opendir_safe($dir);
if ($dh) {
while (defined ($_ = readdir $dh)) { #- load parallel modules
/parallel.*\.pm$/ && -f "$dir/$_" or next;
$urpm->{log}->(N("examining parallel handler in file [%s]", "$dir/$_"));
eval { require "$dir/$_"; $parallel_handler = $urpm->handle_parallel_options($parallel_options) };
$parallel_handler and last;
}
closedir $dh;
}
$parallel_handler and last;
}
}
if ($parallel_handler) {
if ($parallel_handler->{nodes}) {
$urpm->{log}->(N("found parallel handler for nodes: %s", join(', ', keys %{$parallel_handler->{nodes}})));
}
if (!$options{media} && $parallel_handler->{media}) {
$options{media} = $parallel_handler->{media};
$urpm->{log}->(N("using associated media for parallel mode: %s", $options{media}));
}
$urpm->{parallel_handler} = $parallel_handler;
} else {
|