File Coverage

blib/lib/urpm/main_loop.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::main_loop;
2              
3              
4             #- Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 MandrakeSoft SA
5             #- Copyright (C) 2005-2010 Mandriva SA
6             #-
7             #- This program is free software; you can redistribute it and/or modify
8             #- it under the terms of the GNU General Public License as published by
9             #- the Free Software Foundation; either version 2, or (at your option)
10             #- any later version.
11             #-
12             #- This program is distributed in the hope that it will be useful,
13             #- but WITHOUT ANY WARRANTY; without even the implied warranty of
14             #- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15             #- GNU General Public License for more details.
16             #-
17             #- You should have received a copy of the GNU General Public License
18             #- along with this program; if not, write to the Free Software
19             #- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20              
21 1     1   1004 use strict;
  1         2  
  1         29  
22 1     1   31 use urpm;
  0            
  0            
23             use urpm::args;
24             use urpm::msg;
25             use urpm::install;
26             use urpm::media;
27             use urpm::select;
28             use urpm::orphans;
29             use urpm::get_pkgs;
30             use urpm::signature;
31             use urpm::util qw(find intersection member partition);
32              
33             #- global boolean options
34             my ($auto_select, $no_install, $install_src, $clean, $noclean, $force, $parallel, $test);
35             #- global counters
36             my ($ok, $nok);
37             my $exit_code;
38              
39              
40             =head1 NAME
41              
42             urpm::main_loop - The install/remove main loop for urpm based programs (urpmi, gurpmi, rpmdrake, drakx)
43              
44             =head1 SYNOPSIS
45              
46             =head1 DESCRIPTION
47              
48             =over
49              
50             =cut
51              
52             sub _download_callback {
53             my ($urpm, $callbacks, $raw_msg, $msg) = @_;
54             if (my $download_errors = delete $urpm->{download_errors}) {
55             $raw_msg = join("\n", @$download_errors, '');
56             }
57             $callbacks->{ask_yes_or_no}('', $raw_msg . "\n" . $msg . "\n" . N("Retry?"));
58             }
59              
60             sub _download_packages {
61             my ($urpm, $callbacks, $blists, $sources) = @_;
62             my @error_sources;
63             urpm::get_pkgs::download_packages_of_distant_media(
64             $urpm,
65             $blists,
66             $sources,
67             \@error_sources,
68             quiet => $options{verbose} < 0,
69             callback => $callbacks->{trans_log},
70             ask_retry => !$urpm->{options}{auto} && ($callbacks->{ask_retry} || sub {
71             _download_callback($urpm, $callbacks, @_);
72             }),
73             );
74             my @msgs;
75             if (@error_sources) {
76             $_->[0] = urpm::download::hide_password($_->[0]) foreach @error_sources;
77             my @bad = grep { $_->[1] eq 'bad' } @error_sources;
78             my @missing = grep { $_->[1] eq 'missing' } @error_sources;
79              
80             if (@missing) {
81             push @msgs, N("Installation failed, some files are missing:\n%s",
82             join("\n", map { " $_->[0]" } @missing))
83             . "\n" .
84             N("You may need to update your urpmi database.");
85             $urpm->{nb_install} -= scalar @missing;
86             }
87             if (@bad) {
88             push @msgs, N("Installation failed, bad rpms:\n%s",
89             join("\n", map { " $_->[0]" } @bad));
90             }
91             }
92            
93             (\@error_sources, \@msgs);
94             }
95              
96             sub _download_all {
97             my ($urpm, $blists, $sources, $callbacks) = @_;
98             if ($urpm->{options}{'download-all'}) {
99             $urpm->{cachedir} = $urpm->{'urpmi-root'} . $urpm->{options}{'download-all'};
100             urpm::init_dir($urpm, $urpm->{cachedir});
101             }
102             my (undef, $available) = urpm::sys::df("$urpm->{cachedir}/rpms");
103              
104             if (!$urpm->{options}{ignoresize}) {
105             my ($download_size) = urpm::get_pkgs::get_distant_media_filesize($blists, $sources);
106             if ($download_size >= $available*1000) {
107             my $p = N("There is not enough space on your filesystem to download all packages (%s needed, %s available).\nAre you sure you want to continue?", formatXiB($download_size), formatXiB($available*1000));
108             $force || urpm::msg::ask_yes_or_no($p) or return 10;
109             }
110             }
111              
112             #download packages one by one so that we don't try to download them again
113             #and again if the user has to restart urpmi because of some failure
114             my %downloaded_pkgs;
115             foreach my $blist (@$blists) {
116             foreach my $pkg (keys %{$blist->{pkgs}}) {
117             next if $downloaded_pkgs{$pkg};
118             my $blist_one = [{ pkgs => { $pkg => $blist->{pkgs}{$pkg} }, medium => $blist->{medium} }];
119             my ($error_sources) = _download_packages($urpm, $callbacks, $blist_one, $sources);
120             if (@$error_sources) {
121             return 10;
122             }
123             $downloaded_pkgs{$pkg} = 1;
124             }
125             }
126             }
127              
128             sub _verify_rpm {
129             my ($urpm, $callbacks, $transaction_sources_install, $transaction_sources) = @_;
130             $callbacks->{pre_check_sig} and $callbacks->{pre_check_sig}->();
131             my @bad_signatures = urpm::signature::check($urpm, $transaction_sources_install, $transaction_sources,
132             callback => $callbacks->{check_sig}, basename => $options{basename}
133             );
134              
135             if (@bad_signatures) {
136             my $msg = @bad_signatures == 1 ?
137             N("The following package has bad signature")
138             : N("The following packages have bad signatures");
139             my $msg2 = N("Do you want to continue installation ?");
140             my $p = join "\n", @bad_signatures;
141             my $res = $callbacks->{bad_signature}->("$msg:\n$p\n", $msg2);
142             return $res ? 0 : 16;
143             }
144             }
145              
146             sub _install_src {
147             my ($urpm, $transaction_sources_install, $transaction_sources) = @_;
148             if (my @l = grep { /\.src\.rpm$/ } values %$transaction_sources_install, values %$transaction_sources) {
149             my $rpm_opt = $options{verbose} >= 0 ? 'vh' : '';
150             push @l, "--root", $urpm->{root} if $urpm->{root};
151             system("rpm", "-i$rpm_opt", @l);
152             #- Warning : the following message is parsed in urpm::parallel_*
153             if ($?) {
154             $urpm->{print}(N("Installation failed"));
155             ++$nok;
156             } elsif ($urpm->{options}{'post-clean'}) {
157             if (my @tmp_srpm = grep { urpm::is_temporary_file($urpm, $_) } @l) {
158             $urpm->{log}(N("removing installed rpms (%s)", join(' ', @tmp_srpm)));
159             unlink @tmp_srpm;
160             }
161             }
162             }
163             }
164              
165             sub clean_trans_sources_from_src_packages {
166             my ($urpm, $transaction_sources_install, $transaction_sources) = @_;
167             foreach ($transaction_sources_install, $transaction_sources) {
168             foreach my $id (keys %$_) {
169             my $pkg = $urpm->{depslist}[$id] or next;
170             $pkg->arch eq 'src' and delete $_->{$id};
171             }
172             }
173             }
174              
175             sub _continue_on_error {
176             my ($urpm, $callbacks, $msgs, $error_sources, $formatted_errors) = @_;
177             my $go_on;
178             if ($urpm->{options}{auto}) {
179             push @$formatted_errors, @$msgs;
180             } else {
181             my $sub = $callbacks->{ask_for_bad_or_missing} || $callbacks->{ask_yes_or_no};
182             $go_on = $sub->(
183             N("Installation failed"),
184             join("\n\n", @$msgs, N("Try to continue anyway?")));
185             }
186             if (!$go_on) {
187             my @missing = grep { $_->[1] eq 'missing' } @$error_sources;
188             if (@missing) {
189             $exit_code = $ok ? 13 : 14;
190             }
191             return 0;
192             }
193             return 1;
194             }
195              
196             sub _handle_removable_media {
197             my ($urpm, $callbacks, $blists, $sources) = @_;
198             urpm::removable::try_mounting_non_cdroms($urpm, $blists);
199              
200             $callbacks->{pre_removable} and $callbacks->{pre_removable}->();
201             require urpm::cdrom;
202             urpm::cdrom::copy_packages_of_removable_media($urpm,
203             $blists, $sources,
204             $callbacks->{copy_removable});
205             $callbacks->{post_removable} and $callbacks->{post_removable}->();
206             }
207              
208             sub _init_common_options {
209             my ($urpm, $state, $callbacks) = @_;
210             (
211             urpm::install::options($urpm),
212             test => $test,
213             deploops => $options{deploops},
214             verbose => $options{verbose},
215             script_fd => $urpm->{options}{script_fd},
216             oldpackage => $state->{oldpackage},
217             justdb => $options{justdb},
218             replacepkgs => $options{replacepkgs},
219             callback_close_helper => $callbacks->{close_helper},
220             callback_error => $callbacks->{error},
221             callback_inst => $callbacks->{inst},
222             callback_open_helper => $callbacks->{open_helper},
223             callback_trans => $callbacks->{trans},
224             callback_uninst => $callbacks->{uninst},
225             raw_message => 1,
226             );
227             }
228              
229             sub _log_installing {
230             my ($urpm, $transaction_sources_install, $transaction_sources) = @_;
231             if (my @packnames = (values %$transaction_sources_install, values %$transaction_sources)) {
232             (my $common_prefix) = $packnames[0] =~ m!^(.*)/!;
233             if (length($common_prefix) && @packnames == grep { m!^\Q$common_prefix/! } @packnames) {
234             #- there's a common prefix, simplify message
235             $urpm->{print}(N("installing %s from %s", join(' ', map { s!.*/!!; $_ } @packnames), $common_prefix));
236             } else {
237             $urpm->{print}(N("installing %s", join "\n", @packnames));
238             }
239             }
240             }
241              
242             sub _run_parallel_transaction {
243             my ($urpm, $state, $transaction_sources, $transaction_sources_install) = @_;
244             $urpm->{print}(N("distributing %s", join(' ', values %$transaction_sources_install, values %$transaction_sources)));
245             #- no remove are handle here, automatically done by each distant node.
246             $urpm->{log}("starting distributed install");
247             $urpm->{parallel_handler}->parallel_install(
248             $urpm,
249             [ keys %{$state->{rejected} || {}} ], $transaction_sources_install, $transaction_sources,
250             test => $test,
251             excludepath => $urpm->{options}{excludepath}, excludedocs => $urpm->{options}{excludedocs},
252             );
253             }
254              
255             sub _run_transaction {
256             my ($urpm, $state, $callbacks, $set, $transaction_sources_install, $transaction_sources, $errors) = @_;
257              
258             my $options = $urpm->{options};
259             my $allow_force = $options->{'allow-force'};
260              
261             my $to_remove = $allow_force ? [] : $set->{remove} || [];
262              
263             $urpm->{log}("starting installing packages");
264            
265             urpm::orphans::add_unrequested($urpm, $state) if !$test;
266              
267             my %install_options_common = _init_common_options($urpm, $state, $callbacks);
268              
269             install:
270             my @l = urpm::install::install($urpm,
271             $to_remove,
272             $transaction_sources_install, $transaction_sources,
273             %install_options_common,
274             );
275              
276             if (!@l) {
277             ++$ok;
278             return 1;
279             }
280              
281             my ($raw_error, $translated) = partition { /^(badarch|bados|installed|badrelocate|conflicts|installed|diskspace|disknodes|requires|conflicts|unknown)\@/ } @l;
282             @l = @$translated;
283             my $fatal = find { /^disk/ } @$raw_error;
284             my $no_question = $fatal || $options->{auto};
285              
286             #- Warning : the following message is parsed in urpm::parallel_*
287             my $msg = N("Installation failed:") . "\n" . join("\n", map { "\t$_" } @l) . "\n";
288             if (!$no_question && !$install_options_common{nodeps} && ($options->{'allow-nodeps'} || $allow_force)) {
289             if ($callbacks->{ask_yes_or_no}->(N("Installation failed"),
290             $msg . N("Try installation without checking dependencies?"))) {
291             $urpm->{log}("starting installing packages without deps");
292             $install_options_common{nodeps} = 1;
293             # try again:
294             goto install;
295             }
296             } elsif (!$no_question && !$install_options_common{force} && $allow_force) {
297             if ($callbacks->{ask_yes_or_no}->(N("Installation failed"),
298             $msg . N("Try harder to install (--force)?"))) {
299             $urpm->{log}("starting force installing packages without deps");
300             $install_options_common{force} = 1;
301             # try again:
302             goto install;
303             }
304             }
305             $urpm->{log}($msg);
306              
307             ++$nok;
308             push @$errors, @l;
309              
310             !$fatal;
311             }
312              
313             =item run($urpm, $state, $something_was_to_be_done, $ask_unselect, $callbacks)
314              
315             Run the main urpm loop:
316              
317             =over
318              
319             =item * mount removable media if needed
320              
321             =item * split the work in smaller transactions
322              
323             =item * for each transaction:
324              
325             =over
326              
327             =item * prepare the transaction
328              
329             =item * download packages needed for this small transaction
330              
331             =item * verify packages
332              
333             =item * split package that should be installed instead of upgraded,
334              
335             =item * install source package only (whatever the user is root or not, but use rpm for that)
336              
337             =item * install/remove other packages
338              
339             =back
340              
341             =item * migrate the chrooted rpmdb if needed
342              
343             =item * display the final success/error message(s)
344              
345             =back
346              
347             Warning: locking is left to callers...
348              
349             Parameters:
350              
351             =over
352              
353             =item $urpm: the urpm object
354              
355             =item $state: the state object (see L)
356              
357             =item $something_was_to_be_done
358              
359             =item $ask_unselect: an array ref of packages that could not be selected
360              
361             =item $callbacks: a hash ref of callbacks :
362              
363             =over
364              
365             =item packages download:
366              
367             =over
368              
369             =item trans_log($mode, $file, $percent, $total, $eta, $speed): called for displaying download progress
370              
371             =item post_download(): called after completing download of packages
372              
373             =back
374              
375             =item interaction with user:
376              
377             =over
378              
379             =item ask_yes_or_no($_title, $msg)
380              
381             =item need_restart($need_restart_formatted) called when restarting urpmi is needed (priority upgrades)
382              
383             =item message($_title, $msg): display a message (with a title for GUIes)
384              
385             =back
386              
387             =item signature management:
388              
389             =over
390              
391             =item pre_check_sig(): signature checking startup (for rpmdrake)
392              
393             =item check_sig(): signature checking progress (for rpmdrake)
394              
395             =item bad_signature($msg, $msg2): called when a package is not/badly signed
396              
397             =back
398              
399             =item removable media management:
400              
401             =over
402              
403             =item pre_removable(): called before handling removable media (for rpmdrake)
404              
405             =item copy_removable($medium_name): called for asking inserting a CD/DVD
406              
407             =item post_extract($set, $transaction_sources, $transaction_sources_install) called after handling removable media (for rpmdrake)
408              
409             =back
410              
411             =item packages installation callbacks (passed to urpm::install::install(), see L for parameters)
412              
413             =over
414              
415             =item open_helper(): called when opening a package, must return a fd
416              
417             =item close_helper(): called when package is closed
418              
419             =item inst() called for package opening/progress/end
420              
421             =item trans() called for transaction opening/progress/end
422              
423             =item uninst(): called for erasure progress
424              
425             =item error() called for cpio, script or unpacking errors
426              
427             =back
428              
429             =item finish callbacks (mainly GUI callbacks for rpmdrake/gurpmi/drakx)
430              
431             =over
432              
433             =item completed(): called when everything is completed (for cleanups)
434              
435             =item trans_error_summary($nok, $errors) called to warn than $nok transactions failed with $errors messages
436              
437             =item success_summary() called on success
438              
439             =item already_installed_or_not_installable($msg1, $msg2)
440              
441             =back
442              
443             =back
444              
445             =back
446              
447             =cut
448              
449              
450             sub run {
451             my ($urpm, $state, $something_was_to_be_done, $ask_unselect, $callbacks) = @_;
452              
453             #- global boolean options
454             ($auto_select, $no_install, $install_src, $clean, $noclean, $force, $parallel, $test) =
455             ($::auto_select, $::no_install, $::install_src, $::clean, $::noclean, $::force, $::parallel, $::test);
456              
457             urpm::get_pkgs::clean_all_cache($urpm) if $clean;
458              
459             my $options = $urpm->{options};
460              
461             my ($local_sources, $blists) = urpm::get_pkgs::selected2local_and_blists($urpm,
462             $state->{selected},
463             clean_other => !$noclean && $options->{'pre-clean'},
464             );
465             if (!$local_sources && !$blists) {
466             $urpm->{fatal}(3, N("unable to get source packages, aborting"));
467             }
468              
469             my %sources = %$local_sources;
470              
471             _handle_removable_media($urpm, $callbacks, $blists, \%sources);
472              
473             if (exists $options->{'download-all'}) {
474             _download_all($urpm, $blists, \%sources, $callbacks);
475             }
476              
477             #- now create transaction just before installation, this will save user impression of slowness.
478             #- split of transaction should be disabled if --test is used.
479             urpm::install::build_transaction_set_($urpm, $state,
480             nodeps => $options->{'allow-nodeps'} || $options->{'allow-force'},
481             keep => $options->{keep},
482             split_level => $options->{'split-level'},
483             split_length => !$test && $options->{'split-length'});
484              
485             if ($options{debug__do_not_install}) {
486             $urpm->{debug} = sub { print STDERR "$_[0]\n" };
487             }
488              
489             $urpm->{debug} and $urpm->{debug}(join("\n", "scheduled sets of transactions:",
490             urpm::install::transaction_set_to_string($urpm, $state->{transaction} || [])));
491              
492             $options{debug__do_not_install} and exit 0;
493              
494             ($ok, $nok) = (0, 0);
495             my (@errors, @formatted_errors);
496             $exit_code = 0;
497              
498             my $migrate_back_rpmdb_db_version =
499             $urpm->{root} && urpm::select::should_we_migrate_back_rpmdb_db_version($urpm, $state);
500              
501             #- now process each remove/install transaction
502             foreach my $set (@{$state->{transaction} || []}) {
503              
504             #- put a blank line to separate with previous transaction or user question.
505             $urpm->{print}("\n") if $options{verbose} >= 0;
506              
507             my ($transaction_blists, $transaction_sources) =
508             urpm::install::prepare_transaction($set, $blists, \%sources);
509              
510             #- first, filter out what is really needed to download for this small transaction.
511             my ($error_sources, $msgs) = _download_packages($urpm, $callbacks, $transaction_blists, $transaction_sources);
512             if (@$error_sources) {
513             $nok++;
514             last if !_continue_on_error($urpm, $callbacks, $msgs, $error_sources, \@formatted_errors);
515             }
516              
517             $callbacks->{post_download} and $callbacks->{post_download}->();
518              
519             #- extract packages that should be installed instead of upgraded,
520             my %transaction_sources_install = %{$urpm->extract_packages_to_install($transaction_sources) || {}};
521             $callbacks->{post_extract} and $callbacks->{post_extract}->($set, $transaction_sources, \%transaction_sources_install);
522              
523             #- verify packages
524             if (!$force && ($options->{'verify-rpm'} || grep { $_->{'verify-rpm'} } @{$urpm->{media}})) {
525             my $res = _verify_rpm($urpm, $callbacks, \%transaction_sources_install, $transaction_sources);
526             $res and return $res;
527             }
528              
529             #- install source package only (whatever the user is root or not, but use rpm for that).
530             if ($install_src) {
531             _install_src($urpm, \%transaction_sources_install, $transaction_sources);
532             next;
533             }
534              
535             next if $no_install;
536              
537             #- clean to remove any src package now.
538             clean_trans_sources_from_src_packages($urpm, \%transaction_sources_install, $transaction_sources);
539              
540             #- install/remove other packages
541             if (keys(%transaction_sources_install) || keys(%$transaction_sources) || $set->{remove}) {
542             if ($parallel) {
543             _run_parallel_transaction($urpm, $state, $transaction_sources, \%transaction_sources_install);
544             } else {
545             if ($options{verbose} >= 0) {
546             _log_installing($urpm, \%transaction_sources_install, $transaction_sources);
547             }
548             bug_log(scalar localtime(), " ", join(' ', values %transaction_sources_install, values %$transaction_sources), "\n");
549              
550             _run_transaction($urpm, $state, $callbacks, $set, \%transaction_sources_install, $transaction_sources, \@errors)
551             or last;
552             }
553             }
554              
555             last if $callbacks->{is_canceled} && $callbacks->{is_canceled}->();
556             }
557              
558             #- migrate the chrooted rpmdb if needed
559             if ($migrate_back_rpmdb_db_version) {
560             urpm::sys::migrate_back_rpmdb_db_version($urpm, $urpm->{root});
561             }
562              
563             $callbacks->{completed} and $callbacks->{completed}->();
564              
565             _finish($urpm, $state, $callbacks, \@errors, \@formatted_errors, $ask_unselect, $something_was_to_be_done);
566              
567             $exit_code;
568             }
569              
570             sub _finish {
571             my ($urpm, $state, $callbacks, $errors, $formatted_errors, $ask_unselect, $something_was_to_be_done) = @_;
572              
573             if ($nok) {
574             $callbacks->{trans_error_summary} and $callbacks->{trans_error_summary}->($nok, $errors);
575             if (@$formatted_errors) {
576             $urpm->{print}(join("\n", @$formatted_errors));
577             }
578             if (@$errors) {
579             $urpm->{print}(N("Installation failed:") . join("\n", map { "\t$_" } @$errors));
580             }
581             $exit_code ||= $ok ? 11 : 12;
582             } else {
583             $callbacks->{success_summary} and $callbacks->{success_summary}->();
584             if ($something_was_to_be_done || $auto_select) {
585             if (@{$state->{transaction} || []} == 0 && @$ask_unselect == 0) {
586             if ($auto_select) {
587             if ($options{verbose} >= 0) {
588             #- Warning : the following message is parsed in urpm::parallel_*
589             $urpm->{print}(N("Packages are up to date"));
590             }
591             } else {
592             if ($callbacks->{already_installed_or_not_installable}) {
593             my $msg = urpm::select::translate_already_installed($state);
594             $callbacks->{already_installed_or_not_installable}->([$msg], []);
595             }
596             }
597             $exit_code = 15 if our $expect_install;
598             } elsif ($test && $exit_code == 0) {
599             #- Warning : the following message is parsed in urpm::parallel_*
600             print N("Installation is possible"), "\n";
601             } else {
602             handle_need_restart($urpm, $state, $callbacks);
603             }
604             }
605             }
606             $exit_code;
607             }
608              
609             sub handle_need_restart {
610             my ($urpm, $state, $callbacks) = @_;
611              
612             return if $urpm->{root} && !$ENV{URPMI_TEST_RESTART};
613             return if !$callbacks->{need_restart};
614              
615             if (intersection([ keys %{$state->{selected}} ],
616             [ keys %{$urpm->{provides}{'should-restart'}} ])) {
617             if (my $need_restart_formatted = urpm::sys::need_restart_formatted($urpm->{root})) {
618             $callbacks->{need_restart}($need_restart_formatted);
619              
620             # need_restart() accesses rpm db, so we need to ensure things are clean:
621             urpm::sys::may_clean_rpmdb_shared_regions($urpm, $options{test});
622             }
623             }
624             }
625              
626             1;
627              
628             =back
629              
630             =head1 COPYRIGHT
631              
632             Copyright (C) 1999-2005 MandrakeSoft SA
633              
634             Copyright (C) 2005-2010 Mandriva SA
635              
636             Copyright (C) 2011-2015 Mageia
637              
638             =cut