File Coverage

blib/lib/urpm/install.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package urpm::install;
2              
3              
4 1     1   1483 use strict;
  1         2  
  1         23  
5 1     1   28 use urpm;
  0            
  0            
6             use urpm::msg;
7             use urpm::util qw(cat_utf8 member);
8              
9              
10             =head1 NAME
11              
12             urpm::install - Package installation transaction routines for urpmi
13              
14             =head1 SYNOPSIS
15              
16             =head1 DESCRIPTION
17              
18             =over
19              
20             =cut
21              
22             # size of the installation progress bar
23             my $progress_size = 45;
24             if (-t *STDOUT) {
25             eval {
26             require Term::ReadKey;
27             ($progress_size) = Term::ReadKey::GetTerminalSize();
28             $progress_size -= 35;
29             $progress_size < 5 and $progress_size = 5;
30             };
31             }
32              
33              
34             sub _hash_intersect_list {
35             my ($h, $l) = @_;
36             my %h;
37             foreach (@$l) {
38             exists $h->{$_} and $h{$_} = $h->{$_};
39             }
40             \%h;
41             }
42              
43             =item prepare_transaction($set, $blists, $sources)
44              
45             =cut
46              
47              
48             sub prepare_transaction {
49             my ($set, $blists, $sources) = @_;
50              
51             my @blists_subset = map {
52             +{ %$_, pkgs => _hash_intersect_list($_->{pkgs}, $set->{upgrade}) };
53             } @$blists;
54              
55             \@blists_subset, _hash_intersect_list($sources, $set->{upgrade});
56             }
57              
58             sub build_transaction_set_ {
59             my ($urpm, $state, %options) = @_;
60              
61             if ($urpm->{parallel_handler} || !$options{split_length} ||
62             keys %{$state->{selected}} < $options{split_level}) {
63             #- build simplest transaction (no split).
64             $urpm->build_transaction_set(undef, $state, split_length => 0);
65             } else {
66             my $db = urpm::db_open_or_die_($urpm);
67              
68             my $sig_handler = sub { undef $db; exit 3 };
69             local $SIG{INT} = $sig_handler;
70             local $SIG{QUIT} = $sig_handler;
71              
72             #- build transaction set...
73             $urpm->build_transaction_set($db, $state, split_length => $options{split_length}, keep => $options{keep});
74             }
75             }
76              
77             sub transaction_set_to_string {
78             my ($urpm, $set) = @_;
79              
80             my $format_list = sub { int(@_) . '=' . join(',', @_) };
81             map {
82             sprintf('remove=%s update=%s',
83             $format_list->(@{$_->{remove} || []}),
84             $format_list->(map { $urpm->{depslist}[$_]->name } @{$_->{upgrade} || []}));
85             } @$set;
86             }
87              
88             =item install_logger($urpm, $type, $id, $subtype, $amount, $total)
89              
90             Standard logger for transactions
91              
92             See L for parameters
93              
94             =cut
95              
96             # install logger callback
97             my ($erase_logger, $index, $total_pkg, $uninst_count, $current_pkg);
98             sub install_logger {
99             my ($urpm, $type, undef, $subtype, $amount, $total) = @_;
100             local $| = 1;
101              
102             if ($subtype eq 'start') {
103             $urpm->{logger_progress} = 0;
104             if ($type eq 'trans') {
105             $total_pkg = $urpm->{nb_install};
106             $urpm->{logger_count} ||= 0;
107             $uninst_count = 0;
108             my $p = N("Preparing...");
109             print $p, " " x (33 - length $p);
110             } else {
111             my $pname;
112             my $cnt;
113             if ($type eq 'uninst') {
114             $total_pkg = $urpm->{trans}->NElements - $index if !$uninst_count;
115             $cnt = ++$uninst_count;
116             $pname = N("removing %s", $current_pkg);
117             $erase_logger->($urpm, undef, undef, $subtype);
118             } else {
119             $pname = $urpm->{trans}->Element_name($index);
120             ++$urpm->{logger_count} if $pname;
121             $cnt = $pname ? $urpm->{logger_count} : '-';
122             }
123             my $s = sprintf("%9s: %-22s", $cnt . "/" . $total_pkg, $pname);
124             print $s;
125             $s =~ / $/ or printf "\n%9s %-22s", '', '';
126             }
127             } elsif ($subtype eq 'stop') {
128             if ($urpm->{logger_progress} < $progress_size) {
129             $urpm->{print}('#' x ($progress_size - $urpm->{logger_progress}));
130             $urpm->{logger_progress} = 0;
131             }
132             } elsif ($subtype eq 'progress') {
133             my $new_progress = $total > 0 ? int($progress_size * $amount / $total) : $progress_size;
134             if ($new_progress > $urpm->{logger_progress}) {
135             print '#' x ($new_progress - $urpm->{logger_progress});
136             $urpm->{logger_progress} = $new_progress;
137             $urpm->{logger_progress} == $progress_size and print "\n";
138             }
139             }
140             }
141              
142             =item get_README_files($urpm, $trans, $pkg)
143              
144             =cut
145              
146             sub get_README_files {
147             my ($urpm, $trans, $pkg) = @_;
148              
149             foreach my $file ($pkg->doc_files) {
150             my ($kind) = $file =~ m!/README([^/]*)\.urpmi$! or next;
151             my $valid;
152             if ($kind eq '') {
153             $valid = 1;
154             } elsif ($kind eq '.install' && !$pkg->flag_installed) {
155             $valid = 1;
156             } elsif ($kind =~ /(.*)\.(upgrade|update)$/ && $pkg->flag_installed) {
157             if (!$1) {
158             $valid = 1;
159             } else {
160             my $version = $1;
161             foreach my $i (0 .. $trans->NElements - 1) {
162             $trans->Element_name($i) eq $pkg->name or next;
163              
164             # handle README.-.upgrade.urpmi:
165             # the content is displayed when upgrading from rpm older than
166             my $vr = $trans->Element_version($i) . '-' . $trans->Element_release($i);
167             if (URPM::ranges_overlap("== $vr", "< $version")) {
168             $valid = 1;
169             last;
170             }
171             }
172             }
173             }
174             $valid and $urpm->{readmes}{$file} = $pkg->fullname;
175             }
176             }
177              
178             sub options {
179             my ($urpm) = @_;
180              
181             (
182             excludepath => $urpm->{options}{excludepath},
183             excludedocs => $urpm->{options}{excludedocs},
184             post_clean_cache => $urpm->{options}{'post-clean'},
185             nosize => $urpm->{options}{ignoresize},
186             ignorearch => $urpm->{options}{ignorearch},
187             noscripts => $urpm->{options}{noscripts},
188             replacefiles => $urpm->{options}{replacefiles},
189             );
190             }
191              
192             sub _schedule_packages_for_erasing {
193             my ($urpm, $trans, $remove) = @_;
194             foreach (@$remove) {
195             if ($trans->remove($_)) {
196             $urpm->{debug} and $urpm->{debug}("trans: scheduling removal of $_");
197             } else {
198             $urpm->{error}("unable to remove package " . $_);
199             }
200             }
201             }
202              
203             sub _apply_delta_rpm {
204             my ($urpm, $path, $mode, $pkg) = @_;
205             my $true_rpm = urpm::sys::apply_delta_rpm($path, "$urpm->{cachedir}/rpms", $pkg);
206             my $true_pkg;
207             if ($true_rpm) {
208             if (my ($id) = $urpm->parse_rpm($true_rpm)) {
209             $true_pkg = defined $id && $urpm->{depslist}[$id];
210             $mode->{$id} = $true_rpm;
211             } else {
212             $urpm->{error}("Failed to parse $true_pkg");
213             }
214             } else {
215             $urpm->{error}(N("unable to extract rpm from delta-rpm package %s", $path));
216             }
217             $true_rpm, $true_pkg;
218             }
219              
220             sub _schedule_packages {
221             my ($urpm, $trans, $install, $upgrade, %options) = @_;
222             my $update = 0;
223             my (@trans_pkgs, @produced_deltas);
224             foreach my $mode ($install, $upgrade) {
225             foreach (keys %$mode) {
226             my $pkg = $urpm->{depslist}[$_];
227             $pkg->update_header($mode->{$_}, keep_all_tags => 1);
228             my ($true_rpm, $true_pkg);
229             if ($pkg->payload_format eq 'drpm') { #- handle deltarpms
230             ($true_rpm, $true_pkg) = _apply_delta_rpm($urpm, $mode->{$_}, $mode, $pkg);
231             push @produced_deltas, ($mode->{$_} = $true_rpm); #- fix path
232             }
233             if ($trans->add($true_pkg || $pkg, update => $update,
234             $options{excludepath} ? (excludepath => [ split /,/, $options{excludepath} ]) : ())) {
235             $urpm->{debug} and $urpm->{debug}(
236             sprintf('trans: scheduling %s of %s (id=%d, file=%s)',
237             $update ? 'update' : 'install',
238             scalar($pkg->fullname), $_, $mode->{$_}));
239             push @trans_pkgs, $pkg;
240              
241             } else {
242             $urpm->{error}(N("unable to install package %s", $mode->{$_}));
243             my $cachefile = "$urpm->{cachedir}/rpms/" . $pkg->filename;
244             if (-e $cachefile) {
245             $urpm->{error}(N("removing bad rpm (%s) from %s", $pkg->name, "$urpm->{cachedir}/rpms"));
246             unlink $cachefile or $urpm->{fatal}(1, N("removing %s failed: %s", $cachefile, $!));
247             }
248             }
249             }
250             ++$update;
251             }
252             \@produced_deltas, @trans_pkgs;
253             }
254              
255             sub _get_callbacks {
256             my ($urpm, $db, $trans, $options, $install, $upgrade, $have_pkgs) = @_;
257             $index = 0;
258             my $fh;
259              
260             my $is_test = $options->{test}; # fix circular reference
261             #- assume default value for some parameter.
262             $options->{delta} ||= 1000;
263              
264             #- ensure perl does not create a circular reference below, otherwise all this won't be collected,
265             # and rpmdb won't be closed:
266             my ($callback_open_helper, $callback_close_helper) = ($options->{callback_open_helper}, $options->{callback_close_helper});
267             $options->{callback_open} = sub {
268             my ($_data, $_type, $id) = @_;
269             $callback_open_helper and $callback_open_helper->(@_);
270             $fh = urpm::sys::open_safe($urpm, '<', $install->{$id} || $upgrade->{$id});
271             $fh ? fileno $fh : undef;
272             };
273             $options->{callback_close} = sub {
274             my ($urpm, undef, $pkgid) = @_;
275             return unless defined $pkgid;
276             $callback_close_helper and $callback_close_helper->($db, @_);
277             get_README_files($urpm, $trans, $urpm->{depslist}[$pkgid]) if !$is_test;
278             close $fh if defined $fh;
279             };
280              
281             #- ensure perl does not create a circular reference below, otherwise all this won't be collected,
282             # and rpmdb won't be closed
283             my $verbose = $options->{verbose};
284             $erase_logger = sub {
285             my ($urpm, undef, undef, $subtype) = @_;
286              
287             if ($subtype eq 'start') {
288             my $name = $trans->Element_name($index);
289             my @previous = map { $trans->Element_name($_) } 0 .. ($index - 1);
290             # looking at previous packages in transaction
291             # we should be looking only at installed packages, but it should not give a different result
292             if (member($name, @previous)) {
293             $urpm->{log}("removing upgraded package $current_pkg");
294             } else {
295             $urpm->{print}(N("removing package %s", $current_pkg)) if $verbose >= 0;
296             }
297             }
298             };
299              
300             $options->{callback_uninst} ||= $options->{verbose} >= 0 ? \&install_logger : $erase_logger;
301              
302             $options->{callback_elem} ||= sub {
303             my (undef, undef, undef, undef, $idx, undef) = @_;
304             $index = $idx;
305             $current_pkg = $trans->Element_fullname($idx);
306             };
307             $options->{callback_error} ||= sub {
308             my ($urpm, undef, undef, $subtype, undef, undef) = @_;
309             $urpm->{error}("ERROR: '$subtype' failed for $current_pkg");
310             };
311              
312             if ($options->{verbose} >= 0 && $have_pkgs) {
313             $options->{callback_inst} ||= \&install_logger;
314             $options->{callback_trans} ||= \&install_logger;
315             }
316             }
317              
318             =item install($urpm, $remove, $install, $upgrade, %options)
319              
320             Install packages according to each hash (remove, install or upgrade).
321              
322             options:
323             test, excludepath, nodeps, noorder (unused), delta,
324             callback_inst, callback_trans, callback_uninst,
325             callback_open_helper, callback_close_helper,
326             post_clean_cache, verbose
327             (more options for trans->run)
328             excludedocs, nosize, noscripts, oldpackage, replacepkgs, justdb, ignorearch
329              
330             See L for callback parameters
331              
332             =cut
333              
334             #- side-effects: uses a $urpm->{readmes}
335             sub install {
336             my ($urpm, $remove, $install, $upgrade, %options) = @_;
337             $options{translate_message} = 1;
338              
339             my $db = urpm::db_open_or_die_($urpm, !$options{test}); #- open in read/write mode unless testing installation.
340              
341             my $trans = $db->create_transaction;
342             if ($trans) {
343             my ($rm_count, $inst_count, $up_count) = (scalar(@{$remove || []}), scalar(values %$install), scalar(values %$upgrade));
344             sys_log("transaction on %s (remove=%d, install=%d, upgrade=%d)", $urpm->{root} || '/', $rm_count, $inst_count, $up_count);
345             $urpm->{log}(N("created transaction for installing on %s (remove=%d, install=%d, upgrade=%d)", $urpm->{root} || '/',
346             $rm_count, $inst_count, $up_count));
347             } else {
348             return N("unable to create transaction");
349             }
350              
351             $trans->set_script_fd($options{script_fd}) if $options{script_fd};
352              
353             my @errors;
354              
355             _schedule_packages_for_erasing($urpm, $trans, $remove);
356              
357             my ($produced_deltas, @trans_pkgs) = _schedule_packages($urpm, $trans, $install, $upgrade, %options);
358              
359             if (!$options{nodeps} && (@errors = $trans->check(%options))) {
360             } elsif (!$options{noorder} && (@errors = $trans->order(%options))) {
361             } else {
362             $urpm->{readmes} = {};
363              
364             _get_callbacks($urpm, $db, $trans, \%options, $install, $upgrade, scalar @trans_pkgs);
365              
366             local $ENV{LD_PRELOAD}; # fix eatmydata & co
367             local $urpm->{trans} = $trans;
368             @errors = $trans->run($urpm, %options);
369             delete $urpm->{trans};
370             undef $erase_logger;
371              
372             #- don't clear cache if transaction failed. We might want to retry.
373             if (!@errors && !$options{test} && $options{post_clean_cache}) {
374             #- examine the local cache to delete packages which were part of this transaction
375             my $cachedir = "$urpm->{cachedir}/rpms";
376             my @pkgs = grep { -e "$cachedir/$_" } map { $_->filename } @trans_pkgs;
377             $urpm->{log}(N("removing installed rpms (%s) from %s", join(' ', @pkgs), $cachedir)) if @pkgs;
378             foreach (@pkgs) {
379             unlink "$cachedir/$_" or $urpm->{fatal}(1, N("removing %s failed: %s", $_, $!));
380             }
381             }
382              
383             if ($options{verbose} >= 0 && !$options{justdb}) {
384             foreach (keys %{$urpm->{readmes}}) {
385             $urpm->{print}("-" x 70 . "\n" .
386             N("More information on package %s", $urpm->{readmes}{$_}));
387             $urpm->{print}(scalar cat_utf8(($urpm->{root} || '') . $_));
388             $urpm->{print}("-" x 70);
389             }
390             }
391             }
392              
393             unlink @$produced_deltas;
394              
395             urpm::sys::may_clean_rpmdb_shared_regions($urpm, $options{test});
396              
397             # explicitely close the RPM DB (needed for drakx -- looks like refcount has hard work):
398             undef $db;
399             undef $trans;
400              
401             @errors;
402             }
403              
404             1;
405              
406             =back
407              
408             =head1 COPYRIGHT
409              
410             Copyright (C) 1999-2005 MandrakeSoft SA
411              
412             Copyright (C) 2005-2010 Mandriva SA
413              
414             Copyright (C) 2011-2017 Mageia
415              
416             =cut