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