diff options
-rw-r--r-- | urpm.pm | 836 |
1 files changed, 836 insertions, 0 deletions
diff --git a/urpm.pm b/urpm.pm new file mode 100644 index 00000000..cf7cf3e6 --- /dev/null +++ b/urpm.pm @@ -0,0 +1,836 @@ +package urpm; + +use strict; +use vars qw($VERSION @ISA); + +$VERSION = '1.30'; + +=head1 NAME + +urpm - Mandrake perl tools to handle urpmi database + +=head1 SYNOPSYS + + require urpm; + + my $urpm = new urpm; + + $urpm->read_depslist(); + $urpm->read_provides(); + $urpm->read_compss(); + $urpm->read_config(); + +=head1 DESCRIPTION + +C<urpm> is used by urpmi executable to manipulate packages and mediums +on a Linux-Mandrake distribution. + +=head1 SEE ALSO + +rpmtools package is used to manipulate at a lower level hdlist and rpm +files. + +=head1 COPYRIGHT + +Copyright (C) 2000 MandrakeSoft <fpons@mandrakesoft.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +=cut + +use rpmtools; + +#- create a new urpm object. +sub new { + my ($class) = @_; + bless { + config => "/etc/urpmi/urpmi.cfg", + depslist => "/var/lib/urpmi/depslist.ordered", + provides => "/var/lib/urpmi/provides", + compss => "/var/lib/urpmi/compss", + statedir => "/var/lib/urpmi", + media => undef, + params => new rpmtools, + + error => sub { printf STDERR "%s\n", $_[0] }, + log => sub { printf STDERR "%s\n", $_[0] }, + }, $class; +} + +#- quoting/unquoting a string that may be containing space chars. +sub quotespace { local $_ = $_[0]; s/(\s)/\\$1/g; $_ } +sub unquotespace { local $_ = $_[0]; s/\\(\s)/$1/g; $_ } + +#- read /etc/urpmi/urpmi.cfg as config file, keep compability with older +#- configuration file by examining if one entry is of the form +#- <name> <url> { +#- ... +#- } +#- else only this form is used +#- <name> <url> +#- <name> <ftp_url> with <relative_path_hdlist> +sub read_config { + my ($urpm) = @_; + + #- keep in mind if it has been called before. + $urpm->{media} ||= []; + + #- check urpmi.cfg content, if the file is old keep track + #- of old format used. + local (*F, $_); + open F, $urpm->{config}; #- no filename can be allowed on some case + while (<F>) { + chomp; s/#.*$//; s/\s*$//; + /^\s*(.*?[^\\])\s+(?:(.*?[^\\])\s+)?\{$/ and do { #- urpmi.cfg format extention + my $medium = { name => unquotespace($1), clear_url => unquotespace($2) }; + while (<F>) { + chomp; s/#.*$//; s/\s*$//; + /^\s*hdlist\s*:\s*(.*)$/ and $medium->{hdlist} = $1, next; + /^\s*with_hdlist\s*:\s*(.*)$/ and $medium->{with_hdlist} = $1, next; + /^\s*list\s*:\s*(.*)$/ and $medium->{list} = $1, next; + /^\s*removable\s*:\s*(.*)$/ and $medium->{removable} = $1, next; + /^\s*ignore\s*$/ and $medium->{ignore} = 1, next; + /^\s*modified\s*$/ and $medium->{modified} = 1, next; + /^\s*}$/ and last; + /^\s*$/ or $urpm->{error}("syntax error at line $. in $urpm->{config}"); + } + $urpm->probe_medium($medium); + push @{$urpm->{media}}, $medium; + next; }; + /^\s*(.*?[^\\])\s+(.*?[^\\])\s+with\s+(.*)$/ and do { #- urpmi.cfg old format for ftp + my $medium = { name => unquotespace($1), clear_url => unquotespace($2), with_hdlist => unquotespace($3) }; + $urpm->probe_medium($medium); + push @{$urpm->{media}}, $medium; + next; }; + /^\s+(.*?[^\\])\s+(?:(.*?[^\\])\s*)$/ and do { #- urpmi.cfg old format (assume hdlist.<name>.cz2?) + my $medium = { name => unquotespace($1), clear_url => unquotespace($2) }; + $urpm->probe_medium($medium); + push @{$urpm->{media}}, $medium; + next; }; + /^\s*$/ or $urpm->{error}("syntax error at line $. in [$urpm->{config}]"); + } + close F; + + #- keep in mind when an hdlist/list file is used, really usefull for + #- the next probe. + my (%hdlists, %lists); + foreach (@{$urpm->{media}}) { + exists $hdlists{$_->{hdlist}} and + $_->{ignore} = 1, $urpm->{error}("medium \"$_->{name}\" try to use an already used hdlist, medium ignored"); + $_->{ignore} or $hdlists{$_->{hdlist}} = undef; + exists $lists{$_->{list}} and + $_->{ignore} = 1, $urpm->{error}("medium \"$_->{name}\" try to use an already used list, medium ignored"); + $_->{ignore} or $lists{$_->{list}} = undef; + } + + #- urpmi.cfg if old is not enough to known the various media, track + #- directly into /var/lib/urpmi, + foreach (glob("$urpm->{statedir}/hdlist.*")) { + if (/\/hdlist\.((.*)\.cz2?)$/) { + #- check if it has already been detected above. + exists $hdlists{"hdlist.$1"} and next; + + #- if not this is a new media to take care if + #- there is a list file. + if (-s "$urpm->{statedir}/list.$2") { + if (exists $lists{"list.$2"}) { + $urpm->{error}("unable to take medium \"$2\" into account as list file is already used by another medium"); + } else { + my $medium; + foreach (@{$urpm->{media}}) { + $_->{ignore} and next; + $_->{name} eq $2 and $medium = $_, last; + } + $medium and $urpm->{error}("unable to use name \"$2\" for unamed medium because it is already used"), next; + + $medium = { name => $2, hdlist => "hdlist.$1", list => "list.$2" }; + $urpm->probe_medium($medium); + push @{$urpm->{media}}, $medium; + } + } else { + $urpm->{error}("unable to take medium \"$2\" into account as no list file [$urpm->{statedir}/list.$2] exists"); + } + } else { + $urpm->{error}("unable to determine medium of this hdlist file [$_]"); + } + } + + #- check the presence of hdlist file and list file if necessary. + #- TODO?: degraded mode is possible with a list file but no hdlist, the medium + #- is no longer updatable nor removable TODO + foreach (@{$urpm->{media}}) { + $_->{ignore} and next; + -r "$urpm->{statedir}/$_->{hdlist}" or + $_->{ignore} = 1, $urpm->{error}("unable to access hdlist file of \"$_->{name}\", medium ignored"); + $_->{list} && -r "$urpm->{statedir}/$_->{list}" or + $_->{ignore} = 1, $urpm->{error}("unable to access list file of \"$_->{name}\", medium ignored"); + } +} + +#- probe medium to be used, take old medium into account too. +sub probe_medium { + my ($urpm, $medium) = @_; + + unless ($medium->{ignore} || $medium->{hdlist}) { + $medium->{hdlist} = "hdlist.$medium->{name}.cz"; + -e "$urpm->{statedir}/$medium->{hdlist}" or $medium->{hdlist} = "hdlist.$medium->{name}.cz2"; + -e "$urpm->{statedir}/$medium->{hdlist}" or + $medium->{ignore} = 1, $urpm->{error}("unable to find hdlist file for \"$medium->{name}\", medium ignored"); + } + unless ($medium->{ignore} || $medium->{list}) { + $medium->{list} = "list.$1"; + -e "$urpm->{statedir}/$medium->{list}" or + $medium->{ignore} = 1, $urpm->{error}("unable to find list file for \"$medium->{name}\", medium ignored"); + } + + #- there is a little more to do at this point as url is not known, inspect directly list file for it. + unless ($medium->{ignore} || $medium->{url} || $medium->{clear_url}) { + my %probe; + local (*F, $_); + open F, "$urpm->{statedir}/$medium->{list}"; + while (<F>) { + /^(.*)\/[^\/]*/ and $probe{$1} = undef; + } + close F; + foreach (sort { length($a) <=> length($b) } keys %probe) { + if ($medium->{url}) { + $medium->{url} eq substr($_, 0, length($medium->{url})) or + $medium->{ignore} = 1, $urpm->{error}("incoherent list file for \"$medium->{name}\", medium ignored"), last; + } else { + $medium->{url} = $_; + } + } + $medium->{url} or + $medium->{ignore} = 1, $urpm->{error}("unable to inspect list file for \"$medium->{name}\", medium ignored"); + } + $medium->{url} ||= $medium->{clear_url}; +} + +#- write back urpmi.cfg code to allow modification of medium listed. +sub write_config { + my ($urpm) = @_; + + #- avoid trashing exiting configuration in this case. + $urpm->{media} or return; + + local *F; + open F, ">$urpm->{config}" or $urpm->{error}("unable to write config file [$urpm->{config}]"); + foreach my $medium (@{$urpm->{media}}) { + printf F "%s %s {\n", quotespace($medium->{name}), quotespace($medium->{clear_url}); + foreach (qw(hdlist with_hdlist list removable)) { + $medium->{$_} and printf F " %s: %s\n", $_, $medium->{$_}; + } + foreach (qw(ignore modified)) { + $medium->{$_} and printf F " %s\n", $_; + } + printf F "}\n\n"; + } + close F; + $urpm->{log}("write config file [$urpm->{config}]"); +} + +#- add a new medium, sync the config file accordingly. +sub add_medium { + my ($urpm, $name, $url, $with_hdlist) = @_; + + #- make sure configuration has been read. + $urpm->{media} or $urpm->read_config(); + + #- if a medium with that name has already been found + #- we have to exit now + my ($medium); + foreach (@{$urpm->{media}}) { + $_->{ignore} and next; + $_->{name} eq $2 and $medium = $_; + } + $medium and $urpm->{error}("medium \"$medium\" already exists"), return; + + #- creating the medium info. + $medium = { name => $name, + url => $url, + hdlist => "hdlist.$name.cz", + list => "list.$name", + modified => 1, + }; + + #- check to see if the medium is using file protocol or removable medium. + if (my ($prefix, $dir) = $url =~ /^(removable_.*?|file):\/(.*)/) { + #- the directory given does not exist or may be accessible + #- by mounting some other. try to figure out these directory and + #- mount everything necessary. + $urpm->try_mounting($dir); + + #- check if directory is somewhat normalized so that we can get back hdlist, + #- check it that case if depslist, compss and provides file are also + #- provided. + if (!($with_hdlist && -e "$dir/$with_hdlist") && $dir =~ /RPMS([^\/]*)\/*$/) { + foreach my $rdir (qw(Mandrake/base ../Mandrake/base ..)) { + -e "$dir/$_/hdlist$1.cz" and $with_hdlist = "$_/hdlist$1.cz", last; + -e "$dir/$_/hdlist$1.cz2" and $with_hdlist = "$_/hdlist$1.cz2", last; + } + } + + #- add some more flags for this type of medium. + $medium->{clear_url} = $url; + $medium->{removable} = $url =~ /^removable_([^_:]*)(?:_[^:]*)?:/ && "/dev/$1"; + } + + #- all flags once everything has been computed. + $with_hdlist and $medium->{with_hdlist} = $with_hdlist; + + #- create an entry in media list. + push @{$urpm->{media}}, $medium; + + #- keep in mind the database has been modified and base files need to be updated. + $urpm->{modified} = 1; +} + +sub remove_media { + my $urpm = shift; + my %media; @media{@_} = undef; + my @result; + + foreach (@{$urpm->{media}}) { + if (exists $media{$_->{name}}) { + $media{$_->{name}} = 1; #- keep it mind this one has been removed + + #- remove file associated with this medium. + #- this is the hdlist and the list files. + unlink "$urpm->{statedir}/$_->{hdlist}"; + unlink "$urpm->{statedir}/$_->{list}"; + } else { + push @result, $_; #- not removed so keep it + } + } + + #- check if some arguments does not correspond to medium name. + foreach (keys %media) { + if ($media{$_}) { + #- when a medium is removed, depslist and others need to be recomputed. + $urpm->{modified} = 1; + } else { + $urpm->{error}("trying to remove inexistant medium \"$_\""); + } + } + + #- special case if there is no more media registered. + #- there is no need to recompute the hdlist and the files + #- can be safely removed. + if ($urpm->{modified} && @result == 0) { + unlink $urpm->{depslist}; + unlink $urpm->{provides}; + unlink $urpm->{compss}; + } + + #- restore newer media list. + $urpm->{media} = \@result; +} + +sub select_media { + my $urpm = shift; + my %media; @media{@_} = undef; + + foreach (@{$urpm->{media}}) { + if (exists $media{$_->{name}}) { + $media{$_->{name}} = 1; #- keep it mind this one has been selected. + + #- select medium by setting modified flags, do not check ignore. + $_->{modified} = 1; + } + } + + #- check if some arguments does not correspond to medium name. + foreach (keys %media) { + unless ($media{$_}) { + $urpm->{error}("trying to select inexistant medium \"$_\""); + } + } +} + +#- update urpmi database regarding the current configuration. +#- take care of modification and try some trick to bypass +#- computational of base files. +sub update_media { + my ($urpm, %options) = @_; #- do not trust existing hdlist and try to recompute them. + + #- avoid trashing existing configuration in this case. + $urpm->{media} or return; + + #- examine each medium to see if one of them need to be updated. + #- if this is the case and if not forced, try to use a pre-calculated + #- hdlist file else build it from rpms files. + foreach my $medium (@{$urpm->{media}}) { + #- take care of modified medium only or all if all have to be recomputed. + $medium->{ignore} and next; + $options{all} || $medium->{modified} or next; + + #- list of rpm files for this medium, only available for local medium where + #- the source hdlist is not used (use force). + my ($prefix, $dir, $error, @files); + + #- check to see if the medium is using file protocol or removable medium. + if (($prefix, $dir) = $medium->{url} =~ /^(removable_.*?|file):\/(.*)/) { + #- the directory given does not exist and may be accessible + #- by mounting some other. try to figure out these directory and + #- mount everything necessary. + $urpm->try_mounting($dir); + + #- if the source hdlist is present and we are not forcing using rpms file + if (!$options{force} && $medium->{with_hdlist} && -e "$dir/$medium->{with_hdlist}") { + system("cp", "-f", "$dir/$medium->{with_hdlist}", "$urpm->{statedir}/.$medium->{hdlist}"); + -s "$urpm->{statedir}/.$medium->{hdlist}" + or $error = 1, $urpm->{error}("copy of [$dir/$medium->{with_hdlist}] failed"); + } else { + #- try to find rpm files, use recursive method, added additional + #- / after dir to make sure it will be taken into account if this + #- is a symlink to a directory. + @files = split "\n", `find '$dir/' -name "*.rpm" -print`; + + #- check files contains something good! + if (@files > 0) { + #- we need to rebuild from rpm files the hdlist. + $urpm->{params}->build_hdlist("$urpm->{statedir}/.$medium->{hdlist}", @files); + } else { + $error = 1; + $urpm->{error}("no rpm files found from [$dir/]"); + } + } + } else { + system("wget", "-O", "$urpm->{statedir}/.$medium->{hdlist}", "$medium->{url}/$medium->{with_hdlist}"); + $? == 0 or $error = 1, $urpm->{error}("wget of [<source_url>/$medium->{with_hdlist}] failed (maybe wget is missing?)"); + } + + #- build list file according to hdlist used. + #- make sure group and other does not have any access to this file. + unless ($error) { + local *LIST; + my $mask = umask 077; + open LIST, ">$urpm->{statedir}/.$medium->{list}" + or $error = 1, $urpm->{error}("unable to write list file of \"$medium->{name}\""); + umask $mask; + if (@files) { + foreach (@files) { + print LIST "$prefix:/$_\n"; + } + } else { + local (*F, $_); + open F, "parsehdlist '$urpm->{statedir}/.$medium->{hdlist}' |"; + while (<F>) { + print LIST "$medium->{url}/$_"; + } + close F; + } + close LIST; + + #- check if at least something has been written into list file. + -s "$urpm->{statedir}/.$medium->{list}" + or $error = 1, $urpm->{error}("nothing written in list file for \"$medium->{name}\""); + } + + if ($error) { + #- an error has occured for updating the medium, we have to remove tempory files. + unlink "$urpm->{statedir}/.$medium->{hdlist}"; + unlink "$urpm->{statedir}/.$medium->{list}"; + } else { + #- make sure to rebuild base files and clean medium modified state. + $medium->{modified} = 0; + $urpm->{modified} = 1; + + #- but use newly created file. + unlink "$urpm->{statedir}/$medium->{hdlist}"; + unlink "$urpm->{statedir}/$medium->{list}"; + rename "$urpm->{statedir}/.$medium->{hdlist}", "$urpm->{statedir}/$medium->{hdlist}"; + rename "$urpm->{statedir}/.$medium->{list}", "$urpm->{statedir}/$medium->{list}"; + } + } + + #- build base files (depslist.ordered, provides, compss) according to modified global status. + if ($urpm->{modified}) { + #- special case if there is no more media registered. + #- there is no need to recompute the hdlist and the files + #- can be safely removed. + if (@{$urpm->{media}} == 0) { + unlink $urpm->{depslist}; + unlink $urpm->{provides}; + unlink $urpm->{compss}; + + $urpm->{modified} = 0; + } + + if (!$options{force} && @{$urpm->{media}} == 1 && $urpm->{media}[0]{with_hdlist}) { + #- this is a special mode where is only one hdlist using a source hdlist, in such + #- case we are searching for source depslist, provides and compss files. + #- if they are not found or if force is used, an error message is printed and + #- we continue using computed results. + my $medium = $urpm->{media}[0]; + my $basedir = $medium->{with_hdlist} =~ /^(.*)\/[^\/]*$/ && $1; + + foreach my $target ($urpm->{depslist}, $urpm->{provides}, $urpm->{compss}, 'END') { + $target eq 'END' and $urpm->{modified} = 0, last; #- assume everything is ok. + my $basename = $target =~ /^.*\/([^\/]*)$/ && $1; + + if (my ($prefix, $dir) = $medium->{url} =~ /^(removable_.*?|file):\/(.*)/) { + #- the directory should be existing in any cases or this is an error + #- so there is no need of trying to mount it. + if (-e "$dir/$basedir/$basename") { + system("cp", "-f", "$dir/$basedir/$basename", $target); + $? == 0 or $urpm->{error}("unable to copy source of [$target] from [$dir/$basedir/$basename]"), last; + } else { + $urpm->{error}("source of [$target] not found as [$dir/$basedir/$basename]"), last; + } + } else { + #- we have to use wget here instead. + system("wget", "-O", $target, "$medium->{url}/$basedir/$basename"); + $? == 0 or $urpm->{error}("wget of [$medium->{url}/$basedir/$basename] failed (maybe wget is missing?)"), last; + } + } + } + + if ($urpm->{modified}) { + #- cleaning. + $urpm->{params}->clean(); + + #- if a provides exists, try to use it to speed up process + #- but this is not mandatory here. + -r $urpm->{provides} and $urpm->read_provides(); + + #- compute depslist after reading each hdlist of medium + #- in the right order. + foreach my $medium (@{$urpm->{media}}) { + $medium->{ignore} and next; + $urpm->{params}->read_hdlists("$urpm->{statedir}/$medium->{hdlist}"); + $urpm->{params}->compute_depslist(); + } + + #- there has been a problem with provides not resolved on files, there + #- must be at least 2 linked pass on the whole process. + if ($urpm->{params}->get_unresolved_provides_files() > 0) { + #- cleaning. + $urpm->{params}->clean(); + + foreach my $medium (@{$urpm->{media}}) { + $medium->{ignore} and next; + $urpm->{params}->read_hdlists("$urpm->{statedir}/$medium->{hdlist}"); + } + $urpm->{params}->keep_only_cleaned_provides_files(); + foreach my $medium (@{$urpm->{media}}) { + $medium->{ignore} and next; + $urpm->{params}->read_hdlists("$urpm->{statedir}/$medium->{hdlist}"); + $urpm->{params}->compute_depslist(); + } + } + + #- once everything has been computed, write back the files to + #- sync the urpmi database. + $urpm->write_base_files(); + $urpm->{modified} = 0; + } + #- this file is written in any cases. + $urpm->write_config(); + + #- now everything is finished. + system("sync"); + } +} + +#- check for necessity of mounting some directory to get access +sub try_mounting { + my ($urpm, $dir) = @_; + + if (!-e $dir) { + my ($fdir, $pdir, $v, %fstab) = $dir; + + #- read /etc/fstab and check for existing mount point. + local (*F, $_); + open F, "/etc/fstab"; + while (<F>) { + /^\s*\S+\s+(\/\S+)/ and $fstab{$1} = 0; + } + open F, "/etc/mtab"; + while (<F>) { + /^\s*\S+\s+(\/\S+)/ and $fstab{$1} = 1; + } + close F; + + #- try to follow symlink, too complex symlink graph may not + #- be seen. + while ($v = readlink $fdir) { + if ($fdir =~ /^\//) { + $fdir = $v; + } else { + while ($v =~ /^\.\.\/(.*)/) { + $v = $1; + $fdir =~ s/^(.*)\/[^\/]+\/*/$1/; + } + $fdir .= "/$v"; + } + } + + #- check the presence of parent directory to mount directory. + foreach (split '/', $fdir) { + length($_) or next; + $pdir .= "/$_"; + foreach ($pdir, "$pdir/") { + exists $fstab{$_} && !$fstab{$_} and $fstab{$pdir} = 1, `mount $pdir 2>/dev/null`; + } + } + } +} + +#- read depslist file using rpmtools, this file is not managed directly by urpm. +sub read_depslist { + my ($urpm) = @_; + + local *F; + open F, $urpm->{depslist} or $urpm->{error}("unable to read depslist file [$urpm->{depslist}]"), return; + $urpm->{params}->read_depslist(\*F); + close F; + $urpm->{log}("read depslist file [$urpm->{depslist}]"); + 1; +} + +#- read providest file using rpmtools, this file is not managed directly by urpm. +sub read_provides { + my ($urpm) = @_; + + local *F; + open F, $urpm->{provides} or $urpm->{error}("unable to read provides file [$urpm->{provides}]"), return; + $urpm->{params}->read_provides(\*F); + close F; + $urpm->{log}("read provides file [$urpm->{provides}]"); + 1; +} + +#- read providest file using rpmtools, this file is not managed directly by urpm. +sub read_compss { + my ($urpm) = @_; + + local *F; + open F, $urpm->{compss} or $urpm->{error}("unable to read compss file [$urpm->{compss}]"), return; + $urpm->{params}->read_compss(\*F); + close F; + $urpm->{log}("read compss file [$urpm->{compss}]"); + 1; +} + +#- write base files using rpmtools, these files are not managed directly by urpm. +sub write_base_files { + my ($urpm) = @_; + local *F; + + open F, ">$urpm->{depslist}" or $urpm->{error}("unable to write depslist file [$urpm->{depslist}]"); + $urpm->{params}->write_depslist(\*F); + close F; + $urpm->{log}("write depslist file [$urpm->{depslist}]"); + + open F, ">$urpm->{provides}" or $urpm->{error}("unable to write provides file [$urpm->{provides}]"); + $urpm->{params}->write_provides(\*F); + close F; + $urpm->{log}("write provides file [$urpm->{provides}]"); + + open F, ">$urpm->{compss}" or $urpm->{error}("unable to write compss file [$urpm->{compss}]"); + $urpm->{params}->write_compss(\*F); + close F; + $urpm->{log}("write compss file [$urpm->{compss}]"); +} + +#- search packages registered by their name by storing their id into packages hash. +sub search_packages { + my ($urpm, $packages, $names, %options) = @_; + my (%exact, %found, %foundi); + + foreach my $v (@$names) { + #- it is a way of speedup, providing the name of a package directly help + #- to find the package. + #- this is necessary if providing a name list of package to upgrade. + if ($urpm->{params}{info}{$v}) { + $exact{$v} = $urpm->{params}{info}{$v}; next; + } + + my $qv = quotemeta $v; + foreach (keys %{$urpm->{params}{info}}) { + my $info = $urpm->{params}{info}{$_}; + my $pack = $info->{name} .'-'. $info->{version} .'-'. $info->{release}; + + $pack =~ /^$qv-[^-]+-[^-]+$/ and $exact{$v} = $info; + $pack =~ /^$qv-[^-]+$/ and $exact{$v} = $info; + $pack =~ /$qv/ and push @{$found{$v}}, $info; + $pack =~ /$qv/i and push @{$foundi{$v}}, $info; + } + } + + my $result = 1; + foreach (@$names) { + my $info = $exact{$_}; + if ($info) { + $packages->{$info->{id}} = undef; + } else { + my $l = $found{$_} || $foundi{$_}; + if (@{$l || []} == 0) { + $urpm->{error}(_("no package named %s\n", $_)); $result = 0; + } elsif (@$l > 1 && !$options{all}) { + $urpm->{error}(_("The following packages contain %s: %s\n", $_, join(' ', map { $_->{name} } @$l))); $result = 0; + } else { + foreach (@$l) { + $packages->{$_->{id}} = undef; + } + } + } + } + + #- return true if no error have been encoutered, else false. + $result; +} + +#- compute the closure of a list, mostly internal function for filter_packages_to_upgrade. +#- limited to values in packages which should not be a reference. +#- package are identified by their id. +sub compute_closure { + my ($urpm, $packages, $installed, $select_choices) = @_; + + #- select first level of packages, as in packages list will only be + #- examined deps of each. + @{$packages}{keys %$packages} = (); + + #- at this level, compute global closure of what is requested, regardless of + #- choices for which all package in the choices are taken and their dependancies. + #- allow iteration over a modifying list. + my $id; + my @packages = keys %$packages; + while (defined($id = shift @packages)) { + #- get a relocated id if possible, by this way. + $id = $urpm->{params}{depslist}[$id]{id}; + + #- avoid a package if it has already been dropped in the sense of + #- selected directly by another way. + foreach ($id, split ' ', $urpm->{params}{depslist}[$id]{deps}) { + if (/\|/) { + my ($follow_id, @upgradable_choices); + my @choices = map { $urpm->{params}{depslist}[$_]{id} } split /\|/, $_; + foreach (@choices) { + $installed && $installed->{$_} and $follow_id = -1, last; + exists $packages->{$_} && ! ref $packages->{$_} and $follow_id = $_, last; + $installed && exists $installed->{$_} and push @upgradable_choices, $_; + } + unless ($follow_id) { + #- if there are at least one upgradable choice, use it instead + #- of asking the user to chose among a list. + if (@upgradable_choices == 1) { + push @packages, $upgradable_choices[0]; + } else { + @upgradable_choices > 1 and @choices = @upgradable_choices; + $select_choices and push @packages, $select_choices->($urpm, @choices); + foreach (@choices) { + push @{$packages->{$_} ||= []}, \@choices; + } + } + } + } else { + if (ref $packages->{$_}) { + #- all the choices associated here have to be dropped, need to copy else + #- there could be problem with foreach on a modifying list. + foreach my $choices (@{$packages->{$id}}) { + foreach (@$choices) { + $packages->{$_} = [ grep { $_ != $choices } @{$packages->{$_}} ]; + @{$packages->{$_}} > 0 or delete $packages->{$_}; + } + } + } + if ($installed && $installed->{$_}) { + delete $packages->{$_}; + } else { + $packages->{$_} = $installed && ! exists $installed->{$_}; + } + } + } + } +} + +#- filter the packages list (assuming only the key is registered, so undefined +#- value stored) to keep only packages that need to be upgraded, +#- additionnal packages will be stored using non null values, +#- choice will have a list of other choices as values, +#- initial packages will have a 0 stored as values. +#- options allow changing some behaviour of the algorithms: +#- complete -> perform a complete closure before trying to look for upgrade. +sub filter_packages_to_upgrade { + my ($urpm, $packages, $select_choices, %options) = @_; + my ($id, %closures, %installed, @packages_installed); + + #- request the primary list to rpmlib if complete mode is not activated. + if (!$options{complete} && + rpmtools::get_packages_installed('', \@packages_installed, + [ map { $urpm->{params}{depslist}[$_]{name} } keys %$packages ])) { + #- there are not too many packages selected here to allow + #- take care of package up-to-date at this point, + #- so check version and if the package does not need to + #- updated, ignore it and his dependancies. + foreach (@packages_installed) { + my $pkg = $urpm->{params}{info}{$_->{name}}; $pkg or next; #- TODO error + my $cmp = rpmtools::version_compare($pkg->{version}, $_->{version}); + $installed{$pkg->{id}} = !($cmp > 0 || $cmp == 0 && rpmtools::version_compare($pkg->{release}, $_->{release}) > 0) + and delete $packages->{$pkg->{id}}; + } + } + + #- select first level of packages, as in packages list will only be + #- examined deps of each. + #- at this level, compute global closure of what is requested, regardless of + #- choices for which all package in the choices are taken and their dependancies. + #- allow iteration over a modifying list. + @closures{keys %$packages} = (); + $urpm->compute_closure(\%closures, undef, sub { my ($urpm, @l) = @_; @l }); + + #- closures has been done so that its keys are the package that may be examined. + #- according to number of keys, get all package installed or only the necessary + #- packages. + #- do not take care of already examined packages. + delete @closures{keys %installed}; + if (scalar(keys %closures) < 100) { + rpmtools::get_packages_installed('', \@packages_installed, + [ map { $urpm->{params}{depslist}[$_]{name} } keys %closures ]); + } else { + rpmtools::get_all_packages_installed('', \@packages_installed); + } + + #- packages installed that may be upgraded have to be examined now. + foreach (@packages_installed) { + my $pkg = $urpm->{params}{info}{$_->{name}}; $pkg or next; #- TODO error + exists $closures{$pkg->{id}} or next; + my $cmp = rpmtools::version_compare($pkg->{version}, $_->{version}); + $installed{$pkg->{id}} = !($cmp > 0 || $cmp == 0 && rpmtools::version_compare($pkg->{release}, $_->{release}) > 0) + and delete $packages->{$pkg->{id}}; + } + + #- recompute closure but ask for which package to select on a choices. + #- this is necessary to have the result before the end else some dependancy may + #- be losed or added. + #- accept no choice allow to browse list, and to compute it with more iteration. + %closures = (); @closures{keys %$packages} = (); + $urpm->compute_closure(\%closures, \%installed, $select_choices); + + #- restore package to match selection done, update the values according to + #- need upgrade (0), requested (undef), already installed (not present) or + #- newly added (1). + #- choices if not chosen are present as ref. + my @packages = keys %$packages; + %$packages = %closures; + @{$packages}{@packages} = (); + + $packages; +} + + +1; |