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; use MDV::Distribconf; our $VERSION = '4.8.29'; our @ISA = qw(URPM); use URPM; use URPM::Resolve; my $RPMLOCK_FILE; my $LOCK_FILE; #- this violently overrides is_arch_compat() to always return true. sub shunt_ignorearch { eval q( sub URPM::Package::is_arch_compat { 1 } ); } #- 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", private_netrc => "/etc/urpmi/netrc", statedir => "/var/lib/urpmi", cachedir => "/var/cache/urpmi", media => undef, options => {}, 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; } sub requested_ftp_http_downloader { my ($urpm, $media_name) = @_; $urpm->{options}{downloader} || #- cmd-line switch $media_name && do { #- per-media config my $m = name2medium($urpm, $media_name); $m && $m->{downloader}; } || $urpm->{global_config}{downloader}; } #- $medium can be undef #- known options: quiet, resume, callback sub sync_webfetch { my ($urpm, $medium, $files, %options) = @_; my %all_options = ( dir => "$urpm->{cachedir}/partial", proxy => get_proxy($medium), $medium ? (media => $medium->{name}) : (), %options, ); foreach my $cpt (qw(compress limit_rate retry wget-options curl-options rsync-options prozilla-options)) { $all_options{$cpt} = $urpm->{options}{$cpt} if defined $urpm->{options}{$cpt}; } eval { _sync_webfetch_raw($urpm, $files, \%all_options); 1 }; } #- syncing algorithms. sub _sync_webfetch_raw { my ($urpm, $files, $options) = @_; my %files; #- currently ftp and http protocols are managed by curl or wget, #- ssh and rsync protocols are managed by rsync *AND* ssh. foreach (@$files) { my $proto = protocol_from_url($_) or die N("unknown protocol defined for %s", $_); push @{$files{$proto}}, $_; } if ($files{removable} || $files{file}) { my @l = map { file_from_local_url($_) } @{$files{removable} || []}, @{$files{file} || []}; eval { sync_file($options, @l) }; $urpm->{fatal}(10, $@) if $@; delete @files{qw(removable file)}; } if ($files{ftp} || $files{http} || $files{https}) { my @available = urpm::download::available_ftp_http_downloaders(); #- use user default downloader if provided and available my $requested_downloader = requested_ftp_http_downloader($urpm, $options->{media}); my ($preferred) = grep { $_ eq $requested_downloader } @available; if (!$preferred) { #- else first downloader of @available is the default one $preferred = $available[0]; if ($requested_downloader && !our $webfetch_not_available) { $urpm->{log}(N("%s is not available, falling back on %s", $requested_downloader, $preferred)); $webfetch_not_available = 1; } } my $sync = $urpm::download::{"sync_$preferred"} or die N("no webfetch found, supported webfetch are: %s\n", join(", ", urpm::download::ftp_http_downloaders())); $sync->($options, @{$files{ftp} || []}, @{$files{http} || []}, @{$files{https} || []}); delete @files{qw(ftp http https)}; } if ($files{rsync}) { sync_rsync($options, @{$files{rsync}}); delete $files{rsync}; } if ($files{ssh}) { my @ssh_files = map { m!^ssh://([^/]*)(.*)! ? "$1:$2" : () } @{$files{ssh}}; 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 priority-upgrade removable static synthesis update url verify-rpm virtual with_hdlist ); sub read_private_netrc { my ($urpm) = @_; my @words = split(/\s+/, scalar cat_($urpm->{private_netrc})); my @l; my $e; while (@words) { my $keyword = shift @words; if ($keyword eq 'machine') { push @l, $e = { machine => shift(@words) }; } elsif ($keyword eq 'default') { push @l, $e = { default => '' }; } elsif ($keyword eq 'login' || $keyword eq 'password' || $keyword eq 'account') { $e->{$keyword} = shift(@words); } else { $urpm->{error}("unknown netrc command $keyword"); } } @l; } sub parse_url_with_login { my ($url) = @_; $url =~ m!([^:]*)://([^/:\@]*)(:([^/:\@]*))?\@([^/]*)(.*)! && { proto => $1, login => $2, password => $4, machine => $5, dir => $6 }; } sub read_config_add_passwords { my ($urpm, $config) = @_; my @netrc = read_private_netrc($urpm) or return; foreach (values %$config) { my $u = parse_url_with_login($_->{url}) or next; if (my ($e) = grep { ($_->{default} || $_->{machine} eq $u->{machine}) && $_->{login} eq $u->{login} } @netrc) { $_->{url} = sprintf('%s://%s:%s@%s%s', $u->{proto}, $u->{login}, $e->{password}, $u->{machine}, $u->{dir}); } else { $urpm->{log}("no password found for $u->{login}@$u->{machine}"); } } } sub remove_passwords_and_write_private_netrc { my ($urpm, $config) = @_; my @l; foreach (values %$config) { my $u = parse_url_with_login($_->{url}) or next; #- check whether a password is visible $u->{password} or next; push @l, $u; $_->{url} = sprintf('%s://%s@%s%s', $u->{proto}, $u->{login}, $u->{machine}, $u->{dir}); } { my $fh = $urpm->open_safe('>', $urpm->{private_netrc}) or return; foreach my $u (@l) { printf $fh "machine %s login %s password %s\n", $u->{machine}, $u->{login}, $u->{password}; } } chmod 0600, $urpm->{private_netrc}; } #- handle deprecated way of saving passwords sub recover_url_from_list { my ($urpm, $medium) = @_; #- /./ is end of url marker in list file (typically generated by a #- find . -name "*.rpm" > list #- for exportable list file. if (my @probe = map { m!^(.*)/\./! || m!^(.*)/[^/]*$! } cat_(statedir_list($urpm, $medium))) { ($medium->{url}) = sort { length($a) <=> length($b) } @probe; $urpm->{modified} = 1; #- ensure urpmi.cfg is handled using only partially hidden url + netrc, since file list won't be generated anymore } } #- Loads /etc/urpmi/urpmi.cfg and performs basic checks. #- Does not handle old format: [with ] #- options : #- - nocheck_access : don't check presence of hdlist and other files sub read_config { my ($urpm, $b_nocheck_access) = @_; 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 (my $global = $config->{''}) { foreach my $opt (keys %$global) { if (defined $global->{$opt} && !exists $urpm->{options}{$opt}) { $urpm->{options}{$opt} = $global->{$opt}; } } } #- per-media options read_config_add_passwords($urpm, $config); foreach my $m (grep { $_ ne '' } keys %$config) { my $medium = { name => $m }; foreach my $opt (@PER_MEDIA_OPT) { defined $config->{$m}{$opt} and $medium->{$opt} = $config->{$m}{$opt}; } if (!$medium->{url}) { #- recover the url the old deprecated way... #- only useful for migration, new urpmi.cfg will use netrc recover_url_from_list($urpm, $medium); $medium->{url} or $urpm->{error}("unable to find url in list file $medium->{name}, medium ignored"); } $urpm->add_existing_medium($medium, $b_nocheck_access); } eval { require urpm::ldap; urpm::ldap::load_ldap_media($urpm) }; #- 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}} ]; #- read MD5 sums (usually not in urpmi.cfg but in a separate file) foreach (@{$urpm->{media}}) { if (my $md5sum = get_md5sum("$urpm->{statedir}/MD5SUM", statedir_hdlist_or_synthesis($urpm, $_))) { $_->{md5sum} = $md5sum; } } #- remember global options for write_config $urpm->{global_config} = $config->{''}; } #- if invalid, set {ignore} sub check_existing_medium { my ($urpm, $medium, $b_nocheck_access) = @_; 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 { if ($medium->{hdlist}) { #- is this check really needed? keeping just in case $medium->{hdlist} ne 'list' && $medium->{hdlist} ne 'pubkey' or $medium->{ignore} = 1, $urpm->{error}(N("invalid hdlist name")); } if (!$medium->{ignore} && !$medium->{hdlist}) { $medium->{hdlist} = "hdlist.$medium->{name}.cz"; -e statedir_hdlist($urpm, $medium) or $medium->{ignore} = 1, $urpm->{error}(N("unable to find hdlist file for \"%s\", medium ignored", $medium->{name})); } if (!$medium->{ignore} && !$medium->{list}) { unless (defined $medium->{url}) { $medium->{list} = "list.$medium->{name}"; unless (-e statedir_list($urpm, $medium)) { $medium->{ignore} = 1, $urpm->{error}(N("unable to find list file for \"%s\", medium ignored", $medium->{name})); } } } } #- check the presence of hdlist and list files if necessary. if (!$b_nocheck_access && !$medium->{ignore}) { if ($medium->{virtual} && -r hdlist_or_synthesis_for_virtual_medium($medium)) {} elsif (-r statedir_hdlist($urpm, $medium)) {} elsif ($medium->{synthesis} && -r statedir_synthesis($urpm, $medium)) {} else { $medium->{ignore} = 1; $urpm->{error}(N("unable to access hdlist file of \"%s\", medium ignored", $medium->{name})); } if ($medium->{list} && -r statedir_list($urpm, $medium)) {} elsif ($medium->{url}) {} else { $medium->{ignore} = 1; $urpm->{error}(N("unable to access list file of \"%s\", medium ignored", $medium->{name})); } } foreach my $field ('hdlist', 'list') { $medium->{$field} or next; if (grep { $_->{$field} eq $medium->{$field} } @{$urpm->{media}}) { $medium->{ignore} = 1; $urpm->{error}( $field eq 'hdlist' ? N("medium \"%s\" trying to use an already used hdlist, medium ignored", $medium->{name}) : N("medium \"%s\" trying to use an already used list, medium ignored", $medium->{name})); } } } #- probe medium to be used, take old medium into account too. sub add_existing_medium { my ($urpm, $medium, $b_nocheck_access) = @_; if (name2medium($urpm, $medium->{name})) { $urpm->{error}(N("trying to override existing medium \"%s\", skipping", $medium->{name})); return; } check_existing_medium($urpm, $medium, $b_nocheck_access); #- probe removable device. $urpm->probe_removable_device($medium); #- clear URLs for trailing /es. $medium->{url} and $medium->{url} =~ s|(.*?)/*$|$1|; push @{$urpm->{media}}, $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; } sub protocol_from_url { my ($url) = @_; $url =~ m!^([^:_]*)[^:]*:! && $1; } sub file_from_local_url { my ($url) = @_; $url =~ m!^(?:removable[^:]*:/|file:/)?(/.*)! && $1; } sub file_from_file_url { my ($url) = @_; $url =~ m!^(?:file:/)?(/.*)! && $1; } sub hdlist_or_synthesis_for_virtual_medium { my ($medium) = @_; my $path = file_from_file_url($medium->{url}) or return; "$path/$medium->{with_hdlist}"; } sub statedir_hdlist_or_synthesis { my ($urpm, $medium) = @_; $medium->{hdlist} && "$urpm->{statedir}/" . ($medium->{synthesis} ? 'synthesis.' : '') . $medium->{hdlist}; } sub statedir_hdlist { my ($urpm, $medium) = @_; $medium->{hdlist} && "$urpm->{statedir}/$medium->{hdlist}"; } sub statedir_synthesis { my ($urpm, $medium) = @_; $medium->{hdlist} && "$urpm->{statedir}/synthesis.$medium->{hdlist}"; } sub statedir_list { my ($urpm, $medium) = @_; $medium->{list} && "$urpm->{statedir}/$medium->{list}"; } sub statedir_descriptions { my ($urpm, $medium) = @_; $medium->{name} && "$urpm->{statedir}/descriptions.$medium->{name}"; } sub statedir_names { my ($urpm, $medium) = @_; $medium->{name} && "$urpm->{statedir}/names.$medium->{name}"; } sub cachedir_hdlist { my ($urpm, $medium) = @_; $medium->{hdlist} && "$urpm->{cachedir}/partial/$medium->{hdlist}"; } sub cachedir_list { my ($urpm, $medium) = @_; $medium->{list} && "$urpm->{cachedir}/partial/$medium->{list}"; } sub name2medium { my ($urpm, $name) = @_; my ($medium) = grep { $_->{name} eq $name } @{$urpm->{media}}; $medium; } #- probe device associated with a removable device. sub probe_removable_device { my ($urpm, $medium) = @_; if ($medium->{url} && $medium->{url} =~ /^removable/) { #- try to find device name in url scheme, this is deprecated, use medium option "removable" instead if ($medium->{url} =~ /^removable_?([^_:]*)/) { $medium->{removable} ||= $1 && "/dev/$1"; } } else { delete $medium->{removable}; return; } #- try to find device to open/close for removable medium. if (my $dir = file_from_local_url($medium->{url})) { 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})); } } sub write_MD5SUM { my ($urpm) = @_; #- write MD5SUM file my $fh = $urpm->open_safe('>', "$urpm->{statedir}/MD5SUM") or return 0; foreach my $medium (grep { $_->{md5sum} } @{$urpm->{media}}) { my $s = basename(statedir_hdlist_or_synthesis($urpm, $medium)); print $fh "$medium->{md5sum} $s\n"; } $urpm->{log}(N("wrote %s", "$urpm->{statedir}/MD5SUM")); delete $urpm->{md5sum_modified}; } #- Writes the urpmi.cfg file. sub write_urpmi_cfg { 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}; foreach (@PER_MEDIA_OPT) { defined $medium->{$_} and $config->{$medium_name}{$_} = $medium->{$_}; } } remove_passwords_and_write_private_netrc($urpm, $config); urpm::cfg::dump_config($urpm->{config}, $config) or $urpm->{fatal}(6, N("unable to write config file [%s]", $urpm->{config})); $urpm->{log}(N("wrote config file [%s]", $urpm->{config})); #- everything should be synced now. delete $urpm->{modified}; } sub write_config { my ($urpm) = @_; write_urpmi_cfg($urpm); write_MD5SUM($urpm); } sub _configure_parallel { my ($urpm, $alias) = @_; my @parallel_options; #- read parallel configuration foreach (cat_("/etc/urpmi/parallel.cfg")) { 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 $alias and push @parallel_options, $2; } #- if a configuration option has been found, use it; else fatal error. my $parallel_handler; if (@parallel_options) { foreach my $dir (grep { -d $_ } map { "$_/urpm" } @INC) { foreach my $pm (grep { -f $_ } glob("$dir/parallel*.pm")) { #- load parallel modules $urpm->{log}->(N("examining parallel handler in file [%s]", $pm)); # perl_checker: require urpm::parallel_ka_run # perl_checker: require urpm::parallel_ssh eval { require $pm; $parallel_handler = $urpm->handle_parallel_options(join("\n", @parallel_options)) }; $parallel_handler and last; } $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}}))); } $urpm->{parallel_handler} = $parallel_handler; } else { $urpm->{fatal}(1, N("unable to use parallel option \"%s\"", $alias)); } } #- read urpmi.cfg file as well as necessary synthesis files #- options : #- root #- cmdline_skiplist #- nocheck_access (used by read_config) #- #- callback (urpmf) #- need_hdlist (for urpmf: to be able to have info not available in synthesis) #- nodepslist (for urpmq: we don't need the hdlist/synthesis) #- no_skiplist (urpmf) #- #- synthesis (use this synthesis file, and only this synthesis file) #- #- usedistrib (otherwise uses urpmi.cfg) #- parallel #- media #- excludemedia #- sortmedia #- #- update #- searchmedia 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}) { _configure_parallel($urpm, $options{parallel}); if (!$options{media} && $urpm->{parallel_handler}{media}) { $options{media} = $urpm->{parallel_handler}{media}; $urpm->{log}->(N("using associated media for parallel mode: %s", $options{media})); } } else { #- nb: can't have both parallel and root $urpm->{root} = $options{root}; } $urpm->{root} && ! -c "$urpm->{root}/dev/null" and $urpm->{error}(N("there doesn't seem to be devices in the chroot in \"%s\"", $urpm->{root})); if ($options{synthesis}) { if ($options{synthesis} ne 'none') { #- synthesis take precedence over media, update options. $options{media} || $options{excludemedia} || $options{sortmedia} || $options{update} || $options{usedistrib} || $options{parallel} and $urpm->{fatal}(1, N("--synthesis cannot be used with --media, --excludemedia, --sortmedia, --update, --use-distrib or --parallel")); $urpm->parse_synthesis($options{synthesis}); #- synthesis disables the split of transaction (too risky and not useful). $urpm->{options}{'split-length'} = 0; } } else { if ($options{usedistrib}) { $urpm->{media} = []; $urpm->add_distrib_media("Virtual", $options{usedistrib}, %options, 'virtual' => 1); } else { $urpm->read_config($options{nocheck_access}); if (!$options{media} && $urpm->{options}{'default-media'}) { $options{media} = $urpm->{options}{'default-media'}; } } if ($options{media}) { delete $_->{modified} foreach @{$urpm->{media} || []}; $urpm->select_media(split /,/, $options{media}); foreach (grep { !$_->{modified} } @{$urpm->{media} || []}) { #- this is only a local ignore that will not be saved. $_->{tempignore} = $_->{ignore} = 1; } } if ($options{searchmedia}) { $urpm->select_media($options{searchmedia}); #- Ensure this media has been selected foreach (grep { !$_->{ignore} } @{$urpm->{media} || []}) { $_->{name} eq $options{searchmedia} and do { $_->{searchmedia} = 1; last; }; } } if ($options{excludemedia}) { delete $_->{modified} foreach @{$urpm->{media} || []}; foreach (select_media_by_name($urpm, [ split /,/, $options{excludemedia} ], {})) { $_->{modified} = 1; #- this is only a local ignore that will not be saved. $_->{tempignore} = $_->{ignore} = 1; } } if ($options{sortmedia}) { my @sorted_media = map { select_media_by_name($urpm, [$_], {}) } split(/,/, $options{sortmedia}); my @remaining = difference2($urpm->{media}, \@sorted_media); $urpm->{media} = [ @sorted_media, @remaining ]; } unless ($options{nodepslist}) { my $second_pass; do { foreach (grep { !$_->{ignore} && (!$options{update} || $_->{update}) } @{$urpm->{media} || []}) { our $currentmedia = $_; #- hack for urpmf delete @$_{qw(start end)}; if ($_->{virtual}) { if (file_from_file_url($_->{url})) { if ($_->{synthesis}) { _parse_synthesis($urpm, $_, hdlist_or_synthesis_for_virtual_medium($_), $options{callback}); } else { #- we'll need a second pass defined $second_pass or $second_pass = 1; _parse_hdlist($urpm, $_, hdlist_or_synthesis_for_virtual_medium($_), $second_pass ? undef : $options{callback}, ); } } else { $urpm->{error}(N("virtual medium \"%s\" is not local, medium ignored", $_->{name})); $_->{ignore} = 1; } } else { if ($options{need_hdlist} && file_size(statedir_hdlist($urpm, $_)) > 32) { _parse_hdlist($urpm, $_, statedir_hdlist($urpm, $_), $options{callback}); } else { if (!_parse_synthesis($urpm, $_, statedir_synthesis($urpm, $_), $options{callback})) { _parse_hdlist($urpm, $_, statedir_hdlist($urpm, $_), $options{callback}); } } } unless ($_->{ignore}) { _check_after_reading_hdlist_or_synthesis($urpm, $_); } unless ($_->{ignore}) { if ($_->{searchmedia}) { ($urpm->{searchmedia}{start}, $urpm->{searchmedia}{end}) = ($_->{start}, $_->{end}); $urpm->{log}(N("Search start: %s end: %s", $urpm->{searchmedia}{start}, $urpm->{searchmedia}{end})); delete $_->{searchmedia}; } } } } while $second_pass && do { require URPM::Build; $urpm->{log}(N("performing second pass to compute dependencies\n")); $urpm->unresolved_provides_clean; $second_pass--; }; } } #- determine package to withdraw (from skip.list file) only if something should be withdrawn. unless ($options{nodepslist} || $options{no_skiplist}) { my %uniq; $urpm->compute_flags( get_packages_list($urpm->{skiplist}, $options{cmdline_skiplist}), skip => 1, callback => sub { my ($urpm, $pkg) = @_; $pkg->is_arch_compat && ! exists $uniq{$pkg->fullname} or return; $uniq{$pkg->fullname} = undef; $urpm->{log}(N("skipping package %s", scalar($pkg->fullname))); }, ); } unless ($options{nodepslist}) { my %uniq; $urpm->compute_flags( get_packages_list($urpm->{instlist}), disable_obsolete => 1, callback => sub { my ($urpm, $pkg) = @_; $pkg->is_arch_compat && ! exists $uniq{$pkg->fullname} or return; $uniq{$pkg->fullname} = undef; $urpm->{log}(N("would install instead of upgrade package %s", scalar($pkg->fullname))); }, ); } } #- add a new medium, sync the config file accordingly. #- returns the new medium's name. (might be different from the requested #- name if index_name was specified) #- options: ignore, index_name, nolock, update, virtual sub add_medium { my ($urpm, $name, $url, $with_hdlist, %options) = @_; #- make sure configuration has been read. $urpm->{media} or die "caller should have used ->read_config or ->configure first"; $urpm->lock_urpmi_db('exclusive') if !$options{nolock}; #- if a medium with that name has already been found, we have to exit now my $medium; if (defined $options{index_name}) { my $i = $options{index_name}; do { ++$i; $medium = name2medium($urpm, $name . $i); } while $medium; $name .= $i; } else { $medium = name2medium($urpm, $name); } $medium and $urpm->{fatal}(5, N("medium \"%s\" already exists", $medium->{name})); $url =~ s,/*$,,; #- clear URLs for trailing /es. #- creating the medium info. $medium = { name => $name, url => $url, update => $options{update}, modified => 1, ignore => $options{ignore} }; if ($options{virtual}) { file_from_file_url($url) or $urpm->{fatal}(1, N("virtual medium needs to be local")); $medium->{virtual} = 1; } else { $medium->{hdlist} = "hdlist.$name.cz"; $medium->{list} = "list.$name"; $urpm->probe_removable_device($medium); } #- local media have priority, other are added at the end. if (file_from_file_url($url)) { $medium->{priority} = 0.5; } else { $medium->{priority} = 1 + @{$urpm->{media}}; } $with_hdlist and $medium->{with_hdlist} = $with_hdlist; #- create an entry in media list. push @{$urpm->{media}}, $medium; $urpm->{log}(N("added medium %s", $name)); $urpm->{modified} = 1; $options{nolock} or $urpm->unlock_urpmi_db; $name; } #- add distribution media, according to url given. #- returns the list of names of added media. #- options : #- - initial_number : when adding several numbered media, start with this number #- - probe_with : if eq 'synthesis', use synthesis instead of hdlists #- - ask_media : callback to know whether each media should be added #- other options are passed to add_medium() sub add_distrib_media { my ($urpm, $name, $url, %options) = @_; #- make sure configuration has been read. $urpm->{media} or die "caller should have used ->read_config or ->configure first"; my $distribconf; if (my $dir = file_from_local_url($url)) { $urpm->try_mounting($dir) or $urpm->{error}(N("unable to mount the distribution medium")), return (); $distribconf = MDV::Distribconf->new($dir, undef); $distribconf->load or $urpm->{error}(N("this location doesn't seem to contain any distribution")), return (); } else { unlink "$urpm->{cachedir}/partial/media.cfg"; $distribconf = MDV::Distribconf->new($url, undef); $distribconf->settree('mandriva'); $urpm->{log}(N("retrieving media.cfg file...")); if (sync_webfetch($urpm, undef, [ reduce_pathname($distribconf->getfullpath(undef, 'infodir') . '/media.cfg') ], quiet => 1)) { $urpm->{log}(N("...retrieving done")); $distribconf->parse_mediacfg("$urpm->{cachedir}/partial/media.cfg") or $urpm->{error}(N("unable to parse media.cfg")), return(); } else { $urpm->{error}(N("...retrieving failed: %s", $@)); $urpm->{error}(N("unable to access the distribution medium (no media.cfg file found)")); return (); } } #- cosmetic update of name if it contains spaces. $name =~ /\s/ and $name .= ' '; my @newnames; #- at this point, we have found a media.cfg file, so parse it #- and create all necessary media according to it. my $medium = $options{initial_number} || 1; foreach my $media ($distribconf->listmedia) { my $skip = 0; # if one of those values is set, by default, we skip adding the media foreach (qw(noauto)) { $distribconf->getvalue($media, $_) and do { $skip = 1; last; }; } if ($options{ask_media}) { if ($options{ask_media}->( $distribconf->getvalue($media, 'name'), !$skip, )) { $skip = 0; } else { $skip = 1; } } $skip and next; my $media_name = $distribconf->getvalue($media, 'name') || ''; my $is_update_media = $distribconf->getvalue($media, 'updates_for'); push @newnames, $urpm->add_medium( $name ? "$media_name ($name$medium)" : $media_name, reduce_pathname($distribconf->getfullpath($media, 'path')), offset_pathname( $url, $distribconf->getpath($media, 'path'), ) . '/' . $distribconf->getpath($media, $options{probe_with} eq 'synthesis' ? 'synthesis' : 'hdlist'), index_name => $name ? undef : 0, %options, # the following override %options update => $is_update_media ? 1 : undef, ); ++$medium; } return @newnames; } #- deprecated, use select_media_by_name instead sub select_media { my $urpm = shift; my $options = {}; if (ref $_[0]) { $options = shift } foreach (select_media_by_name($urpm, [ @_ ], $options)) { #- select medium by setting the modified flag, do not check ignore. $_->{modified} = 1; } } sub select_media_by_name { my ($urpm, $names, $options) = @_; my %wanted = map { $_ => 1 } @$names; #- first the exact matches my @l = grep { delete $wanted{$_->{name}} } @{$urpm->{media}}; #- check if some arguments don't correspond to the medium name. #- in such case, try to find the unique medium (or list candidate #- media found). foreach (keys %wanted) { my $q = quotemeta; my (@found, @foundi); my $regex = $options->{strict_match} ? qr/^$q$/ : qr/$q/; my $regexi = $options->{strict_match} ? qr/^$q$/i : qr/$q/i; foreach my $medium (@{$urpm->{media}}) { $medium->{name} =~ $regex and push @found, $medium; $medium->{name} =~ $regexi and push @foundi, $medium; } @found = @foundi if !@found; if (@found == 0) { $urpm->{error}(N("trying to select nonexistent medium \"%s\"", $_)); } else { if (@found > 1) { $urpm->{log}(N("selecting multiple media: %s", join(", ", map { qq("$_->{name}") } @found))); } #- changed behaviour to select all occurences by default. push @l, @found; } } @l; } #- deprecated, use remove_media instead sub remove_selected_media { my ($urpm) = @_; remove_media($urpm, [ grep { $_->{modified} } @{$urpm->{media}} ]); } sub remove_media { my ($urpm, $to_remove) = @_; foreach my $medium (@$to_remove) { $urpm->{log}(N("removing medium \"%s\"", $medium->{name})); #- mark to re-write configuration. $urpm->{modified} = 1; #- remove files associated with this medium. unlink grep { $_ } map { $_->($urpm, $medium) } \&statedir_hdlist, \&statedir_list, \&statedir_synthesis, \&statedir_descriptions, \&statedir_names; #- remove proxy settings for this media urpm::download::remove_proxy_media($medium->{name}); } $urpm->{media} = [ difference2($urpm->{media}, $to_remove) ]; } #- return list of synthesis or hdlist reference to probe. sub _probe_with_try_list { my ($suffix, $probe_with) = @_; my @probe_synthesis = ( "media_info/synthesis.hdlist.cz", "../media_info/synthesis.hdlist_$suffix.cz", "synthesis.hdlist.cz", ); my @probe_hdlist = ( "media_info/hdlist.cz", "../media_info/hdlist_$suffix.cz", "hdlist.cz", ); $probe_with =~ /synthesis/ ? (@probe_synthesis, @probe_hdlist) : (@probe_hdlist, @probe_synthesis); } sub may_reconfig_urpmi { my ($urpm, $medium) = @_; my $f; if (my $dir = file_from_file_url($medium->{url})) { $f = reduce_pathname("$dir/reconfig.urpmi"); } else { unlink($f = "$urpm->{cachedir}/partial/reconfig.urpmi"); sync_webfetch($urpm, $medium, [ reduce_pathname("$medium->{url}/reconfig.urpmi") ], quiet => 1); } if (-s $f) { reconfig_urpmi($urpm, $f, $medium->{name}); } unlink $f if !file_from_file_url($medium->{url}); } #- read a reconfiguration file for urpmi, and reconfigure media accordingly #- $rfile is the reconfiguration file (local), $name is the media name #- #- the format is similar to the RewriteRule of mod_rewrite, so: #- PATTERN REPLACEMENT [FLAG] #- where FLAG can be L or N #- #- example of reconfig.urpmi: #- # this is an urpmi reconfiguration file #- /cooker /cooker/$ARCH sub reconfig_urpmi { my ($urpm, $rfile, $name) = @_; -r $rfile or return; $urpm->{log}(N("reconfiguring urpmi for media \"%s\"", $name)); my ($magic, @lines) = cat_($rfile); #- the first line of reconfig.urpmi must be magic, to be sure it's not an error file $magic =~ /^# this is an urpmi reconfiguration file/ or return undef; my @replacements; foreach (@lines) { chomp; s/^\s*//; s/#.*$//; s/\s*$//; $_ or next; my ($p, $r, $f) = split /\s+/, $_, 3; push @replacements, [ quotemeta $p, $r, $f || 1 ]; } my $reconfigured = 0; my @reconfigurable = qw(url with_hdlist); my $medium = name2medium($urpm, $name) or return; my %orig = %$medium; URLS: foreach my $k (@reconfigurable) { foreach my $r (@replacements) { if ($medium->{$k} =~ s/$r->[0]/$r->[1]/) { $reconfigured = 1; #- Flags stolen from mod_rewrite: L(ast), N(ext) if ($r->[2] =~ /L/) { last; } elsif ($r->[2] =~ /N/) { #- dangerous option redo URLS; } } } #- check that the new url exists before committing changes (local mirrors) my $file = file_from_local_url($medium->{$k}); if ($file && !-e $file) { %$medium = %orig; $reconfigured = 0; $urpm->{log}(N("...reconfiguration failed")); return; } } if ($reconfigured) { $urpm->{log}(N("reconfiguration done")); $urpm->write_config; } $reconfigured; } sub _guess_hdlist_suffix { my ($dir) = @_; my ($suffix) = $dir =~ m!\bmedia/(\w+)/*\Z!; $suffix; } sub _hdlist_suffix { my ($medium) = @_; $medium->{with_hdlist} =~ /hdlist(.*?)(?:\.src)?\.cz$/ ? $1 : ''; } sub _update_media__when_not_modified { my ($urpm, $medium) = @_; delete @$medium{qw(start end)}; if ($medium->{virtual}) { if (file_from_file_url($medium->{url})) { if ($medium->{synthesis}) { _parse_synthesis($urpm, $medium, hdlist_or_synthesis_for_virtual_medium($medium)); } else { _parse_hdlist($urpm, $medium, hdlist_or_synthesis_for_virtual_medium($medium)); } } else { $urpm->{error}(N("virtual medium \"%s\" is not local, medium ignored", $medium->{name})); $medium->{ignore} = 1; } } else { if (!_parse_synthesis($urpm, $medium, statedir_synthesis($urpm, $medium))) { _parse_hdlist($urpm, $medium, statedir_hdlist($urpm, $medium)); } } unless ($medium->{ignore}) { _check_after_reading_hdlist_or_synthesis($urpm, $medium); } } sub _parse_hdlist_or_synthesis__virtual { my ($urpm, $medium, $with_hdlist_dir) = @_; if ($medium->{with_hdlist} && -e $with_hdlist_dir) { delete $medium->{modified}; $urpm->{md5sum_modified} = 1; if ($medium->{synthesis}) { if (_parse_synthesis($urpm, $medium, $with_hdlist_dir)) { $medium->{synthesis} = 1; } elsif (_parse_hdlist($urpm, $medium, $with_hdlist_dir)) { delete $medium->{synthesis}; } } else { if (_parse_hdlist($urpm, $medium, $with_hdlist_dir)) { delete $medium->{synthesis}; } elsif (_parse_synthesis($urpm, $medium, $with_hdlist_dir)) { $medium->{synthesis} = 1; } } _check_after_reading_hdlist_or_synthesis($urpm, $medium); } else { $urpm->{error}(N("virtual medium \"%s\" should have valid source hdlist or synthesis, medium ignored", $medium->{name})); $medium->{ignore} = 1; } } #- names. is used by external progs (namely for bash-completion) sub generate_media_names { my ($urpm) = @_; #- make sure names files are regenerated. foreach (@{$urpm->{media}}) { unlink statedir_names($urpm, $_); if (is_valid_medium($_)) { if (my $fh = $urpm->open_safe(">", statedir_names($urpm, $_))) { foreach ($_->{start} .. $_->{end}) { if (defined $urpm->{depslist}[$_]) { print $fh $urpm->{depslist}[$_]->name . "\n"; } else { $urpm->{error}(N("Error generating names file: dependency %d not found", $_)); } } } else { $urpm->{error}(N("Error generating names file: Can't write to file (%s)", $!)); } } } } sub _read_existing_synthesis_and_hdlist_if_same_time_and_msize { my ($urpm, $medium, $basename) = @_; same_size_and_mtime("$urpm->{cachedir}/partial/$basename", statedir_hdlist($urpm, $medium)) or return; unlink "$urpm->{cachedir}/partial/$basename"; _read_existing_synthesis_and_hdlist($urpm, $medium); 1; } sub _read_existing_synthesis_and_hdlist_if_same_md5sum { my ($urpm, $medium, $retrieved_md5sum) = @_; #- if an existing hdlist or synthesis file has the same md5sum, we assume the #- files are the same. #- if local md5sum is the same as distant md5sum, this means there is no need to #- download hdlist or synthesis file again. $retrieved_md5sum && $medium->{md5sum} eq $retrieved_md5sum or return; unlink "$urpm->{cachedir}/partial/" . basename($medium->{with_hdlist}); _read_existing_synthesis_and_hdlist($urpm, $medium); 1; } sub _read_existing_synthesis_and_hdlist { my ($urpm, $medium) = @_; $urpm->{log}(N("medium \"%s\" is up-to-date", $medium->{name})); #- the medium is now considered not modified. $medium->{modified} = 0; #- XXX we could link the new hdlist to the old one. #- (However links need to be managed. see bug #12391.) #- as previously done, just read synthesis file here, this is enough. if (!_parse_synthesis($urpm, $medium, statedir_synthesis($urpm, $medium))) { _parse_hdlist($urpm, $medium, statedir_hdlist($urpm, $medium)); _check_after_reading_hdlist_or_synthesis($urpm, $medium); } 1; } sub _parse_hdlist { my ($urpm, $medium, $hdlist_file, $o_callback) = @_; $urpm->{log}(N("examining hdlist file [%s]", $hdlist_file)); ($medium->{start}, $medium->{end}) = $urpm->parse_hdlist($hdlist_file, packing => 1, $o_callback ? (callback => $o_callback) : @{[]}); } sub _parse_synthesis { my ($urpm, $medium, $synthesis_file, $o_callback) = @_; $urpm->{log}(N("examining synthesis file [%s]", $synthesis_file)); ($medium->{start}, $medium->{end}) = $urpm->parse_synthesis($synthesis_file, $o_callback ? (callback => $o_callback) : @{[]}); } sub _build_hdlist_using_rpm_headers { my ($urpm, $medium) = @_; $urpm->{log}(N("building hdlist [%s]", statedir_hdlist($urpm, $medium))); #- finish building operation of hdlist. $urpm->build_hdlist(start => $medium->{start}, end => $medium->{end}, dir => "$urpm->{cachedir}/headers", hdlist => statedir_hdlist($urpm, $medium), ); } sub _build_synthesis { my ($urpm, $medium) = @_; eval { $urpm->build_synthesis( start => $medium->{start}, end => $medium->{end}, synthesis => statedir_synthesis($urpm, $medium), ) }; if ($@) { $urpm->{error}(N("Unable to build synthesis file for medium \"%s\". Your hdlist file may be corrupted.", $medium->{name})); $urpm->{error}($@); unlink statedir_synthesis($urpm, $medium); } else { $urpm->{log}(N("built hdlist synthesis file for medium \"%s\"", $medium->{name})); } #- keep in mind we have a modified database, sure at this point. $urpm->{md5sum_modified} = 1; } sub is_valid_medium { my ($medium) = @_; defined $medium->{start} && defined $medium->{end}; } sub _check_after_reading_hdlist_or_synthesis { my ($urpm, $medium) = @_; if (!is_valid_medium($medium)) { $urpm->{error}(N("problem reading hdlist or synthesis file of medium \"%s\"", $medium->{name})); $medium->{ignore} = 1; } } sub db_open_or_die { my ($urpm, $root, $b_force) = @_; my $db = URPM::DB::open($root, $b_force) or $urpm->{fatal}(9, N("unable to open rpmdb")); $db; } sub _get_list_or_pubkey__local { my ($urpm, $medium, $with_hdlist_dir, $name) = @_; my $path = reduce_pathname("$with_hdlist_dir/../$name" . _hdlist_suffix($medium)); -e $path or $path = file_from_local_url($medium->{url}) . "/$name"; if (-e $path) { copy_and_own($path, "$urpm->{cachedir}/partial/$name") or $urpm->{error}(N("...copying failed")), return; } 1; } sub _get_list_or_pubkey__remote { my ($urpm, $medium, $name) = @_; my $found; if (_hdlist_suffix($medium)) { my $local_name = $name . _hdlist_suffix($medium); if (sync_webfetch($urpm, $medium, [reduce_pathname("$medium->{url}/$medium->{with_hdlist}/../$local_name")], quiet => 1)) { rename("$urpm->{cachedir}/partial/$local_name", "$urpm->{cachedir}/partial/$name"); $found = 1; } } if (!$found) { sync_webfetch($urpm, $medium, [reduce_pathname("$medium->{url}/$name")], quiet => 1) or unlink "$urpm->{cachedir}/partial/$name"; } } sub clean_dir { my ($dir) = @_; require File::Path; File::Path::rmtree([$dir]); mkdir $dir, 0755; } sub get_descriptions_local { my ($urpm, $medium) = @_; unlink statedir_descriptions($urpm, $medium); my $dir = file_from_local_url($medium->{url}); my $description_file = "$dir/media_info/descriptions"; #- new default location -e $description_file or $description_file = "$dir/../descriptions"; -e $description_file or return; $urpm->{log}(N("copying description file of \"%s\"...", $medium->{name})); if (copy_and_own($description_file, statedir_descriptions($urpm, $medium))) { $urpm->{log}(N("...copying done")); } else { $urpm->{error}(N("...copying failed")); $medium->{ignore} = 1; } } sub get_descriptions_remote { my ($urpm, $medium) = @_; unlink "$urpm->{cachedir}/partial/descriptions"; if (-e statedir_descriptions($urpm, $medium)) { urpm::util::move(statedir_descriptions($urpm, $medium), "$urpm->{cachedir}/partial/descriptions"); } sync_webfetch($urpm, $medium, [ reduce_pathname("$medium->{url}/media_info/descriptions") ], quiet => 1) or #- try older location sync_webfetch($urpm, $medium, [ reduce_pathname("$medium->{url}/../descriptions") ], quiet => 1); if (-e "$urpm->{cachedir}/partial/descriptions") { urpm::util::move("$urpm->{cachedir}/partial/descriptions", statedir_descriptions($urpm, $medium)); } } sub get_hdlist_or_synthesis__local { my ($urpm, $medium, $with_hdlist_dir, $callback) = @_; unlink cachedir_hdlist($urpm, $medium); $urpm->{log}(N("copying source hdlist (or synthesis) of \"%s\"...", $medium->{name})); $callback and $callback->('copy', $medium->{name}); if (copy_and_own($with_hdlist_dir, cachedir_hdlist($urpm, $medium))) { $callback and $callback->('done', $medium->{name}); $urpm->{log}(N("...copying done")); if (file_size(cachedir_hdlist($urpm, $medium)) <= 32) { $urpm->{error}(N("copy of [%s] failed (file is suspiciously small)", cachedir_hdlist($urpm, $medium))); 0; } else { 1; } } else { $callback and $callback->('failed', $medium->{name}); #- force error, reported afterwards unlink cachedir_hdlist($urpm, $medium); 0; } } sub _update_medium__parse_if_unmodified__or_get_files__local { my ($urpm, $medium, $second_pass, $clean_cache, $rpm_files, $options) = @_; my $dir = file_from_local_url($medium->{url}); #- try to figure a possible hdlist_path (or parent directory of searched directory). #- this is used to probe for a possible hdlist file. my $with_hdlist_dir = reduce_pathname($dir . ($medium->{with_hdlist} ? "/$medium->{with_hdlist}" : "/..")); #- the directory given does not exist and may be accessible #- by mounting some other directory. Try to figure it out and mount #- everything that might be necessary. -d $dir or $urpm->try_mounting( !$options->{force_building_hdlist} && ($options->{probe_with} || $medium->{with_hdlist}) ? $with_hdlist_dir : $dir, #- in case of an iso image, pass its name is_iso($medium->{removable}) && $medium->{removable}, ) or $urpm->{error}(N("unable to access medium \"%s\", this could happen if you mounted manually the directory when creating the medium.", $medium->{name})), return 'unmodified'; #- try to probe for possible with_hdlist parameter, unless #- it is already defined (and valid). if ($options->{probe_with} && (!$medium->{with_hdlist} || ! -e "$dir/$medium->{with_hdlist}")) { foreach (_probe_with_try_list(_guess_hdlist_suffix($dir), $options->{probe_with})) { -e "$dir/$_" or next; if (file_size("$dir/$_") > 32) { $medium->{with_hdlist} = $_; last; } else { $urpm->{error}(N("invalid hdlist file %s for medium \"%s\"", "$dir/$_", $medium->{name})); } } #- redo... $with_hdlist_dir = reduce_pathname($dir . ($medium->{with_hdlist} ? "/$medium->{with_hdlist}" : "/..")); } if ($medium->{virtual}) { #- syncing a virtual medium is very simple, just try to read the file in order to #- determine its type, once a with_hdlist has been found (but is mandatory). _parse_hdlist_or_synthesis__virtual($urpm, $medium, $with_hdlist_dir); } get_descriptions_local($urpm, $medium); #- examine if a distant MD5SUM file is available. #- this will only be done if $with_hdlist is not empty in order to use #- an existing hdlist or synthesis file, and to check if download was good. #- if no MD5SUM is available, do it as before... #- we can assume at this point a basename is existing, but it needs #- to be checked for being valid, nothing can be deduced if no MD5SUM #- file is present. my ($retrieved_md5sum, $error); unless ($medium->{virtual}) { if ($medium->{with_hdlist}) { if (!$options->{nomd5sum} && file_size(reduce_pathname("$with_hdlist_dir/../MD5SUM")) > 32) { recompute_local_md5sum($urpm, $medium, $options->{force}); if ($medium->{md5sum}) { $retrieved_md5sum = parse_md5sum($urpm, reduce_pathname("$with_hdlist_dir/../MD5SUM"), basename($with_hdlist_dir)); _read_existing_synthesis_and_hdlist_if_same_md5sum($urpm, $medium, $retrieved_md5sum) and return 'unmodified'; } } #- if the source hdlist is present and we are not forcing using rpm files if (!$options->{force_building_hdlist} && -e $with_hdlist_dir) { get_hdlist_or_synthesis__local($urpm, $medium, $with_hdlist_dir, $options->{callback}) or $error = 1; } #- keep checking md5sum of file just copied ! (especially on nfs or removable device). if (!$error && $retrieved_md5sum) { $urpm->{log}(N("computing md5sum of copied source hdlist (or synthesis)")); md5sum(cachedir_hdlist($urpm, $medium)) eq $retrieved_md5sum or $error = 1, $urpm->{error}(N("copy of [%s] failed (md5sum mismatch)", $with_hdlist_dir)); } #- check if the files are equal... and no force copy... if (!$error && !$options->{force} && -e statedir_synthesis($urpm, $medium)) { _read_existing_synthesis_and_hdlist_if_same_time_and_msize($urpm, $medium, $medium->{hdlist}) and return 'unmodified'; } #- if copying hdlist has failed, try to build it directly. if ($error) { if ($urpm->{options}{'build-hdlist-on-error'}) { $options->{force_building_hdlist} = 1; #- clear error state now. $error = undef; } else { $urpm->{error}(N("unable to access hdlist file of \"%s\", medium ignored", $medium->{name})); $medium->{ignore} = 1; } } } else { #- no available hdlist/synthesis, try to build it from rpms $options->{force_building_hdlist} = 1; } if (!$error && $options->{force_building_hdlist}) { push @$rpm_files, glob("$dir/*.rpm"); #- check files contains something good! if (@$rpm_files > 0) { #- we need to rebuild from rpm files the hdlist. $urpm->{log}(N("reading rpm files from [%s]", $dir)); my @unresolved_before = grep { ! defined $urpm->{provides}{$_}; } keys %{$urpm->{provides} || {}}; $medium->{start} = @{$urpm->{depslist}}; eval { $medium->{headers} = [ $urpm->parse_rpms_build_headers( dir => "$urpm->{cachedir}/headers", rpms => $rpm_files, clean => $$clean_cache, packing => 1, ) ]; }; if ($@) { $error = 1; $urpm->{error}(N("unable to read rpm files from [%s]: %s", $dir, $@)); delete $medium->{headers}; #- do not propagate these. } else { $medium->{end} = $#{$urpm->{depslist}}; if ($medium->{start} > $medium->{end}) { #- an error occured (provided there are files in input.) delete $medium->{start}; delete $medium->{end}; $urpm->{fatal}(9, N("no rpms read")); } else { #- make sure the headers will not be removed for another media. $$clean_cache = 0; my @unresolved = grep { ! defined $urpm->{provides}{$_}; } keys %{$urpm->{provides} || {}}; @unresolved_before == @unresolved or $$second_pass = 1; } delete $medium->{synthesis}; #- when building hdlist by ourself, drop synthesis property. } } else { $error = 1; $urpm->{error}(N("no rpm files found from [%s]", $dir)); $medium->{ignore} = 1; } } } #- examine if a local pubkey file is available. if (!$options->{nopubkey} && !$medium->{'key-ids'}) { _get_list_or_pubkey__local($urpm, $medium, $with_hdlist_dir, 'pubkey'); } ($error, $retrieved_md5sum); } sub _update_medium__parse_if_unmodified__or_get_files__remote { my ($urpm, $medium, $options) = @_; my ($error, $retrieved_md5sum, $basename); get_descriptions_remote($urpm, $medium); #- examine if a distant MD5SUM file is available. #- this will only be done if $with_hdlist is not empty in order to use #- an existing hdlist or synthesis file, and to check if download was good. #- if no MD5SUM is available, do it as before... if ($medium->{with_hdlist}) { #- we can assume at this point a basename is existing, but it needs #- to be checked for being valid, nothing can be deduced if no MD5SUM #- file is present. $basename = basename($medium->{with_hdlist}); unlink "$urpm->{cachedir}/partial/MD5SUM"; if (!$options->{nomd5sum} && sync_webfetch($urpm, $medium, [ reduce_pathname("$medium->{url}/$medium->{with_hdlist}/../MD5SUM") ], quiet => 1) && file_size("$urpm->{cachedir}/partial/MD5SUM") > 32) { recompute_local_md5sum($urpm, $medium, $options->{force} >= 2); if ($medium->{md5sum}) { $retrieved_md5sum = parse_md5sum($urpm, "$urpm->{cachedir}/partial/MD5SUM", $basename); _read_existing_synthesis_and_hdlist_if_same_md5sum($urpm, $medium, $retrieved_md5sum) and return 'unmodified'; } } else { #- at this point, we don't if a basename exists and is valid, let probe it later. $basename = undef; } } #- try to probe for possible with_hdlist parameter, unless #- it is already defined (and valid). $urpm->{log}(N("retrieving source hdlist (or synthesis) of \"%s\"...", $medium->{name})); $options->{callback} and $options->{callback}('retrieve', $medium->{name}); if ($options->{probe_with}) { my @probe_list = ( $medium->{with_hdlist} ? $medium->{with_hdlist} : _probe_with_try_list(_guess_hdlist_suffix($medium->{url}), $options->{probe_with}) ); foreach my $with_hdlist (@probe_list) { $basename = basename($with_hdlist) or next; $options->{force} and unlink "$urpm->{cachedir}/partial/$basename"; if (sync_webfetch($urpm, $medium, [ reduce_pathname("$medium->{url}/$with_hdlist") ], quiet => $options->{quiet}, callback => $options->{callback}) && file_size("$urpm->{cachedir}/partial/$basename") > 32) { $urpm->{log}(N("...retrieving done")); $medium->{with_hdlist} = $with_hdlist; $urpm->{log}(N("found probed hdlist (or synthesis) as %s", $medium->{with_hdlist})); last; #- found a suitable with_hdlist in the list above. } } } else { $basename = basename($medium->{with_hdlist}); if ($options->{force}) { unlink "$urpm->{cachedir}/partial/$basename"; } else { #- try to sync (copy if needed) local copy after restored the previous one. #- this is useful for rsync (?) if (-e statedir_hdlist_or_synthesis($urpm, $medium)) { copy_and_own( statedir_hdlist_or_synthesis($urpm, $medium), "$urpm->{cachedir}/partial/$basename", ) or $urpm->{error}(N("...copying failed")), $error = 1; } } if (sync_webfetch($urpm, $medium, [ reduce_pathname("$medium->{url}/$medium->{with_hdlist}") ], quiet => $options->{quiet}, callback => $options->{callback})) { $urpm->{log}(N("...retrieving done")); } else { $urpm->{error}(N("...retrieving failed: %s", $@)); unlink "$urpm->{cachedir}/partial/$basename"; } } #- check downloaded file has right signature. if (file_size("$urpm->{cachedir}/partial/$basename") > 32 && $retrieved_md5sum) { $urpm->{log}(N("computing md5sum of retrieved source hdlist (or synthesis)")); unless (md5sum("$urpm->{cachedir}/partial/$basename") eq $retrieved_md5sum) { $urpm->{error}(N("...retrieving failed: md5sum mismatch")); unlink "$urpm->{cachedir}/partial/$basename"; } } if (file_size("$urpm->{cachedir}/partial/$basename") > 32) { $options->{callback} and $options->{callback}('done', $medium->{name}); unless ($options->{force}) { _read_existing_synthesis_and_hdlist_if_same_time_and_msize($urpm, $medium, $basename) and return 'unmodified'; } #- the files are different, update local copy. rename("$urpm->{cachedir}/partial/$basename", cachedir_hdlist($urpm, $medium)); #- retrieve pubkey file. if (!$options->{nopubkey} && !$medium->{'key-ids'}) { _get_list_or_pubkey__remote($urpm, $medium, 'pubkey'); } } else { $error = 1; $options->{callback} and $options->{callback}('failed', $medium->{name}); $urpm->{error}(N("retrieval of source hdlist (or synthesis) failed")); } ($error, $retrieved_md5sum); } sub _read_cachedir_pubkey { my ($urpm, $medium) = @_; -s "$urpm->{cachedir}/partial/pubkey" or return; $urpm->{log}(N("examining pubkey file of \"%s\"...", $medium->{name})); my %key_ids; $urpm->import_needed_pubkeys( [ $urpm->parse_armored_file("$urpm->{cachedir}/partial/pubkey") ], root => $urpm->{root}, callback => sub { my (undef, undef, $_k, $id, $imported) = @_; if ($id) { $key_ids{$id} = undef; $imported and $urpm->{log}(N("...imported key %s from pubkey file of \"%s\"", $id, $medium->{name})); } else { $urpm->{error}(N("unable to import pubkey file of \"%s\"", $medium->{name})); } }); if (keys(%key_ids)) { $medium->{'key-ids'} = join(',', keys %key_ids); } } sub _write_rpm_list_if_needed { my ($urpm, $medium, $rpm_list) = @_; if (@$rpm_list) { if (!$medium->{list}) { $urpm->{error}("{list} is not set, please report bug"); return; } #- write list file. $urpm->{log}(N("writing list file for medium \"%s\"", $medium->{name})); my $listfh = $urpm->open_safe('>', cachedir_list($urpm, $medium)) or return; print $listfh basename($_), "\n" foreach @$rpm_list; } else { #- the flag is no longer necessary. if ($medium->{list}) { unlink statedir_list($urpm, $medium); delete $medium->{list}; } } 1; } sub _update_medium_first_pass { my ($urpm, $medium, $second_pass, $clean_cache, %options) = @_; #- we should create the associated synthesis file if it does not already exist... file_size(statedir_synthesis($urpm, $medium)) > 32 or $medium->{must_build_synthesis} = 1; unless ($medium->{modified}) { #- the medium is not modified, but to compute dependencies, #- we still need to read it and all synthesis will be written if #- an unresolved provides is found. #- to speed up the process, we only read the synthesis at the beginning. _update_media__when_not_modified($urpm, $medium); return; } #- always delete a remaining list file or pubkey file in cache. foreach (qw(list pubkey)) { unlink "$urpm->{cachedir}/partial/$_"; } #- check for a reconfig.urpmi file (if not already reconfigured) if (!$medium->{noreconfigure}) { may_reconfig_urpmi($urpm, $medium); } #- list of rpm files for this medium, only available for local medium where #- the source hdlist is not used (use force). my ($error, $retrieved_md5sum, @rpm_files); { (my $rc, $retrieved_md5sum) = file_from_local_url($medium->{url}) ? _update_medium__parse_if_unmodified__or_get_files__local($urpm, $medium, $second_pass, $clean_cache, \@rpm_files, \%options) : _update_medium__parse_if_unmodified__or_get_files__remote($urpm, $medium, \%options); if ($rc eq 'unmodified') { return; } else { $error = $rc; } } #- build list file according to hdlist. if (!$medium->{headers} && !$medium->{virtual} && file_size(cachedir_hdlist($urpm, $medium)) <= 32) { $error = 1; $urpm->{error}(N("no hdlist file found for medium \"%s\"", $medium->{name})); } if (!$error && !$medium->{virtual}) { if (!$medium->{headers}) { #- read first pass hdlist or synthesis, try to open as synthesis, if file #- is larger than 1MB, this is probably an hdlist else a synthesis. #- anyway, if one tries fails, try another mode. $options{callback} and $options{callback}('parse', $medium->{name}); my @unresolved_before = grep { ! defined $urpm->{provides}{$_} } keys %{$urpm->{provides} || {}}; if (!$medium->{synthesis} || file_size(cachedir_hdlist($urpm, $medium)) > 262144) { if (_parse_hdlist($urpm, $medium, cachedir_hdlist($urpm, $medium))) { delete $medium->{synthesis}; } elsif (_parse_synthesis($urpm, $medium, cachedir_hdlist($urpm, $medium))) { $medium->{synthesis} = 1; } } else { if (_parse_synthesis($urpm, $medium, cachedir_hdlist($urpm, $medium))) { $medium->{synthesis} = 1; } elsif (_parse_hdlist($urpm, $medium, cachedir_hdlist($urpm, $medium))) { delete $medium->{synthesis}; } } if (is_valid_medium($medium)) { $options{callback} && $options{callback}('done', $medium->{name}); } else { $error = 1; $urpm->{error}(N("unable to parse hdlist file of \"%s\"", $medium->{name})); $options{callback} && $options{callback}('failed', $medium->{name}); #- we will have to read back the current synthesis file unmodified. } unless ($error) { my @unresolved_after = grep { ! defined $urpm->{provides}{$_} } keys %{$urpm->{provides} || {}}; @unresolved_before == @unresolved_after or $$second_pass = 1; @rpm_files = grep { /\.rpm$/ } map { chomp; $_ } cat_("$urpm->{cachedir}/partial/list"); } } unless ($error) { _write_rpm_list_if_needed($urpm, $medium, \@rpm_files) or $error = 1; } } unless ($error) { _read_cachedir_pubkey($urpm, $medium); } unless ($medium->{virtual}) { if ($error) { #- an error has occured for updating the medium, we have to remove temporary files. unlink cachedir_hdlist($urpm, $medium); $medium->{list} and unlink cachedir_list($urpm, $medium); #- read default synthesis (we have to make sure nothing get out of depslist). if (!_parse_synthesis($urpm, $medium, statedir_synthesis($urpm, $medium))) { $urpm->{error}(N("problem reading synthesis file of medium \"%s\"", $medium->{name})); $medium->{ignore} = 1; } } else { #- make sure to rebuild base files and clear medium modified state. $medium->{modified} = 0; $urpm->{md5sum_modified} = 1; #- but use newly created file. unlink statedir_hdlist($urpm, $medium); $medium->{synthesis} and unlink statedir_synthesis($urpm, $medium); $medium->{list} and unlink statedir_list($urpm, $medium); unless ($medium->{headers}) { unlink statedir_synthesis($urpm, $medium); unlink statedir_hdlist($urpm, $medium); urpm::util::move(cachedir_hdlist($urpm, $medium), statedir_hdlist_or_synthesis($urpm, $medium)); } if ($medium->{list}) { urpm::util::move(cachedir_list($urpm, $medium), statedir_list($urpm, $medium)); } $medium->{md5sum} = $retrieved_md5sum; #- anyway, keep it, the previous one is no longer useful. #- and create synthesis file associated. $medium->{must_build_synthesis} = !$medium->{synthesis}; } } } #- take care of modified medium only, or all if all have to be recomputed. sub _update_medium_second_pass { my ($urpm, $medium, $second_pass, $callback) = @_; $callback and $callback->('parse', $medium->{name}); #- a modified medium is an invalid medium, we have to read back the previous hdlist #- or synthesis which has not been modified by first pass above. if ($medium->{headers} && !$medium->{modified}) { if ($second_pass) { $urpm->{log}(N("reading headers from medium \"%s\"", $medium->{name})); ($medium->{start}, $medium->{end}) = $urpm->parse_headers(dir => "$urpm->{cachedir}/headers", headers => $medium->{headers}, ); } _build_hdlist_using_rpm_headers($urpm, $medium); #- synthesis needs to be created, since the medium has been built from rpm files. _build_synthesis($urpm, $medium); } elsif ($medium->{synthesis}) { if ($second_pass) { if ($medium->{virtual}) { if (file_from_file_url($medium->{url})) { _parse_synthesis($urpm, $medium, hdlist_or_synthesis_for_virtual_medium($medium)); } } else { _parse_synthesis($urpm, $medium, statedir_synthesis($urpm, $medium)); } } } else { if ($second_pass) { _parse_hdlist($urpm, $medium, statedir_hdlist($urpm, $medium)); } #- check if the synthesis file can be built. if (($second_pass || $medium->{must_build_synthesis}) && !$medium->{modified} && !$medium->{virtual}) { _build_synthesis($urpm, $medium); } } $callback && $callback->('done', $medium->{name}); } sub remove_obsolete_headers_in_cache { my ($urpm) = @_; my %headers; if (my $dh = $urpm->opendir_safe("$urpm->{cachedir}/headers")) { local $_; while (defined($_ = readdir $dh)) { m|^([^/]*-[^-]*-[^-]*\.[^\.]*)(?::\S*)?$| and $headers{$1} = $_; } } if (%headers) { my $previous_total = scalar(keys %headers); foreach (@{$urpm->{depslist}}) { delete $headers{$_->fullname}; } $urpm->{log}(N("found %d rpm headers in cache, removing %d obsolete headers", $previous_total, scalar(keys %headers))); foreach (values %headers) { unlink "$urpm->{cachedir}/headers/$_"; } } } sub _update_media__handle_some_flags { my ($urpm, $options) = @_; foreach my $medium (grep { !$_->{ignore} } @{$urpm->{media}}) { $options->{forcekey} and delete $medium->{'key-ids'}; if ($medium->{static}) { #- don't ever update static media $medium->{modified} = 0; } elsif ($options->{all}) { #- if we're rebuilding all media, mark them as modified (except removable ones) $medium->{modified} ||= $medium->{url} !~ m!^removable!; } } } #- Update the urpmi database w.r.t. the current configuration. #- Takes care of modifications, and tries some tricks to bypass #- the recomputation of base files. #- Recognized options : #- all : all medias are being rebuilt #- callback : UI callback #- forcekey : force retrieval of pubkey #- force : try to force rebuilding base files (1) or hdlist from rpm files (2) #- noclean : keep old files in the header cache directory #- nolock : don't lock the urpmi database #- nomd5sum : don't verify MD5SUM of retrieved files #- nopubkey : don't use rpm pubkeys #- probe_with : probe synthesis or hdlist (or none) #- quiet : download hdlists quietly sub update_media { my ($urpm, %options) = @_; $urpm->{media} or return; # verify that configuration has been read $options{nopubkey} ||= $urpm->{options}{nopubkey}; #- get gpg-pubkey signature. if (!$options{nopubkey}) { $urpm->lock_rpm_db('exclusive'); $urpm->{keys} or $urpm->parse_pubkeys(root => $urpm->{root}); } #- lock database if allowed. $urpm->lock_urpmi_db('exclusive') if !$options{nolock}; #- examine each medium to see if one of them needs to be updated. #- if this is the case and if not forced, try to use a pre-calculated #- hdlist file, else build it from rpm files. $urpm->clean; _update_media__handle_some_flags($urpm, \%options); my $clean_cache = !$options{noclean}; my $second_pass; foreach my $medium (grep { !$_->{ignore} } @{$urpm->{media}}) { _update_medium_first_pass($urpm, $medium, \$second_pass, \$clean_cache, %options); } #- some unresolved provides may force to rebuild all synthesis, #- a second pass will be necessary. if ($second_pass) { $urpm->{log}(N("performing second pass to compute dependencies\n")); $urpm->unresolved_provides_clean; } #- second pass consists in reading again synthesis or hdlists. foreach my $medium (grep { !$_->{ignore} } @{$urpm->{media}}) { _update_medium_second_pass($urpm, $medium, $second_pass, $options{callback}); } if ($urpm->{modified}) { if ($options{noclean}) { #- clean headers cache directory to remove everything that is no longer #- useful according to the depslist. remove_obsolete_headers_in_cache($urpm); } #- write config files in any case $urpm->write_config; dump_proxy_config(); } elsif ($urpm->{md5sum_modified}) { #- NB: in case of $urpm->{modified}, write_MD5SUM is called in write_config above write_MD5SUM($urpm); } generate_media_names($urpm); $options{nolock} or $urpm->unlock_urpmi_db; $options{nopubkey} or $urpm->unlock_rpm_db; } #- clean params and depslist computation zone. sub clean { my ($urpm) = @_; $urpm->{depslist} = []; $urpm->{provides} = {}; foreach (@{$urpm->{media} || []}) { delete $_->{start}; delete $_->{end}; } } sub try_mounting { my ($urpm, $dir, $o_removable) = @_; my %infos; my $is_iso = is_iso($o_removable); my @mntpoints = $is_iso #- note: for isos, we don't parse the fstab because it might not be declared in it. #- so we try to remove suffixes from the dir name until the dir exists ? ($dir = urpm::sys::trim_until_d($dir)) : urpm::sys::find_mntpoints($dir = reduce_pathname($dir), \%infos); foreach (grep { ! $infos{$_}{mounted} && $infos{$_}{fs} ne 'supermount'; } @mntpoints) { $urpm->{log}(N("mounting %s", $_)); if ($is_iso) { #- to mount an iso image, grab the first loop device my $loopdev = urpm::sys::first_free_loopdev(); sys_log("mount iso $_ on $o_removable"); $loopdev and system('mount', $o_removable, $_, '-t', 'iso9660', '-o', "loop=$loopdev"); } else { sys_log("mount $_"); system("mount '$_' 2>/dev/null"); } $o_removable && $infos{$_}{fs} ne 'supermount' and $urpm->{removable_mounted}{$_} = undef; } -e $dir; } sub try_umounting { my ($urpm, $dir) = @_; my %infos; $dir = reduce_pathname($dir); foreach (reverse grep { $infos{$_}{mounted} && $infos{$_}{fs} ne 'supermount'; } urpm::sys::find_mntpoints($dir, \%infos)) { $urpm->{log}(N("unmounting %s", $_)); sys_log("umount $_"); system("umount '$_' 2>/dev/null"); delete $urpm->{removable_mounted}{$_}; } ! -e $dir; } sub try_umounting_removables { my ($urpm) = @_; foreach (keys %{$urpm->{removable_mounted}}) { $urpm->try_umounting($_); } delete $urpm->{removable_mounted}; } #- register local packages for being installed, keep track of source. sub register_rpms { my ($urpm, @files) = @_; my ($start, $id, $error, %requested); #- examine each rpm and build the depslist for them using current #- depslist and provides environment. $start = @{$urpm->{depslist}}; foreach (@files) { /\.(?:rpm|spec)$/ or $error = 1, $urpm->{error}(N("invalid rpm file name [%s]", $_)), next; #- if that's an URL, download. if (protocol_from_url($_)) { my $basename = basename($_); unlink "$urpm->{cachedir}/partial/$basename"; $urpm->{log}(N("retrieving rpm file [%s] ...", $_)); if (sync_webfetch($urpm, undef, [$_], quiet => 1)) { $urpm->{log}(N("...retrieving done")); $_ = "$urpm->{cachedir}/partial/$basename"; } else { $urpm->{error}(N("...retrieving failed: %s", $@)); unlink "$urpm->{cachedir}/partial/$basename"; next; } } else { -r $_ or $error = 1, $urpm->{error}(N("unable to access rpm file [%s]", $_)), next; } if (/\.spec$/) { my $pkg = URPM::spec2srcheader($_) or $error = 1, $urpm->{error}(N("unable to parse spec file %s [%s]", $_, $!)), next; $id = @{$urpm->{depslist}}; $urpm->{depslist}[$id] = $pkg; #- It happens that URPM sets an internal id to the depslist id. #- We need to set it by hand here. $pkg->set_id($id); $urpm->{source}{$id} = $_; } else { ($id) = $urpm->parse_rpm($_); my $pkg = defined $id && $urpm->{depslist}[$id]; $pkg or $error = 1, $urpm->{error}(N("unable to register rpm file")), next; $pkg->arch eq 'src' || $pkg->is_arch_compat or $error = 1, $urpm->{error}(N("Incompatible architecture for rpm [%s]", $_)), next; $urpm->{source}{$id} = $_; } } $error and $urpm->{fatal}(2, N("error registering local packages")); defined $id && $start <= $id and @requested{($start .. $id)} = (1) x ($id-$start+1); #- distribute local packages to distant nodes directly in cache of each machine. @files && $urpm->{parallel_handler} and $urpm->{parallel_handler}->parallel_register_rpms($urpm, @files); %requested; } sub _findindeps { my ($urpm, $found, $qv, $v, %options) = @_; foreach (keys %{$urpm->{provides}}) { #- search through provides to find if a provide matches this one; #- but manage choices correctly (as a provides may be virtual or #- defined several times). /$qv/ || !$options{caseinsensitive} && /$qv/i or next; my @list = grep { defined $_ } map { my $pkg = $urpm->{depslist}[$_]; $pkg && ($options{src} ? $pkg->arch eq 'src' : $pkg->arch ne 'src') ? $pkg->id : undef; } keys %{$urpm->{provides}{$_} || {}}; @list > 0 and push @{$found->{$v}}, join '|', @list; } } #- search packages registered by their names by storing their ids into the $packages hash. #- Recognized options: #- all #- caseinsensitive #- fuzzy #- src #- use_provides sub search_packages { my ($urpm, $packages, $names, %options) = @_; my (%exact, %exact_a, %exact_ra, %found, %foundi); foreach my $v (@$names) { my $qv = quotemeta $v; $qv = '(?i)' . $qv if $options{caseinsensitive}; unless ($options{fuzzy}) { #- try to search through provides. if (my @l = map { $_ && ($options{src} ? $_->arch eq 'src' : $_->is_arch_compat) && ($options{use_provides} || $_->name eq $v) && defined($_->id) && (!defined $urpm->{searchmedia} || $urpm->{searchmedia}{start} <= $_->id && $urpm->{searchmedia}{end} >= $_->id) ? $_ : @{[]}; } map { $urpm->{depslist}[$_]; } keys %{$urpm->{provides}{$v} || {}}) { #- we assume that if there is at least one package providing #- the resource exactly, this should be the best one; but we #- first check if one of the packages has the same name as searched. if (my @l2 = grep { $_->name eq $v } @l) { @l = @l2; } $exact{$v} = join('|', map { $_->id } @l); next; } } if ($options{use_provides} && $options{fuzzy}) { _findindeps($urpm, \%found, $qv, $v, %options); } foreach my $id (defined $urpm->{searchmedia} ? ($urpm->{searchmedia}{start} .. $urpm->{searchmedia}{end}) : (0 .. $#{$urpm->{depslist}}) ) { my $pkg = $urpm->{depslist}[$id]; ($options{src} ? $pkg->arch eq 'src' : $pkg->is_arch_compat) or next; my $pack_name = $pkg->name; my $pack_ra = $pack_name . '-' . $pkg->version; my $pack_a = "$pack_ra-" . $pkg->release; my $pack = "$pack_a." . $pkg->arch; unless ($options{fuzzy}) { if ($pack eq $v) { $exact{$v} = $id; next; } elsif ($pack_a eq $v) { push @{$exact_a{$v}}, $id; next; } elsif ($pack_ra eq $v || $options{src} && $pack_name eq $v) { push @{$exact_ra{$v}}, $id; next; } } $pack =~ /$qv/ and push @{$found{$v}}, $id; $pack =~ /$qv/i and push @{$foundi{$v}}, $id unless $options{caseinsensitive}; } } my $result = 1; foreach (@$names) { if (defined $exact{$_}) { $packages->{$exact{$_}} = 1; foreach (split /\|/, $exact{$_}) { my $pkg = $urpm->{depslist}[$_] or next; $pkg->set_flag_skip(0); #- reset skip flag as manually selected. } } else { #- at this level, we need to search the best package given for a given name, #- always prefer already found package. my %l; foreach (@{$exact_a{$_} || $exact_ra{$_} || $found{$_} || $foundi{$_} || []}) { my $pkg = $urpm->{depslist}[$_]; push @{$l{$pkg->name}}, $pkg; } if (values(%l) == 0 || values(%l) > 1 && !$options{all}) { $urpm->{error}(N("No package named %s", $_)); values(%l) != 0 and $urpm->{error}( N("The following packages contain %s: %s", $_, "\n" . join("\n", sort { $a cmp $b } keys %l)) ); $result = 0; } else { if (!@{$exact_a{$_} || $exact_ra{$_} || []}) { #- we found a non-exact match $result = 'substring'; } foreach (values %l) { my $best; foreach (@$_) { if ($best && $best != $_) { $_->compare_pkg($best) > 0 and $best = $_; } else { $best = $_; } } $packages->{$best->id} = 1; $best->set_flag_skip(0); #- reset skip flag as manually selected. } } } } #- return true if no error has been encountered, else false. $result; } #- Resolves dependencies between requested packages (and auto selection if any). #- handles parallel option if any. #- The return value is true if program should be restarted (in order to take #- care of important packages being upgraded (priority upgrades) #- %options : #- rpmdb #- auto_select #- callback_choices #- install_src #- keep #- nodeps #- priority_upgrade sub resolve_dependencies { #- $state->{selected} will contain the selection of packages to be #- installed or upgraded my ($urpm, $state, $requested, %options) = @_; my $need_restart; if ($options{install_src}) { #- only src will be installed, so only update $state->{selected} according #- to src status of files. foreach (keys %$requested) { my $pkg = $urpm->{depslist}[$_] or next; $pkg->arch eq 'src' or next; $state->{selected}{$_} = undef; } } if ($urpm->{parallel_handler}) { #- build the global synthesis file first. my $file = "$urpm->{cachedir}/partial/parallel.cz"; unlink $file; foreach (@{$urpm->{media}}) { is_valid_medium($_) or next; my $f = statedir_synthesis($urpm, $_); system "cat '$f' >> '$file'"; } #- let each node determine what is requested, according to handler given. $urpm->{parallel_handler}->parallel_resolve_dependencies($file, $urpm, $state, $requested, %options); } else { my $db; if ($options{rpmdb}) { $db = new URPM; $db->parse_synthesis($options{rpmdb}); } else { $db = db_open_or_die($urpm, $urpm->{root}); } my $sig_handler = sub { undef $db; exit 3 }; local $SIG{INT} = $sig_handler; local $SIG{QUIT} = $sig_handler; #- auto select package for upgrading the distribution. if ($options{auto_select}) { $urpm->request_packages_to_upgrade($db, $state, $requested, requested => undef, start => $urpm->{searchmedia}{start}, end => $urpm->{searchmedia}{end}); } #- resolve dependencies which will be examined for packages that need to #- have urpmi restarted when they're updated. $urpm->resolve_requested($db, $state, $requested, %options); if ($options{priority_upgrade} && !$options{rpmdb}) { my (%priority_upgrade, %priority_requested); @priority_upgrade{split /,/, $options{priority_upgrade}} = (); #- check if a priority upgrade should be tried foreach (keys %{$state->{selected}}) { my $pkg = $urpm->{depslist}[$_] or next; exists $priority_upgrade{$pkg->name} or next; $priority_requested{$pkg->id} = undef; } if (%priority_requested) { my %priority_state; $urpm->resolve_requested($db, \%priority_state, \%priority_requested, %options); if (grep { ! exists $priority_state{selected}{$_} } keys %priority_requested) { #- some packages which were selected previously have not been selected, strange! $need_restart = 0; } elsif (grep { ! exists $priority_state{selected}{$_} } keys %{$state->{selected}}) { #- there are other packages to install after this priority transaction. %$state = %priority_state; $need_restart = 1; } } } } $need_restart; } sub create_transaction { my ($urpm, $state, %options) = @_; if ($urpm->{parallel_handler} || !$options{split_length} || keys %{$state->{selected}} < $options{split_level}) { #- build simplest transaction (no split). $urpm->build_transaction_set(undef, $state, split_length => 0); } else { my $db; if ($options{rpmdb}) { $db = new URPM; $db->parse_synthesis($options{rpmdb}); } else { $db = db_open_or_die($urpm, $urpm->{root}); } my $sig_handler = sub { undef $db; exit 3 }; local $SIG{INT} = $sig_handler; local $SIG{QUIT} = $sig_handler; #- build transaction set... $urpm->build_transaction_set($db, $state, split_length => $options{split_length}); } } #- get the list of packages that should not be upgraded or installed, #- typically from the inst.list or skip.list files. sub get_packages_list { my ($file, $o_extra) = @_; my $val = []; open(my $f, '<', $file) or return []; foreach (<$f>, split /,/, $o_extra || '') { chomp; s/#.*$//; s/^\s*//; s/\s*$//; next if $_ eq ''; push @$val, $_; } $val; } #- select sources for selected packages, #- according to keys of the packages hash. #- returns a list of lists containing the source description for each rpm, #- matching the exact number of registered media; ignored media being #- associated to a null list. sub get_source_packages { my ($urpm, $packages, %options) = @_; my (%protected_files, %local_sources, %fullname2id); #- build association hash to retrieve id and examine all list files. foreach (keys %$packages) { foreach (split /\|/, $_) { if ($urpm->{source}{$_}) { $protected_files{$local_sources{$_} = $urpm->{source}{$_}} = undef; } else { $fullname2id{$urpm->{depslist}[$_]->fullname} = $_ . ''; } } } #- examine each medium to search for packages. #- now get rpm file name in hdlist to match list file. my %file2fullnames; foreach my $pkg (@{$urpm->{depslist} || []}) { $file2fullnames{$pkg->filename}{$pkg->fullname} = undef; } #- examine the local repository, which is trusted (no gpg or pgp signature check but md5 is now done). my $dh = $urpm->opendir_safe("$urpm->{cachedir}/rpms"); if ($dh) { while (defined(my $filename = readdir $dh)) { my $filepath = "$urpm->{cachedir}/rpms/$filename"; if (-d $filepath) { } elsif ($options{clean_all} || ! -s _) { unlink $filepath; #- this file should be removed or is already empty. } else { if (keys(%{$file2fullnames{$filename} || {}}) > 1) { $urpm->{error}(N("there are multiple packages with the same rpm filename \"%s\"", $filename)); } elsif (keys(%{$file2fullnames{$filename} || {}}) == 1) { my ($fullname) = keys(%{$file2fullnames{$filename} || {}}); if (defined(my $id = delete $fullname2id{$fullname})) { $local_sources{$id} = $filepath; } else { $options{clean_other} && ! exists $protected_files{$filepath} and unlink $filepath; } } else { $options{clean_other} && ! exists $protected_files{$filepath} and unlink $filepath; } } } closedir $dh; } if ($options{clean_all}) { #- clean download directory, do it here even if this is not the best moment. clean_dir("$urpm->{cachedir}/partial"); } my ($error, @list_error, @list, %examined); foreach my $medium (@{$urpm->{media} || []}) { my (%sources, %list_examined, $list_warning); if (is_valid_medium($medium) && !$medium->{ignore}) { #- always prefer a list file if available. my $listfile = $medium->{list} ? statedir_list($urpm, $medium) : ''; if (!$listfile && $medium->{virtual}) { my $dir = file_from_local_url($medium->{url}); my $with_hdlist_dir = reduce_pathname($dir . ($medium->{with_hdlist} ? "/$medium->{with_hdlist}" : "/..")); my $local_list = 'list' . _hdlist_suffix($medium); $listfile = reduce_pathname("$with_hdlist_dir/../$local_list"); -s $listfile or $listfile = "$dir/list"; } if ($listfile && -r $listfile) { if (my $fh = $urpm->open_safe('<', $listfile)) { local $_; while (<$fh>) { chomp; if (my ($filename) = m!([^/]*\.rpm)$!) { if (keys(%{$file2fullnames{$filename} || {}}) > 1) { $urpm->{error}(N("there are multiple packages with the same rpm filename \"%s\"", $filename)); next; } elsif (keys(%{$file2fullnames{$filename} || {}}) == 1) { my ($fullname) = keys(%{$file2fullnames{$filename} || {}}); if (defined(my $id = $fullname2id{$fullname})) { if (!/\.delta\.rpm$/ || $urpm->is_delta_installable($urpm->{depslist}[$id], $options{root})) { $sources{$id} = "$medium->{url}/$filename"; } } $list_examined{$fullname} = $examined{$fullname} = undef; } } else { chomp; $error = 1; $urpm->{error}(N("unable to correctly parse [%s] on value \"%s\"", $listfile, $_)); last; } } } } elsif ($listfile && -e $listfile) { # list file exists but isn't readable # report error only if no result found, list files are only readable by root push @list_error, N("unable to access list file of \"%s\", medium ignored", $medium->{name}); $< and push @list_error, " " . N("(retry as root?)"); next; } if (defined $medium->{url}) { foreach ($medium->{start} .. $medium->{end}) { my $pkg = $urpm->{depslist}[$_]; my $fi = $pkg->filename; if (keys(%{$file2fullnames{$fi} || {}}) > 1) { $urpm->{error}(N("there are multiple packages with the same rpm filename \"%s\"", $fi)); next; } elsif (keys(%{$file2fullnames{$fi} || {}}) == 1) { my ($fullname) = keys(%{$file2fullnames{$fi} || {}}); unless (exists($list_examined{$fullname})) { ++$list_warning; if (defined(my $id = $fullname2id{$fullname})) { if ($fi !~ /\.delta\.rpm$/ || $urpm->is_delta_installable($urpm->{depslist}[$id], $options{root})) { $sources{$id} = "$medium->{url}/" . $fi; } } $examined{$fullname} = undef; } } } $list_warning && $medium->{list} && -r statedir_list($urpm, $medium) && -f _ and $urpm->{error}(N("medium \"%s\" uses an invalid list file: mirror is probably not up-to-date, trying to use alternate method", $medium->{name})); } elsif (!%list_examined) { $error = 1; $urpm->{error}(N("medium \"%s\" does not define any location for rpm files", $medium->{name})); } } push @list, \%sources; } #- examine package list to see if a package has not been found. foreach (grep { ! exists($examined{$_}) } keys %fullname2id) { # print list errors only once if any $urpm->{error}($_) foreach @list_error; @list_error = (); $error = 1; $urpm->{error}(N("package %s is not found.", $_)); } $error ? @{[]} : (\%local_sources, \@list); } #- checks whether the delta RPM represented by $pkg is installable wrt the #- RPM DB on $root. For this, it extracts the rpm version to which the #- delta applies from the delta rpm filename itself. So naming conventions #- do matter :) sub is_delta_installable { my ($urpm, $pkg, $root) = @_; $pkg->flag_installed or return 0; my $f = $pkg->filename; my $n = $pkg->name; my ($v_match) = $f =~ /^\Q$n\E-(.*)_.+\.delta\.rpm$/; my $db = db_open_or_die($urpm, $root); my $v_installed; $db->traverse(sub { my ($p) = @_; $p->name eq $n and $v_installed = $p->version . '-' . $p->release; }); $v_match eq $v_installed; } #- Obsolescent method. sub download_source_packages { my ($urpm, $local_sources, $list, %options) = @_; my %sources = %$local_sources; my %error_sources; $urpm->lock_urpmi_db('exclusive') if !$options{nolock}; $urpm->copy_packages_of_removable_media($list, \%sources, %options) or return; $urpm->download_packages_of_distant_media($list, \%sources, \%error_sources, %options); $urpm->unlock_urpmi_db unless $options{nolock}; %sources, %error_sources; } #- lock policy concerning chroot : # - lock rpm db in chroot # - lock urpmi db in / sub _lock { my ($urpm, $fh_ref, $file, $b_exclusive) = @_; #- avoid putting a require on Fcntl ':flock' (which is perl and not perl-base). my ($LOCK_SH, $LOCK_EX, $LOCK_NB) = (1, 2, 4); if ($b_exclusive) { #- lock urpmi database, but keep lock to wait for an urpmi.update to finish. } else { #- create the .LOCK file if needed (and if possible) -e $file or open(my $_f, ">", $file); #- lock urpmi database, if the LOCK file doesn't exists no share lock. } my ($sense, $mode) = $b_exclusive ? ('>', $LOCK_EX) : ('<', $LOCK_SH); open $$fh_ref, $sense, $file or return; flock $$fh_ref, $mode|$LOCK_NB or $urpm->{fatal}(7, N("urpmi database locked")); } sub lock_rpm_db { my ($urpm, $b_exclusive) = @_; _lock($urpm, \$RPMLOCK_FILE, "$urpm->{root}/$urpm->{statedir}/.RPMLOCK", $b_exclusive); } sub lock_urpmi_db { my ($urpm, $b_exclusive) = @_; _lock($urpm, \$LOCK_FILE, "$urpm->{statedir}/.LOCK", $b_exclusive); } #- deprecated sub exlock_urpmi_db { my ($urpm) = @_; lock_urpmi_db($urpm, 'exclusive'); } sub _unlock { my ($fh_ref) = @_; #- avoid putting a require on Fcntl ':flock' (which is perl and not perl-base). my $LOCK_UN = 8; #- now everything is finished. #- release lock on database. flock $$fh_ref, $LOCK_UN; close $$fh_ref; } sub unlock_rpm_db { my ($_urpm) = @_; _unlock(\$RPMLOCK_FILE); } sub unlock_urpmi_db { my ($_urpm) = @_; _unlock(\$LOCK_FILE); } #- $list is a [ { pkg_id1 => url1, ... }, { ... }, ... ] #- where there is one hash for each medium in {media} sub copy_packages_of_removable_media { my ($urpm, $list, $sources, %options) = @_; my %removables; #- make sure everything is correct on input... $urpm->{media} or return; @{$urpm->{media}} == @$list or return; #- examine if given medium is already inside a removable device. my $check_notfound = sub { my ($id, $dir, $removable) = @_; if ($dir) { $urpm->try_mounting($dir, $removable); -e $dir or return 2; } foreach (values %{$list->[$id]}) { chomp; my $dir_ = file_from_local_url($_) or next; $dir_ =~ m!/.*/! or next; #- is this really needed?? unless ($dir) { $dir = $dir_; $urpm->try_mounting($dir, $removable); } -r $dir_ or return 1; } 0; }; #- removable media have to be examined to keep mounted the one that has #- more packages than others. my $examine_removable_medium = sub { my ($id, $device) = @_; my $medium = $urpm->{media}[$id]; if (my $dir = file_from_local_url($medium->{url})) { #- the directory given does not exist and may be accessible #- by mounting some other directory. Try to figure it out and mount #- everything that might be necessary. while ($check_notfound->($id, $dir, is_iso($medium->{removable}) ? $medium->{removable} : 'removable')) { is_iso($medium->{removable}) || $options{ask_for_medium} or $urpm->{fatal}(4, N("medium \"%s\" is not selected", $medium->{name})); $urpm->try_umounting($dir); system("/usr/bin/eject '$device' 2>/dev/null"); is_iso($medium->{removable}) || $options{ask_for_medium}(remove_internal_name($medium->{name}), $medium->{removable}) or $urpm->{fatal}(4, N("medium \"%s\" is not selected", $medium->{name})); } if (-e $dir) { while (my ($i, $url) = each %{$list->[$id]}) { chomp $url; my ($filepath, $filename) = do { my $f = file_from_local_url($url) or next; $f =~ m!/.*/! or next; #- is this really needed?? dirname($f), basename($f); }; if (-r $filepath) { #- we should assume a possibly buggy removable device... #- First, copy in partial cache, and if the package is still good, #- transfer it to the rpms cache. unlink "$urpm->{cachedir}/partial/$filename"; if (copy_and_own($filepath, "$urpm->{cachedir}/partial/$filename") && URPM::verify_rpm("$urpm->{cachedir}/partial/$filename", nosignatures => 1)) { #- now we can consider the file to be fine. unlink "$urpm->{cachedir}/rpms/$filename"; urpm::util::move("$urpm->{cachedir}/partial/$filename", "$urpm->{cachedir}/rpms/$filename"); -r "$urpm->{cachedir}/rpms/$filename" and $sources->{$i} = "$urpm->{cachedir}/rpms/$filename"; } } unless ($sources->{$i}) { #- fallback to use other method for retrieving the file later. $urpm->{error}(N("unable to read rpm file [%s] from medium \"%s\"", $filepath, $medium->{name})); } } } else { $urpm->{error}(N("medium \"%s\" is not selected", $medium->{name})); } } else { #- we have a removable device that is not removable, well... $urpm->{error}(N("inconsistent medium \"%s\" marked removable but not really", $medium->{name})); } }; foreach (0..$#$list) { values %{$list->[$_]} or next; my $medium = $urpm->{media}[$_]; #- examine non removable device but that may be mounted. if ($medium->{removable}) { push @{$removables{$medium->{removable}} ||= []}, $_; } elsif (my $dir = file_from_local_url($medium->{url})) { -e $dir || $urpm->try_mounting($dir) or $urpm->{error}(N("unable to access medium \"%s\"", $medium->{name})), next; } } foreach my $device (keys %removables) { next if $device =~ m![^a-zA-Z0-9_./-]!; #- bad path #- Here we have only removable devices. #- If more than one media uses this device, we have to sort #- needed packages to copy the needed rpm files. if (@{$removables{$device}} > 1) { my @sorted_media = sort { values(%{$list->[$a]}) <=> values(%{$list->[$b]}) } @{$removables{$device}}; #- check if a removable device is already mounted (and files present). if (my ($already_mounted_medium) = grep { !$check_notfound->($_) } @sorted_media) { @sorted_media = grep { $_ ne $already_mounted_medium } @sorted_media; unshift @sorted_media, $already_mounted_medium; } #- mount all except the biggest one. my $biggest = pop @sorted_media; foreach (@sorted_media) { $examine_removable_medium->($_, $device); } #- now mount the last one... $removables{$device} = [ $biggest ]; } $examine_removable_medium->($removables{$device}[0], $device); } 1; } # TODO verify that files are downloaded from the right corresponding media sub download_packages_of_distant_media { my ($urpm, $list, $sources, $error_sources, %options) = @_; #- get back all ftp and http accessible rpm files into the local cache foreach my $n (0..$#$list) { my %distant_sources; #- ignore media that contain nothing for the current set of files values %{$list->[$n]} or next; #- examine all files to know what can be indexed on multiple media. while (my ($i, $url) = each %{$list->[$n]}) { #- the given URL is trusted, so the file can safely be ignored. defined $sources->{$i} and next; my $local_file = file_from_local_url($url); if ($local_file && $local_file =~ /\.rpm$/) { if (-r $local_file) { $sources->{$i} = $local_file; } else { $error_sources->{$i} = $local_file; } } elsif ($url =~ m!^([^:]*):/(.*/([^/]*\.rpm))\Z!) { $distant_sources{$i} = "$1:/$2"; #- will download now } else { $urpm->{error}(N("malformed URL: [%s]", $url)); } } #- download files from the current medium. if (%distant_sources) { $urpm->{log}(N("retrieving rpm files from medium \"%s\"...", $urpm->{media}[$n]{name})); if (sync_webfetch($urpm, $urpm->{media}[$n], [ values %distant_sources ], quiet => $options{quiet}, resume => $urpm->{options}{resume}, callback => $options{callback})) { $urpm->{log}(N("...retrieving done")); } else { $urpm->{error}(N("...retrieving failed: %s", $@)); } #- clean files that have not been downloaded, but keep in mind #- there have been problems downloading them at least once, this #- is necessary to keep track of failing downloads in order to #- present the error to the user. foreach my $i (keys %distant_sources) { my ($filename) = $distant_sources{$i} =~ m|/([^/]*\.rpm)$|; if ($filename && -s "$urpm->{cachedir}/partial/$filename" && URPM::verify_rpm("$urpm->{cachedir}/partial/$filename", nosignatures => 1)) { #- it seems the the file has been downloaded correctly and has been checked to be valid. unlink "$urpm->{cachedir}/rpms/$filename"; urpm::util::move("$urpm->{cachedir}/partial/$filename", "$urpm->{cachedir}/rpms/$filename"); -r "$urpm->{cachedir}/rpms/$filename" and $sources->{$i} = "$urpm->{cachedir}/rpms/$filename"; } unless ($sources->{$i}) { $error_sources->{$i} = $distant_sources{$i}; } } } } #- clean failed download which have succeeded. delete @$error_sources{keys %$sources}; 1; } #- prepare transaction. sub prepare_transaction { my ($_urpm, $set, $list, $sources, $transaction_list, $transaction_sources) = @_; foreach my $id (@{$set->{upgrade}}) { foreach (0..$#$list) { exists $list->[$_]{$id} and $transaction_list->[$_]{$id} = $list->[$_]{$id}; } exists $sources->{$id} and $transaction_sources->{$id} = $sources->{$id}; } } #- extract package that should be installed instead of upgraded, #- sources is a hash of id -> source rpm filename. sub extract_packages_to_install { my ($urpm, $sources, $state) = @_; my %inst; my $rej = ref $state ? $state->{rejected} || {} : {}; foreach (keys %$sources) { my $pkg = $urpm->{depslist}[$_] or next; $pkg->flag_disable_obsolete || !$pkg->flag_installed and !grep { exists $rej->{$_}{closure}{$pkg->fullname} } keys %$rej and $inst{$pkg->id} = delete $sources->{$pkg->id}; } \%inst; } # size of the installation progress bar my $progress_size = 45; eval { require Term::ReadKey; ($progress_size) = Term::ReadKey::GetTerminalSize(); $progress_size -= 35; $progress_size < 5 and $progress_size = 5; }; # install logger callback sub install_logger { my ($urpm, $type, $id, $subtype, $amount, $total) = @_; my $pkg = defined $id && $urpm->{depslist}[$id]; my $total_pkg = $urpm->{nb_install}; local $| = 1; if ($subtype eq 'start') { $urpm->{logger_progress} = 0; if ($type eq 'trans') { $urpm->{logger_id} ||= 0; $urpm->{logger_count} ||= 0; my $p = N("Preparing..."); print $p, " " x (33 - length $p); } else { ++$urpm->{logger_id}; my $pname = $pkg ? $pkg->name : ''; ++$urpm->{logger_count} if $pname; my $cnt = $pname ? $urpm->{logger_count} : '-'; $pname ||= N("[repackaging]"); printf "%9s: %-22s", $cnt . "/" . $total_pkg, $pname; } } elsif ($subtype eq 'stop') { if ($urpm->{logger_progress} < $progress_size) { print '#' x ($progress_size - $urpm->{logger_progress}), "\n"; $urpm->{logger_progress} = 0; } } elsif ($subtype eq 'progress') { my $new_progress = $total > 0 ? int($progress_size * $amount / $total) : $progress_size; if ($new_progress > $urpm->{logger_progress}) { print '#' x ($new_progress - $urpm->{logger_progress}); $urpm->{logger_progress} = $new_progress; $urpm->{logger_progress} == $progress_size and print "\n"; } } } #- install packages according to each hash (remove, install or upgrade). sub install { my ($urpm, $remove, $install, $upgrade, %options) = @_; my %readmes; my $db = db_open_or_die($urpm, $urpm->{root}, !$options{test}); #- open in read/write mode unless testing installation. my $trans = $db->create_transaction($urpm->{root}); if ($trans) { sys_log("transaction on %s (remove=%d, install=%d, upgrade=%d)", $urpm->{root} || '/', scalar(@{$remove || []}), scalar(values %$install), scalar(values %$upgrade)); $urpm->{log}(N("created transaction for installing on %s (remove=%d, install=%d, upgrade=%d)", $urpm->{root} || '/', scalar(@{$remove || []}), scalar(values %$install), scalar(values %$upgrade))); } else { return N("unable to create transaction"); } my ($update, @l) = 0; my @produced_deltas; foreach (@$remove) { if ($trans->remove($_)) { $urpm->{log}(N("removing package %s", $_)); } else { $urpm->{error}(N("unable to remove package %s", $_)); } } foreach my $mode ($install, $upgrade) { foreach (keys %$mode) { my $pkg = $urpm->{depslist}[$_]; $pkg->update_header($mode->{$_}); if ($pkg->payload_format eq 'drpm') { #- handle deltarpms my $true_rpm = urpm::sys::apply_delta_rpm($mode->{$_}, "$urpm->{cachedir}/rpms", $pkg); if ($true_rpm) { push @produced_deltas, ($mode->{$_} = $true_rpm); #- fix path } else { $urpm->{error}(N("unable to extract rpm from delta-rpm package %s", $mode->{$_})); } } if ($trans->add($pkg, update => $update, $options{excludepath} ? (excludepath => [ split /,/, $options{excludepath} ]) : () )) { $urpm->{log}(N("adding package %s (id=%d, eid=%d, update=%d, file=%s)", scalar($pkg->fullname), $_, $pkg->id, $update, $mode->{$_})); } else { $urpm->{error}(N("unable to install package %s", $mode->{$_})); } } ++$update; } if (($options{nodeps} || !(@l = $trans->check(%options))) && ($options{noorder} || !(@l = $trans->order))) { my $fh; #- assume default value for some parameter. $options{delta} ||= 1000; $options{callback_open} ||= sub { my ($_data, $_type, $id) = @_; $fh = $urpm->open_safe('<', $install->{$id} || $upgrade->{$id}); $fh ? fileno $fh : undef; }; $options{callback_close} ||= sub { my ($urpm, undef, $pkgid) = @_; return unless defined $pkgid; my $pkg = $urpm->{depslist}[$pkgid]; my $fullname = $pkg->fullname; my $trtype = (grep { /\Q$fullname\E/ } values %$install) ? 'install' : '(upgrade|update)'; foreach ($pkg->files) { /\bREADME(\.$trtype)?\.urpmi$/ and $readmes{$_} = $fullname } close $fh if defined $fh; }; if ($::verbose >= 0 && (scalar keys %$install || scalar keys %$upgrade)) { $options{callback_inst} ||= \&install_logger; $options{callback_trans} ||= \&install_logger; } @l = $trans->run($urpm, %options); #- don't clear cache if transaction failed. We might want to retry. if (@l == 0 && !$options{test} && $options{post_clean_cache}) { #- examine the local cache to delete packages which were part of this transaction foreach (keys %$install, keys %$upgrade) { my $pkg = $urpm->{depslist}[$_]; unlink "$urpm->{cachedir}/rpms/" . $pkg->filename; } } } unlink @produced_deltas; if ($::verbose >= 0) { foreach (keys %readmes) { print "-" x 70, "\n", N("More information on package %s", $readmes{$_}), "\n"; print cat_($_); print "-" x 70, "\n"; } } @l; } #- install all files to node as remembered according to resolving done. sub parallel_install { my @para = @_; my ($urpm, $_remove, $_install, $_upgrade, %_options) = @para; $urpm->{parallel_handler}->parallel_install(@para); } #- find packages to remove. #- options: #- bundle #- callback_base #- callback_fuzzy #- callback_notfound #- force #- matches #- root #- test sub find_packages_to_remove { my ($urpm, $state, $l, %options) = @_; if ($urpm->{parallel_handler}) { #- invoke parallel finder. $urpm->{parallel_handler}->parallel_find_remove($urpm, $state, $l, %options, find_packages_to_remove => 1); } else { my $db = db_open_or_die($urpm, $options{root}); my (@m, @notfound); if (!$options{matches}) { foreach (@$l) { my ($n, $found); #- check if name-version-release-architecture was given. if (($n) = /^(.*)-[^\-]*-[^\-]*\.[^\.\-]*$/) { $db->traverse_tag('name', [ $n ], sub { my ($p) = @_; $p->fullname eq $_ or return; $urpm->resolve_rejected($db, $state, $p, removed => 1, bundle => $options{bundle}); push @m, scalar $p->fullname; $found = 1; }); $found and next; } #- check if name-version-release was given. if (($n) = /^(.*)-[^\-]*-[^\-]*$/) { $db->traverse_tag('name', [ $n ], sub { my ($p) = @_; my ($name, $version, $release) = $p->fullname; "$name-$version-$release" eq $_ or return; $urpm->resolve_rejected($db, $state, $p, removed => 1, bundle => $options{bundle}); push @m, scalar $p->fullname; $found = 1; }); $found and next; } #- check if name-version was given. if (($n) = /^(.*)-[^\-]*$/) { $db->traverse_tag('name', [ $n ], sub { my ($p) = @_; my ($name, $version) = $p->fullname; "$name-$version" eq $_ or return; $urpm->resolve_rejected($db, $state, $p, removed => 1, bundle => $options{bundle}); push @m, scalar $p->fullname; $found = 1; }); $found and next; } #- check if only name was given. $db->traverse_tag('name', [ $_ ], sub { my ($p) = @_; $p->name eq $_ or return; $urpm->resolve_rejected($db, $state, $p, removed => 1, bundle => $options{bundle}); push @m, scalar $p->fullname; $found = 1; }); $found and next; push @notfound, $_; } if (!$options{force} && @notfound && @$l > 1) { $options{callback_notfound} && $options{callback_notfound}->($urpm, @notfound) or return (); } } if ($options{matches} || @notfound) { my $match = join "|", map { quotemeta } @$l; my $qmatch = qr/$match/; #- reset what has been already found. %$state = (); @m = (); #- search for packages that match, and perform closure again. $db->traverse(sub { my ($p) = @_; my $f = scalar $p->fullname; $f =~ $qmatch or return; $urpm->resolve_rejected($db, $state, $p, removed => 1, bundle => $options{bundle}); push @m, $f; }); if (!$options{force} && @notfound) { if (@m) { $options{callback_fuzzy} && $options{callback_fuzzy}->($urpm, @$l > 1 ? $match : $l->[0], @m) or return (); } else { $options{callback_notfound} && $options{callback_notfound}->($urpm, @notfound) or return (); } } } #- check if something needs to be removed. find_removed_from_basesystem($urpm, $db, $state, $options{callback_base}) or return (); } grep { $state->{rejected}{$_}{removed} && !$state->{rejected}{$_}{obsoleted} } keys %{$state->{rejected}}; } sub find_removed_from_basesystem { my ($urpm, $db, $state, $callback_base) = @_; if ($callback_base && %{$state->{rejected} || {}}) { my %basepackages; my @dont_remove = ('basesystem', split /,\s*/, $urpm->{global_config}{'prohibit-remove'}); #- check if a package to be removed is a part of basesystem requires. $db->traverse_tag('whatprovides', \@dont_remove, sub { my ($p) = @_; $basepackages{$p->fullname} = 0; }); foreach (grep { $state->{rejected}{$_}{removed} && !$state->{rejected}{$_}{obsoleted} } keys %{$state->{rejected}}) { exists $basepackages{$_} or next; ++$basepackages{$_}; } if (grep { $_ } values %basepackages) { return $callback_base->($urpm, grep { $basepackages{$_} } keys %basepackages); } } return 1; } #- remove packages from node as remembered according to resolving done. sub parallel_remove { my ($urpm, $remove, %options) = @_; my $state = {}; my $callback = sub { $urpm->{fatal}(1, "internal distributed remove fatal error") }; $urpm->{parallel_handler}->parallel_find_remove($urpm, $state, $remove, %options, callback_notfound => undef, callback_fuzzy => $callback, callback_base => $callback, ); } #- misc functions to help finding ask_unselect and ask_remove elements with their reasons translated. sub unselected_packages { my (undef, $state) = @_; grep { $state->{rejected}{$_}{backtrack} } keys %{$state->{rejected} || {}}; } sub uniq { my %l; $l{$_} = 1 foreach @_; grep { delete $l{$_} } @_ } sub translate_why_unselected { my ($urpm, $state, @fullnames) = @_; join("\n", map { translate_why_unselected_one($urpm, $state, $_) } sort @fullnames); } sub translate_why_unselected_one { my ($urpm, $state, $fullname) = @_; my $rb = $state->{rejected}{$fullname}{backtrack}; my @froms = keys %{$rb->{closure} || {}}; my @unsatisfied = @{$rb->{unsatisfied} || []}; my $s = join ", ", ( (map { N("due to missing %s", $_) } @froms), (map { N("due to unsatisfied %s", $_) } uniq(map { #- XXX in theory we shouldn't need this, dependencies (and not ids) should #- already be present in @unsatisfied. But with biarch packages this is #- not always the case. /\D/ ? $_ : scalar($urpm->{depslist}[$_]->fullname); } @unsatisfied)), $rb->{promote} && !$rb->{keep} ? N("trying to promote %s", join(", ", @{$rb->{promote}})) : (), $rb->{keep} ? N("in order to keep %s", join(", ", @{$rb->{keep}})) : (), ); $fullname . ($s ? " ($s)" : ''); } sub removed_packages { my (undef, $state) = @_; grep { $state->{rejected}{$_}{removed} && !$state->{rejected}{$_}{obsoleted}; } keys %{$state->{rejected} || {}}; } sub translate_why_removed { my ($urpm, $state, @fullnames) = @_; join("\n", map { translate_why_removed_one($urpm, $state, $_) } sort @fullnames); } sub translate_why_removed_one { my ($urpm, $state, $fullname) = @_; my $closure = $state->{rejected}{$fullname}{closure}; my ($from) = keys %$closure; my ($whyk) = keys %{$closure->{$from}}; my $whyv = $closure->{$from}{$whyk}; my $frompkg = $urpm->search($from, strict_fullname => 1); my $s = do { if ($whyk =~ /old_requested/) { N("in order to install %s", $frompkg ? scalar $frompkg->fullname : $from); } elsif ($whyk =~ /unsatisfied/) { join(",\n ", map { if (/([^\[\s]*)(?:\[\*\])?(?:\[|\s+)([^\]]*)\]?$/ && $2 ne '*') { N("due to unsatisfied %s", "$1 $2"); } else { N("due to missing %s", $_); } } @$whyv); } elsif ($whyk =~ /conflicts/) { N("due to conflicts with %s", $whyv); } elsif ($whyk =~ /unrequested/) { N("unrequested"); } else { undef; } }; #- now insert the reason if available. $fullname . ($s ? "\n ($s)" : ''); } sub check_sources_signatures { my ($urpm, $sources_install, $sources, %options) = @_; sort(_check_sources_signatures($urpm, $sources_install, %options), _check_sources_signatures($urpm, $sources, %options)); } sub _check_sources_signatures { my ($urpm, $sources, %options) = @_; my ($medium, %invalid_sources); foreach my $id (keys %$sources) { my $filepath = $sources->{$id}; my $verif = URPM::verify_signature($filepath); if ($verif =~ /NOT OK/) { $verif =~ s/\n//g; $invalid_sources{$filepath} = N("Invalid signature (%s)", $verif); } else { unless ($medium && is_valid_medium($medium) && $medium->{start} <= $id && $id <= $medium->{end}) { $medium = undef; foreach (@{$urpm->{media}}) { is_valid_medium($_) && $_->{start} <= $id && $id <= $_->{end} and $medium = $_, last; } } #- no medium found for this rpm ? next if !$medium; #- check whether verify-rpm is specifically disabled for this medium next if defined $medium->{'verify-rpm'} && !$medium->{'verify-rpm'}; my $key_ids = $medium->{'key-ids'} || $urpm->{options}{'key-ids'}; #- check that the key ids of the medium match the key ids of the package. if ($key_ids) { my $valid_ids = 0; my $invalid_ids = 0; foreach my $key_id ($verif =~ /(?:key id \w{8}|#)(\w+)/gi) { if (grep { hex($_) == hex($key_id) } split /[,\s]+/, $key_ids) { ++$valid_ids; } else { ++$invalid_ids; } } if ($invalid_ids) { $invalid_sources{$filepath} = N("Invalid Key ID (%s)", $verif); } elsif (!$valid_ids) { $invalid_sources{$filepath} = N("Missing signature (%s)", $verif); } } #- invoke check signature callback. $options{callback} and $options{callback}->( $urpm, $filepath, %options, id => $id, verif => $verif, why => $invalid_sources{$filepath}, ); } } map { ($options{basename} ? basename($_) : $_) . ($options{translate} ? ": $invalid_sources{$_}" : "") } keys %invalid_sources; } #- get reason of update for packages to be updated #- use all update medias if none given sub get_updates_description { my ($urpm, @update_medias) = @_; my %update_descr; my ($cur, $section); @update_medias or @update_medias = grep { !$_->{ignore} && $_->{update} } @{$urpm->{media}}; foreach (map { cat_(statedir_descriptions($urpm, $_)), '%package dummy' } @update_medias) { /^%package (.+)/ and do { if (exists $cur->{importance} && $cur->{importance} ne "security" && $cur->{importance} ne "bugfix") { $cur->{importance} = 'normal'; } $update_descr{$_} = $cur foreach @{$cur->{pkgs}}; $cur = {}; $cur->{pkgs} = [ split /\s/, $1 ]; $section = 'pkg'; next; }; /^Updated: (.+)/ && $section eq 'pkg' and $cur->{updated} = $1; /^Importance: (.+)/ && $section eq 'pkg' and $cur->{importance} = $1; /^%pre/ and do { $section = 'pre'; next }; /^%description/ and do { $section = 'description'; next }; $section eq 'pre' and $cur->{pre} .= $_; $section eq 'description' and $cur->{description} .= $_; } \%update_descr; } #- parse an MD5SUM file from a mirror sub get_md5sum { my ($md5sum_file, $f) = @_; my $basename = basename($f); my ($retrieved_md5sum) = map { my ($md5sum, $file) = m|(\S+)\s+(?:\./)?(\S+)|; $file && $file eq $basename ? $md5sum : @{[]}; } cat_($md5sum_file); $retrieved_md5sum; } sub parse_md5sum { my ($urpm, $md5sum_file, $basename) = @_; $urpm->{log}(N("examining MD5SUM file")); my $retrieved_md5sum = get_md5sum($md5sum_file, $basename) or $urpm->{log}(N("warning: md5sum for %s unavailable in MD5SUM file", $basename)); return $retrieved_md5sum; } sub recompute_local_md5sum { my ($urpm, $medium, $force) = @_; if ($force) { #- force downloading the file again, else why a force option has been defined ? delete $medium->{md5sum}; } else { compute_local_md5sum($urpm, $medium) if !$medium->{md5sum}; } } sub compute_local_md5sum { my ($urpm, $medium) = @_; $urpm->{log}(N("computing md5sum of existing source hdlist (or synthesis)")); my $f = statedir_hdlist_or_synthesis($urpm, $medium); if (-e $f) { $medium->{md5sum} = md5sum($f); } } sub syserror { my ($urpm, $msg, $info) = @_; $urpm->{error}("$msg [$info] [$!]") } sub open_safe { my ($urpm, $sense, $filename) = @_; open my $f, $sense, $filename or $urpm->syserror($sense eq '>' ? "Can't write file" : "Can't open file", $filename), return undef; return $f; } sub opendir_safe { my ($urpm, $dirname) = @_; opendir my $d, $dirname or $urpm->syserror("Can't open directory", $dirname), return undef; return $d; } sub error_restricted ($) { my ($urpm) = @_; $urpm->{fatal}(2, N("This operation is forbidden while running in restricted mode")); } sub DESTROY {} 1; __END__ =head1 NAME urpm - Mandriva perl tools to handle the urpmi database =head1 DESCRIPTION C is used by urpmi executables to manipulate packages and media on a Mandriva Linux distribution. =head1 SEE ALSO The C package is used to manipulate at a lower level hdlist and rpm files. =head1 COPYRIGHT Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005 MandrakeSoft SA Copyright (C) 2005, 2006 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. =cut # ex: set ts=8 sts=4 sw=4 noet: