1#! /usr/bin/perl 2 3# ex:ts=8 sw=4: 4# $OpenBSD: PkgAdd.pm,v 1.90 2016/09/14 14:14:22 espie Exp $ 5# 6# Copyright (c) 2003-2014 Marc Espie <espie@openbsd.org> 7# 8# Permission to use, copy, modify, and distribute this software for any 9# purpose with or without fee is hereby granted, provided that the above 10# copyright notice and this permission notice appear in all copies. 11# 12# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 13# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 14# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 15# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 16# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 17# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 18# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 20use strict; 21use warnings; 22 23use OpenBSD::AddDelete; 24 25package OpenBSD::PackingList; 26 27sub uses_old_libs 28{ 29 my $plist = shift; 30 require OpenBSD::RequiredBy; 31 32 return grep {/^\.libs\d*\-/o} 33 OpenBSD::Requiring->new($plist->pkgname)->list; 34} 35 36sub has_different_sig 37{ 38 my ($plist, $state) = @_; 39 if (!defined $plist->{different_sig}) { 40 my $n = OpenBSD::PackingList->from_installation($plist->pkgname)->signature; 41 my $o = $plist->signature; 42 my $r = $n->compare($o, $state->defines("SHORTENED")); 43 $state->print("Comparing full signature for #1 \"#2\" vs. \"#3\":", 44 $plist->pkgname, $o->string, $n->string) 45 if $state->verbose >= 3; 46 if (defined $r) { 47 if ($r == 0) { 48 $plist->{different_sig} = 0; 49 $state->say("equal") if $state->verbose >= 3; 50 } elsif ($r > 0) { 51 $plist->{different_sig} = 1; 52 $state->say("greater") if $state->verbose >= 3; 53 } else { 54 $plist->{different_sig} = 1; 55 $state->say("less") if $state->verbose >= 3; 56 } 57 } else { 58 $plist->{different_sig} = 1; 59 $state->say("non comparable") if $state->verbose >= 3; 60 } 61 } 62 return $plist->{different_sig}; 63} 64 65package OpenBSD::PackingElement; 66sub hash_files 67{ 68} 69sub tie_files 70{ 71} 72 73package OpenBSD::PackingElement::FileBase; 74sub hash_files 75{ 76 my ($self, $sha, $state) = @_; 77 return if $self->{link} or $self->{symlink} or $self->{nochecksum}; 78 if (defined $self->{d}) { 79 $sha->{$self->{d}->key} = $self; 80 } 81} 82 83sub tie_files 84{ 85 my ($self, $sha, $state) = @_; 86 return if $self->{link} or $self->{symlink} or $self->{nochecksum}; 87 # XXX python doesn't like this, overreliance on timestamps 88 return if $self->{name} =~ m/\.py$/ && !defined $self->{ts}; 89 if (defined $sha->{$self->{d}->key}) { 90 my $tied = $sha->{$self->{d}->key}; 91 # don't tie if there's a problem with the file 92 return unless -f $tied->realname($state); 93 # and do a sanity check that this file wasn't altered 94 return unless (stat _)[7] == $self->{size}; 95 $self->{tieto} = $tied; 96 $tied->{tied} = 1; 97 $state->say("Tieing #1 to #2", $self->stringize, 98 $tied->stringize) if $state->verbose >= 3; 99 } 100} 101 102package OpenBSD::PkgAdd::State; 103our @ISA = qw(OpenBSD::AddDelete::State); 104 105sub handle_options 106{ 107 my $state = shift; 108 $state->SUPER::handle_options('ruUzl:A:P:', 109 '[-acinqrsUuvxz] [-A arch] [-B pkg-destdir] [-D name[=value]]', 110 '[-L localbase] [-l file] [-P type] pkg-name ...'); 111 112 $state->{arch} = $state->opt('A'); 113 114 if ($state->opt('P')) { 115 if ($state->opt('P') eq 'cdrom') { 116 $state->{cdrom_only} = 1; 117 } 118 elsif ($state->opt('P') eq 'ftp') { 119 $state->{ftp_only} = 1; 120 } 121 else { 122 $state->usage("bad option: -P #1", $state->opt('P')); 123 } 124 } 125 if (defined $state->opt('B')) { 126 $state->{destdir} = $state->opt('B'); 127 } 128 if (defined $state->{destdir}) { 129 $state->{destdir}.='/'; 130 } else { 131 $state->{destdir} = ''; 132 } 133 134 135 $state->{hard_replace} = $state->opt('r'); 136 $state->{newupdates} = $state->opt('u') || $state->opt('U'); 137 $state->{allow_replacing} = $state->{hard_replace} || 138 $state->{newupdates}; 139 $state->{pkglist} = $state->opt('l'); 140 $state->{update} = $state->opt('u'); 141 $state->{fuzzy} = $state->opt('z'); 142 143 if (@ARGV == 0 && !$state->{update} && !$state->{pkglist}) { 144 $state->usage("Missing pkgname"); 145 } 146} 147 148sub set_name_from_handle 149{ 150 my ($state, $h, $extra) = @_; 151 $extra //= ''; 152 $state->log->set_context($extra.$h->pkgname); 153} 154 155sub updateset 156{ 157 my $self = shift; 158 require OpenBSD::UpdateSet; 159 160 return OpenBSD::UpdateSet->new($self); 161} 162 163sub updateset_with_new 164{ 165 my ($self, $pkgname) = @_; 166 167 return $self->updateset->add_newer( 168 OpenBSD::Handle->create_new($pkgname)); 169} 170 171sub updateset_from_location 172{ 173 my ($self, $location) = @_; 174 175 return $self->updateset->add_newer( 176 OpenBSD::Handle->from_location($location)); 177} 178 179sub display_timestamp 180{ 181 my ($state, $pkgname, $timestamp) = @_; 182 $state->say("#1 signed on #2", $pkgname, $timestamp); 183} 184 185OpenBSD::Auto::cache(updater, 186 sub { 187 require OpenBSD::Update; 188 return OpenBSD::Update->new; 189 }); 190 191OpenBSD::Auto::cache(tracker, 192 sub { 193 require OpenBSD::Tracker; 194 return OpenBSD::Tracker->new; 195 }); 196 197package OpenBSD::ConflictCache; 198our @ISA = (qw(OpenBSD::Cloner)); 199sub new 200{ 201 my $class = shift; 202 bless {done => {}, c => {}}, $class; 203} 204 205sub add 206{ 207 my ($self, $handle, $state) = @_; 208 return if $self->{done}{$handle}; 209 $self->{done}{$handle} = 1; 210 for my $conflict (OpenBSD::PkgCfl::find_all($handle, $state)) { 211 $self->{c}{$conflict} = 1; 212 } 213} 214 215sub list 216{ 217 my $self = shift; 218 return keys %{$self->{c}}; 219} 220 221sub merge 222{ 223 my ($self, @extra) = @_; 224 $self->clone('c', @extra); 225 $self->clone('done', @extra); 226} 227 228package OpenBSD::UpdateSet; 229use OpenBSD::PackageInfo; 230use OpenBSD::Error; 231use OpenBSD::Handle; 232 233OpenBSD::Auto::cache(solver, 234 sub { 235 return OpenBSD::Dependencies::Solver->new(shift); 236 }); 237 238OpenBSD::Auto::cache(conflict_cache, 239 sub { 240 return OpenBSD::ConflictCache->new; 241 }); 242 243sub setup_header 244{ 245 my ($set, $state, $handle, $info) = @_; 246 247 my $header = $state->deptree_header($set); 248 if (defined $handle) { 249 $header .= $handle->pkgname; 250 } else { 251 $header .= $set->print; 252 } 253 if (defined $info) { 254 $header.=" ($info)"; 255 } 256 257 if (!$state->progress->set_header($header)) { 258 return unless $state->verbose; 259 if (!defined $info) { 260 $header = "Adding $header"; 261 } 262 if (defined $state->{lastheader} && 263 $header eq $state->{lastheader}) { 264 return; 265 } 266 $state->{lastheader} = $header; 267 $state->print("#1", $header); 268 $state->print("(pretending) ") if $state->{not}; 269 $state->print("\n"); 270 } 271} 272 273my $checked = {}; 274 275sub check_security 276{ 277 my ($set, $state, $plist, $h) = @_; 278 return if $checked->{$plist->fullpkgpath}; 279 $checked->{$plist->fullpkgpath} = 1; 280 return if $set->{quirks}; 281 my ($error, $bad); 282 $state->run_quirks( 283 sub { 284 my $quirks = shift; 285 return unless $quirks->can("check_security"); 286 $bad = $quirks->check_security($plist->fullpkgpath); 287 if (defined $bad) { 288 require OpenBSD::PkgSpec; 289 my $spec = OpenBSD::PkgSpec->new($bad); 290 my $r = $spec->match_locations([$h->{location}]); 291 if (@$r != 0) { 292 $error++; 293 } 294 } 295 }); 296 if ($error) { 297 $state->errsay("Package #1 found, matching insecure #2", 298 $h->pkgname, $bad); 299 } 300} 301 302sub display_timestamp 303{ 304 my ($pkgname, $plist, $state) = @_; 305 306 return unless $plist->is_signed; 307 if ($state->defines('nosig')) { 308 $state->errsay("NOT CHECKING DIGITAL SIGNATURE FOR #1", 309 $pkgname); 310 return; 311 } 312 if (!$plist->check_signature($state)) { 313 $state->fatal("#1 is corrupted", $pkgname); 314 } 315 $state->display_timestamp($pkgname, 316 $plist->get('digital-signature')->iso8601); 317} 318 319sub find_kept_handle 320{ 321 my ($set, $n, $state) = @_; 322 unless (defined $n->{location} && defined $n->{location}{update_info}) { 323 $n->complete($state); 324 } 325 my $plist = $n->dependency_info; 326 return if !defined $plist; 327 my $pkgname = $plist->pkgname; 328 if ($set->{quirks}) { 329 display_timestamp($pkgname, $plist, $state); 330 } 331 # condition for no update 332 unless (is_installed($pkgname) && 333 (!$state->{allow_replacing} || 334 !$state->defines('installed') && 335 !$plist->has_different_sig($state) && 336 !$plist->uses_old_libs)) { 337 $set->check_security($state, $plist, $n); 338 return; 339 } 340 my $o = $set->{older}{$pkgname}; 341 if (!defined $o) { 342 $o = OpenBSD::Handle->create_old($pkgname, $state); 343 if (!defined $o->pkgname) { 344 $state->{bad}++; 345 $set->cleanup(OpenBSD::Handle::CANT_INSTALL, 346 "Bogus package already installed"); 347 return; 348 } 349 } 350 $set->check_security($state, $plist, $o); 351 $set->move_kept($o); 352 $o->{tweaked} = 353 OpenBSD::Add::tweak_package_status($pkgname, $state); 354 $state->updater->progress_message($state, "No change in $pkgname"); 355 delete $set->{newer}{$pkgname}; 356 $n->cleanup; 357} 358 359sub figure_out_kept 360{ 361 my ($set, $state) = @_; 362 363 for my $n ($set->newer) { 364 $set->find_kept_handle($n, $state); 365 } 366} 367 368sub complete 369{ 370 my ($set, $state) = @_; 371 372 for my $n ($set->newer) { 373 $n->complete($state); 374 my $plist = $n->plist; 375 return 1 if !defined $plist; 376 return 1 if $n->has_error; 377 } 378 # XXX kept must have complete plists to be able to track 379 # libs for OldLibs 380 for my $o ($set->older, $set->kept) { 381 $o->complete_old($state); 382 } 383 384 my $check = $set->install_issues($state); 385 return 0 if !defined $check; 386 387 if ($check) { 388 $state->{bad}++; 389 $set->cleanup(OpenBSD::Handle::CANT_INSTALL, $check); 390 $state->tracker->cant($set); 391 } 392 return 1; 393} 394 395sub find_conflicts 396{ 397 my ($set, $state) = @_; 398 399 my $c = $set->conflict_cache; 400 401 for my $handle ($set->newer) { 402 $c->add($handle, $state); 403 } 404 return $c->list; 405} 406 407sub mark_as_manual_install 408{ 409 my $set = shift; 410 411 for my $handle ($set->newer) { 412 my $plist = $handle->plist; 413 $plist->has('manual-installation') or 414 OpenBSD::PackingElement::ManualInstallation->add($plist); 415 } 416} 417 418sub updates 419{ 420 my ($n, $plist) = @_; 421 if (!$n->location->update_info->match_pkgpath($plist)) { 422 return 0; 423 } 424 if (!$n->conflict_list->conflicts_with($plist->pkgname)) { 425 return 0; 426 } 427 my $r = OpenBSD::PackageName->from_string($n->pkgname)->compare( 428 OpenBSD::PackageName->from_string($plist->pkgname)); 429 if (defined $r && $r < 0) { 430 return 0; 431 } 432 return 1; 433} 434 435sub is_an_update_from 436{ 437 my ($set, @conflicts) = @_; 438LOOP: for my $c (@conflicts) { 439 next if $c =~ m/^\.libs\d*\-/; 440 next if $c =~ m/^partial\-/; 441 my $plist = OpenBSD::PackingList->from_installation($c, \&OpenBSD::PackingList::UpdateInfoOnly); 442 return 0 unless defined $plist; 443 for my $n ($set->newer) { 444 if (updates($n, $plist)) { 445 next LOOP; 446 } 447 } 448 return 0; 449 } 450 return 1; 451} 452 453sub install_issues 454{ 455 my ($set, $state) = @_; 456 457 my @conflicts = $set->find_conflicts($state); 458 459 if (@conflicts == 0) { 460 if ($state->defines('update_only')) { 461 return "only update, no install"; 462 } else { 463 return 0; 464 } 465 } 466 467 if (!$state->{allow_replacing}) { 468 if (grep { !/^\.libs\d*\-/ && !/^partial\-/ } @conflicts) { 469 if (!$set->is_an_update_from(@conflicts)) { 470 $state->errsay("Can't install #1 because of conflicts (#2)", 471 $set->print, join(',', @conflicts)); 472 return "conflicts"; 473 } 474 } 475 } 476 477 my $later = 0; 478 for my $toreplace (@conflicts) { 479 if ($state->tracker->is_installed($toreplace)) { 480 $state->errsay("Cannot replace #1 in #2: just got installed", 481 $toreplace, $set->print); 482 return "replacing just installed"; 483 } 484 485 next if defined $set->{older}{$toreplace}; 486 next if defined $set->{kept}{$toreplace}; 487 488 $later = 1; 489 my $s = $state->tracker->is_to_update($toreplace); 490 if (defined $s && $s ne $set) { 491 $set->merge($state->tracker, $s); 492 } else { 493 $set->add_older(OpenBSD::Handle->create_old($toreplace, 494 $state)); 495 } 496 } 497 498 return if $later; 499 500 501 my $manual_install = 0; 502 503 for my $old ($set->older) { 504 my $name = $old->pkgname; 505 506 if ($old->has_error(OpenBSD::Handle::NOT_FOUND)) { 507 $state->fatal("can't find #1 in installation", $name); 508 } 509 if ($old->has_error(OpenBSD::Handle::BAD_PACKAGE)) { 510 $state->fatal("couldn't find packing-list for #1", 511 $name); 512 } 513 514 if ($old->plist->has('manual-installation')) { 515 $manual_install = 1; 516 } 517 } 518 519 $set->mark_as_manual_install if $manual_install; 520 521 return 0; 522} 523 524sub try_merging 525{ 526 my ($set, $m, $state) = @_; 527 528 my $s = $state->tracker->is_to_update($m); 529 if (!defined $s) { 530 $s = $state->updateset->add_older( 531 OpenBSD::Handle->create_old($m, $state)); 532 } 533 if ($state->updater->process_set($s, $state)) { 534 $state->say("Merging #1 (#2)", $s->print, $state->ntogo); 535 $set->merge($state->tracker, $s); 536 return 1; 537 } else { 538 $state->errsay("NOT MERGING: can't find update for #1 (#2)", 539 $s->print, $state->ntogo); 540 return 0; 541 } 542} 543 544sub check_forward_dependencies 545{ 546 my ($set, $state) = @_; 547 548 require OpenBSD::ForwardDependencies; 549 $set->{forward} = OpenBSD::ForwardDependencies->find($set); 550 my $bad = $set->{forward}->check($state); 551 552 if (%$bad) { 553 my $no_merge = 1; 554 if (!$state->defines('dontmerge')) { 555 my $okay = 1; 556 for my $m (keys %$bad) { 557 if ($set->try_merging($m, $state)) { 558 $no_merge = 0; 559 } else { 560 $okay = 0; 561 } 562 } 563 return 0 if $okay == 1; 564 } 565 if ($state->defines('updatedepends')) { 566 $state->errsay("Forcing update"); 567 return $no_merge; 568 } elsif ($state->confirm("Proceed with update anyway", 0)) { 569 return $no_merge; 570 } else { 571 return undef; 572 } 573 } 574 return 1; 575} 576 577sub recheck_conflicts 578{ 579 my ($set, $state) = @_; 580 581 # no conflicts between newer sets nor kept sets 582 for my $h ($set->newer, $set->kept) { 583 for my $h2 ($set->newer, $set->kept) { 584 next if $h2 == $h; 585 if ($h->conflict_list->conflicts_with($h2->pkgname)) { 586 $state->errsay("#1: internal conflict between #2 and #3", 587 $set->print, $h->pkgname, $h2->pkgname); 588 return 0; 589 } 590 } 591 } 592 593 return 1; 594} 595 596package OpenBSD::PkgAdd; 597our @ISA = qw(OpenBSD::AddDelete); 598 599use OpenBSD::Dependencies; 600use OpenBSD::PackingList; 601use OpenBSD::PackageInfo; 602use OpenBSD::PackageName; 603use OpenBSD::PkgCfl; 604use OpenBSD::Add; 605use OpenBSD::SharedLibs; 606use OpenBSD::UpdateSet; 607use OpenBSD::Error; 608 609sub failed_message 610{ 611 my ($base_msg, $received, @l) = @_; 612 my $msg = $base_msg; 613 if ($received) { 614 $msg = "Caught SIG$received. $msg"; 615 } 616 if (@l > 0) { 617 $msg.= ", partial installation recorded as ".join(',', @l); 618 } 619 return $msg; 620} 621 622sub save_partial_set 623{ 624 my ($set, $state) = @_; 625 626 return () if $state->{not}; 627 my @l = (); 628 for my $h ($set->newer) { 629 next unless defined $h->{partial}; 630 push(@l, OpenBSD::Add::record_partial_installation($h->plist, $state, $h->{partial})); 631 } 632 return @l; 633} 634 635sub partial_install 636{ 637 my ($base_msg, $set, $state) = @_; 638 return failed_message($base_msg, $state->{received}, save_partial_set($set, $state)); 639} 640 641sub build_before 642{ 643 my %known = map {($_->pkgname, 1)} @_; 644 require OpenBSD::RequiredBy; 645 for my $c (@_) { 646 for my $d (OpenBSD::RequiredBy->new($c->pkgname)->list) { 647 push(@{$c->{before}}, $d) if $known{$d}; 648 } 649 } 650} 651 652sub okay 653{ 654 my ($h, $c) = @_; 655 656 for my $d (@{$c->{before}}) { 657 return 0 if !$h->{$d}; 658 } 659 return 1; 660} 661 662sub iterate 663{ 664 my $sub = pop @_; 665 my $done = {}; 666 my $something_done; 667 668 do { 669 $something_done = 0; 670 671 for my $c (@_) { 672 next if $done->{$c->pkgname}; 673 if (okay($done, $c)) { 674 &$sub($c); 675 $done->{$c->pkgname} = 1; 676 $something_done = 1; 677 } 678 } 679 } while ($something_done); 680 # if we can't do stuff in order, do it anyway 681 for my $c (@_) { 682 next if $done->{$c->pkgname}; 683 &$sub($c); 684 } 685} 686 687sub check_digital_signature 688{ 689 my ($set, $state) = @_; 690 for my $handle ($set->newer) { 691 $state->set_name_from_handle($handle, '+'); 692 my $plist = $handle->plist; 693 my $pkgname = $plist->pkgname; 694 if ($plist->is_signed) { 695 if ($state->defines('nosig')) { 696 $state->errsay("NOT CHECKING DIGITAL SIGNATURE FOR #1", 697 $pkgname); 698 } else { 699 if (!$plist->check_signature($state)) { 700 $state->fatal("#1 is corrupted", 701 $pkgname); 702 } 703 $plist->{check_digest} = 1; 704 $state->{packages_with_sig}++; 705 } 706 } else { 707 $state->{packages_without_sig}{$pkgname} = 1; 708 return if $state->{signature_style} eq 'unsigned'; 709 my $okay = 0; 710 my $url; 711 if (defined $handle->location) { 712 $url = $handle->location->url; 713 } else { 714 $url = $pkgname; 715 } 716 $okay = $state->confirm("UNSIGNED PACKAGE $url: install anyway", 0); 717 if (!$okay) { 718 $state->fatal("Unsigned package #1", $url); 719 } 720 } 721 } 722} 723 724sub delete_old_packages 725{ 726 my ($set, $state) = @_; 727 728 build_before($set->older_to_do); 729 iterate($set->older_to_do, sub { 730 return if $state->{size_only}; 731 my $o = shift; 732 $set->setup_header($state, $o, "deleting"); 733 my $oldname = $o->pkgname; 734 $state->set_name_from_handle($o, '-'); 735 require OpenBSD::Delete; 736 try { 737 OpenBSD::Delete::delete_plist($o->plist, $state); 738 } catchall { 739 $state->errsay($_); 740 $state->fatal(partial_install( 741 "Deinstallation of $oldname failed", 742 $set, $state)); 743 }; 744 745 if (defined $state->{updatedepends}) { 746 delete $state->{updatedepends}->{$oldname}; 747 } 748 OpenBSD::PkgCfl::unregister($o, $state); 749 }); 750 $set->cleanup_old_shared($state); 751 # Here there should be code to handle old libs 752} 753 754sub delayed_delete 755{ 756 my $state = shift; 757 for my $realname (@{$state->{delayed}}) { 758 if (!unlink $realname) { 759 $state->errsay("Problem deleting #1: #2", $realname, 760 $!); 761 $state->log("deleting #1 failed: #2", $realname, $!); 762 } 763 } 764 delete $state->{delayed}; 765} 766 767sub really_add 768{ 769 my ($set, $state) = @_; 770 771 my $errors = 0; 772 773 check_digital_signature($set, $state); 774 775 if ($state->{not}) { 776 $state->status->what("Pretending to add"); 777 } else { 778 $state->status->what("Adding"); 779 } 780 $set->setup_header($state); 781 782 # XXX in `combined' updates, some dependencies may remove extra 783 # packages, so we do a double-take on the list of packages we 784 # are actually replacing. 785 my $replacing = 0; 786 if ($set->older_to_do) { 787 $replacing = 1; 788 } 789 $state->{replacing} = $replacing; 790 791 my $handler = sub { 792 $state->{received} = shift; 793 $state->errsay("Interrupted"); 794 if ($state->{hardkill}) { 795 delete $state->{hardkill}; 796 return; 797 } 798 $state->{interrupted}++; 799 }; 800 local $SIG{'INT'} = $handler; 801 local $SIG{'QUIT'} = $handler; 802 local $SIG{'HUP'} = $handler; 803 local $SIG{'KILL'} = $handler; 804 local $SIG{'TERM'} = $handler; 805 806 $state->{hardkill} = $state->{delete_first}; 807 808 if ($replacing) { 809 require OpenBSD::OldLibs; 810 OpenBSD::OldLibs->save($set, $state); 811 } 812 813 if ($state->{delete_first}) { 814 delete_old_packages($set, $state); 815 } 816 817 for my $handle ($set->newer) { 818 next if $state->{size_only}; 819 $set->setup_header($state, $handle, "extracting"); 820 821 try { 822 OpenBSD::Add::perform_extraction($handle, 823 $state); 824 } catchall { 825 unless ($state->{interrupted}) { 826 $state->errsay($_); 827 $errors++; 828 } 829 }; 830 if ($state->{interrupted} || $errors) { 831 $state->fatal(partial_install("Installation of ". 832 $handle->pkgname." failed", $set, $state)); 833 } 834 } 835 if ($state->{delete_first}) { 836 delayed_delete($state); 837 } else { 838 $state->{hardkill} = 1; 839 delete_old_packages($set, $state); 840 } 841 842 iterate($set->newer, sub { 843 return if $state->{size_only}; 844 my $handle = shift; 845 my $pkgname = $handle->pkgname; 846 my $plist = $handle->plist; 847 848 $set->setup_header($state, $handle, "installing"); 849 $state->set_name_from_handle($handle, '+'); 850 851 try { 852 OpenBSD::Add::perform_installation($handle, $state); 853 } catchall { 854 unless ($state->{interrupted}) { 855 $state->errsay($_); 856 $errors++; 857 } 858 }; 859 860 unlink($plist->infodir.CONTENTS); 861 if ($state->{interrupted} || $errors) { 862 $state->fatal(partial_install("Installation of $pkgname failed", 863 $set, $state)); 864 } 865 }); 866 $set->setup_header($state); 867 $state->progress->next($state->ntogo(-1)); 868 for my $handle ($set->newer) { 869 my $pkgname = $handle->pkgname; 870 my $plist = $handle->plist; 871 OpenBSD::SharedLibs::add_libs_from_plist($plist, $state); 872 OpenBSD::Add::tweak_plist_status($plist, $state); 873 OpenBSD::Add::register_installation($plist, $state); 874 add_installed($pkgname); 875 delete $handle->{partial}; 876 OpenBSD::PkgCfl::register($handle, $state); 877 } 878 delete $state->{partial}; 879 $set->{solver}->register_dependencies($state); 880 if ($replacing) { 881 $set->{forward}->adjust($state); 882 } 883 if ($state->{repairdependencies}) { 884 $set->{solver}->repair_dependencies($state); 885 } 886 delete $state->{delete_first}; 887 $state->syslog("Added #1", $set->print); 888 if ($state->{received}) { 889 die "interrupted"; 890 } 891} 892 893sub newer_has_errors 894{ 895 my ($set, $state) = @_; 896 897 for my $handle ($set->newer) { 898 if ($handle->has_error(OpenBSD::Handle::ALREADY_INSTALLED)) { 899 $set->cleanup(OpenBSD::Handle::ALREADY_INSTALLED); 900 return 1; 901 } 902 if ($handle->has_error) { 903 $state->set_name_from_handle($handle); 904 $state->log("Can't install #1: #2", 905 $handle->pkgname, $handle->error_message); 906 $state->{bad}++; 907 $set->cleanup($handle->has_error); 908 $state->tracker->cant($set); 909 return 1; 910 } 911 } 912 return 0; 913} 914 915sub newer_is_bad_arch 916{ 917 my ($set, $state) = @_; 918 919 for my $handle ($set->newer) { 920 if ($handle->plist->has('arch')) { 921 unless ($handle->plist->{arch}->check($state->{arch})) { 922 $state->set_name_from_handle($handle); 923 $state->log("#1 is not for the right architecture", 924 $handle->pkgname); 925 if (!$state->defines('arch')) { 926 $state->{bad}++; 927 $set->cleanup(OpenBSD::Handle::CANT_INSTALL); 928 $state->tracker->cant($set); 929 return 1; 930 } 931 } 932 } 933 } 934 return 0; 935} 936 937sub may_tie_files 938{ 939 my ($set, $state) = @_; 940 if ($set->newer > 0 && $set->older_to_do > 0 && !$state->defines('donttie')) { 941 my $sha = {}; 942 943 for my $o ($set->older_to_do) { 944 $o->{plist}->hash_files($sha, $state); 945 } 946 for my $n ($set->newer) { 947 $n->{plist}->tie_files($sha, $state); 948 } 949 } 950} 951 952sub process_set 953{ 954 my ($self, $set, $state) = @_; 955 956 $state->{current_set} = $set; 957 958 if (!$state->updater->process_set($set, $state)) { 959 return (); 960 } 961 962 for my $handle ($set->newer) { 963 if ($state->tracker->is_installed($handle->pkgname)) { 964 $set->move_kept($handle); 965 $handle->{tweaked} = OpenBSD::Add::tweak_package_status($handle->pkgname, $state); 966 } 967 } 968 969 $set->figure_out_kept($state); 970 971 if (newer_has_errors($set, $state)) { 972 return (); 973 } 974 if ($set->newer == 0 && $set->older_to_do == 0) { 975 $state->tracker->uptodate($set); 976 return (); 977 } 978 979 my @deps = $set->solver->solve_depends($state); 980 if ($state->verbose >= 2) { 981 $set->solver->dump($state); 982 } 983 if (@deps > 0) { 984 $state->build_deptree($set, @deps); 985 $set->solver->check_for_loops($state); 986 return (@deps, $set); 987 } 988 989 if (!$set->complete($state)) { 990 return $set; 991 } 992 993 if (newer_has_errors($set, $state)) { 994 return (); 995 } 996 997 for my $h ($set->newer) { 998 $set->check_security($state, $h->plist, $h); 999 } 1000 1001 if (newer_is_bad_arch($set, $state)) { 1002 return (); 1003 } 1004 1005 if ($set->older_to_do) { 1006 my $r = $set->check_forward_dependencies($state); 1007 if (!defined $r) { 1008 $state->{bad}++; 1009 $set->cleanup(OpenBSD::Handle::CANT_INSTALL); 1010 $state->tracker->cant($set); 1011 return (); 1012 } 1013 if ($r == 0) { 1014 return $set; 1015 } 1016 } 1017 1018 # verify dependencies have been installed 1019 my $baddeps = $set->solver->check_depends; 1020 1021 if (@$baddeps) { 1022 $state->errsay("Can't install #1: can't resolve #2", 1023 $set->print, join(',', @$baddeps)); 1024 $state->{bad}++; 1025 $set->cleanup(OpenBSD::Handle::CANT_INSTALL,"bad dependencies"); 1026 $state->tracker->cant($set); 1027 return (); 1028 } 1029 1030 if (!$set->solver->solve_wantlibs($state)) { 1031 $state->{bad}++; 1032 $set->cleanup(OpenBSD::Handle::CANT_INSTALL, "libs not found"); 1033 $state->tracker->cant($set); 1034 return (); 1035 } 1036# if (!$set->solver->solve_tags($state)) { 1037# if (!$state->defines('libdepends')) { 1038# $state->{bad}++; 1039# return (); 1040# } 1041# } 1042 if (!$set->recheck_conflicts($state)) { 1043 $state->{bad}++; 1044 $set->cleanup(OpenBSD::Handle::CANT_INSTALL, "fatal conflicts"); 1045 $state->tracker->cant($set); 1046 return (); 1047 } 1048 if ($set->older_to_do) { 1049 require OpenBSD::Replace; 1050 if (!OpenBSD::Replace::is_set_safe($set, $state)) { 1051 $state->{bad}++; 1052 $set->cleanup(OpenBSD::Handle::CANT_INSTALL, "exec detected"); 1053 $state->tracker->cant($set); 1054 return (); 1055 } 1056 } 1057 may_tie_files($set, $state); 1058 if ($set->newer > 0 || $set->older_to_do > 0) { 1059 for my $h ($set->newer) { 1060 $h->plist->set_infodir($h->location->info); 1061 delete $h->location->{contents}; 1062 } 1063 1064 if (!$set->validate_plists($state)) { 1065 $state->{bad}++; 1066 $set->cleanup(OpenBSD::Handle::CANT_INSTALL, 1067 "file issues"); 1068 $state->tracker->cant($set); 1069 return (); 1070 } 1071 1072 really_add($set, $state); 1073 } 1074 $set->cleanup; 1075 $state->tracker->done($set); 1076 return (); 1077} 1078 1079sub inform_user_of_problems 1080{ 1081 my $state = shift; 1082 my @cantupdate = $state->tracker->cant_list; 1083 if (@cantupdate > 0) { 1084 $state->run_quirks( 1085 sub { 1086 my $quirks = shift; 1087 $quirks->filter_obsolete(\@cantupdate, $state); 1088 }); 1089 1090 $state->say("Couldn't find updates for #1", 1091 join(' ', sort @cantupdate)) if @cantupdate > 0; 1092 } 1093 if (defined $state->{issues}) { 1094 $state->say("There were some ambiguities. ". 1095 "Please run in interactive mode again."); 1096 } 1097} 1098 1099# if we already have quirks, we update it. If not, we try to install it. 1100sub quirk_set 1101{ 1102 my $state = shift; 1103 require OpenBSD::Search; 1104 1105 my $set = $state->updateset; 1106 $set->{quirks} = 1; 1107 my $l = $state->repo->installed->match_locations(OpenBSD::Search::Stem->new('quirks')); 1108 if (@$l > 0) { 1109 $set->add_older(map {OpenBSD::Handle->from_location($_)} @$l); 1110 } else { 1111 $set->add_hints2('quirks'); 1112 } 1113 return $set; 1114} 1115 1116sub do_quirks 1117{ 1118 my ($self, $state) = @_; 1119 my $set = quirk_set($state); 1120 $self->process_set($set, $state); 1121} 1122 1123 1124sub process_parameters 1125{ 1126 my ($self, $state) = @_; 1127 my $add_hints = $state->{fuzzy} ? "add_hints" : "add_hints2"; 1128 1129 # match against a list 1130 if ($state->{pkglist}) { 1131 open my $f, '<', $state->{pkglist} or 1132 $state->fatal("bad list #1: #2", $state->{pkglist}, $!); 1133 while (<$f>) { 1134 chomp; 1135 s/\s.*//; 1136 s/\.tgz$//; 1137 push(@{$state->{setlist}}, 1138 $state->updateset->$add_hints($_)); 1139 } 1140 } 1141 1142 # update existing stuff 1143 if ($state->{update}) { 1144 1145 if (@ARGV == 0) { 1146 @ARGV = sort(installed_packages()); 1147 $state->{allupdates} = 1; 1148 } 1149 my $inst = $state->repo->installed; 1150 for my $pkgname (@ARGV) { 1151 my $l; 1152 1153 next if $pkgname =~ m/^quirks\-\d/; 1154 if (OpenBSD::PackageName::is_stem($pkgname)) { 1155 $l = $state->updater->stem2location($inst, $pkgname, $state); 1156 } else { 1157 $l = $inst->find($pkgname); 1158 } 1159 if (!defined $l) { 1160 $state->say("Problem finding #1", $pkgname); 1161 } else { 1162 push(@{$state->{setlist}}, 1163 $state->updateset->add_older(OpenBSD::Handle->from_location($l))); 1164 } 1165 } 1166 } else { 1167 1168 # actual names 1169 for my $pkgname (@ARGV) { 1170 next if $pkgname =~ m/^quirks\-\d/; 1171 push(@{$state->{setlist}}, 1172 $state->updateset->$add_hints($pkgname)); 1173 } 1174 } 1175} 1176 1177sub finish_display 1178{ 1179 my ($self, $state) = @_; 1180 OpenBSD::Add::manpages_index($state); 1181 1182 # and display delayed thingies. 1183 my $warn = 1; 1184 if ($state->{signature_style} eq 'unsigned') { 1185 $warn = 0; 1186 } 1187 if ($state->{packages_with_sig}) { 1188 $warn = 1; 1189 } 1190 if ($warn && $state->{packages_without_sig}) { 1191 $state->say("UNSIGNED PACKAGES: #1", 1192 join(', ', keys %{$state->{packages_without_sig}})); 1193 } 1194 if (defined $state->{updatedepends} && %{$state->{updatedepends}}) { 1195 $state->say("Forced updates, bogus dependencies for ", 1196 join(' ', sort(keys %{$state->{updatedepends}})), 1197 " may remain"); 1198 } 1199 inform_user_of_problems($state); 1200} 1201 1202sub tweak_list 1203{ 1204 my ($self, $state) = @_; 1205 1206 $state->run_quirks( 1207 sub { 1208 my $quirks = shift; 1209 $quirks->tweak_list($state->{setlist}, $state); 1210 }); 1211} 1212 1213sub main 1214{ 1215 my ($self, $state) = @_; 1216 1217 $state->progress->set_header(''); 1218 $self->do_quirks($state); 1219 1220 $self->process_setlist($state); 1221} 1222 1223 1224sub new_state 1225{ 1226 my ($self, $cmd) = @_; 1227 return OpenBSD::PkgAdd::State->new($cmd); 1228} 1229 12301; 1231