1#! /usr/bin/perl 2 3# ex:ts=8 sw=4: 4# $OpenBSD: PkgAdd.pm,v 1.151 2024/12/02 22:32:57 sthen 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 $state->ldconfig->ensure; 851 delete $state->{partial}; 852 $set->{solver}->register_dependencies($state); 853 if ($replacing) { 854 $set->{forward}->adjust($state); 855 } 856 if ($state->{repairdependencies}) { 857 $set->{solver}->repair_dependencies($state); 858 } 859 delete $state->{delete_first}; 860 $state->syslog("Added #1", $set->print); 861 if ($state->{received}) { 862 die "interrupted"; 863 } 864 if (!$set->{quirks}) { 865 $state->{did_something} = 1; 866 } 867} 868 869sub newer_has_errors($set, $state) 870{ 871 for my $handle ($set->newer) { 872 if ($handle->has_error(OpenBSD::Handle::ALREADY_INSTALLED)) { 873 $set->cleanup(OpenBSD::Handle::ALREADY_INSTALLED); 874 return 1; 875 } 876 if ($handle->has_error) { 877 $state->set_name_from_handle($handle); 878 $state->log("Can't install #1: #2", 879 $handle->pkgname, $handle->error_message) 880 unless $handle->has_reported_error; 881 $state->{bad}++; 882 $set->cleanup($handle->has_error); 883 $state->tracker->cant($set); 884 return 1; 885 } 886 } 887 return 0; 888} 889 890sub newer_is_bad_arch($set, $state) 891{ 892 for my $handle ($set->newer) { 893 if ($handle->plist->has('arch')) { 894 unless ($handle->plist->{arch}->check($state->{arch})) { 895 $state->set_name_from_handle($handle); 896 $state->log("#1 is not for the right architecture", 897 $handle->pkgname); 898 if (!$state->defines('arch')) { 899 $state->{bad}++; 900 $set->cleanup(OpenBSD::Handle::CANT_INSTALL); 901 $state->tracker->cant($set); 902 return 1; 903 } 904 } 905 } 906 } 907 return 0; 908} 909 910sub may_tie_files($set, $state) 911{ 912 if ($set->newer > 0 && $set->older_to_do > 0 && 913 !$state->defines('donttie')) { 914 my $sha = {}; 915 916 for my $o ($set->older_to_do) { 917 $set->setup_header($state, $o, "hashing"); 918 $state->progress->visit_with_count($o->{plist}, 919 'hash_files', $sha); 920 } 921 for my $n ($set->newer) { 922 $set->setup_header($state, $n, "tieing"); 923 $state->progress->visit_with_count($n->{plist}, 924 'tie_files', $sha); 925 } 926 } 927} 928 929sub process_set($self, $set, $state) 930{ 931 $state->{current_set} = $set; 932 933 if (!$state->updater->process_set($set, $state)) { 934 return (); 935 } 936 937 $set->setup_header($state, undef, "processing"); 938 $state->progress->message("..."); 939 $set->precomplete($state); 940 for my $handle ($set->newer) { 941 if ($state->tracker->is_installed($handle->pkgname)) { 942 $set->move_kept($handle); 943 $handle->{tweaked} = OpenBSD::Add::tweak_package_status($handle->pkgname, $state); 944 } 945 } 946 947 if (newer_has_errors($set, $state)) { 948 return (); 949 } 950 951 my @deps = $set->solver->solve_depends($state); 952 if ($state->verbose >= 2) { 953 $set->solver->dump($state); 954 } 955 if (@deps > 0) { 956 $state->build_deptree($set, @deps); 957 $set->solver->check_for_loops($state); 958 return (@deps, $set); 959 } 960 961 $set->figure_out_kept($state); 962 963 if ($set->newer == 0 && $set->older_to_do == 0) { 964 $state->tracker->uptodate($set); 965 return (); 966 } 967 968 if (!$set->complete($state)) { 969 return $set; 970 } 971 972 if (newer_has_errors($set, $state)) { 973 return (); 974 } 975 976 for my $h ($set->newer) { 977 $set->check_security($state, $h->plist, $h); 978 } 979 980 if (newer_is_bad_arch($set, $state)) { 981 return (); 982 } 983 984 if ($set->older_to_do) { 985 my $r = $set->check_forward_dependencies($state); 986 if (!defined $r) { 987 $state->{bad}++; 988 $set->cleanup(OpenBSD::Handle::CANT_INSTALL); 989 $state->tracker->cant($set); 990 return (); 991 } 992 if ($r == 0) { 993 return $set; 994 } 995 } 996 997 # verify dependencies have been installed 998 my $baddeps = $set->solver->check_depends; 999 1000 if (@$baddeps) { 1001 $state->errsay("Can't install #1: can't resolve #2", 1002 $set->print, join(',', @$baddeps)); 1003 $state->{bad}++; 1004 $set->cleanup(OpenBSD::Handle::CANT_INSTALL,"bad dependencies"); 1005 $state->tracker->cant($set); 1006 return (); 1007 } 1008 1009 if (!$set->solver->solve_wantlibs($state)) { 1010 $state->{bad}++; 1011 $set->cleanup(OpenBSD::Handle::CANT_INSTALL, "libs not found"); 1012 $state->tracker->cant($set); 1013 return (); 1014 } 1015 if (!$set->solver->solve_tags($state)) { 1016 $set->cleanup(OpenBSD::Handle::CANT_INSTALL, "tags not found"); 1017 $state->tracker->cant($set); 1018 $state->{bad}++; 1019 return (); 1020 } 1021 if (!$set->recheck_conflicts($state)) { 1022 $state->{bad}++; 1023 $set->cleanup(OpenBSD::Handle::CANT_INSTALL, "fatal conflicts"); 1024 $state->tracker->cant($set); 1025 return (); 1026 } 1027 # sets with only tags can be updated without temp files while skipping 1028 # installing 1029 if ($set->older_to_do) { 1030 require OpenBSD::Replace; 1031 $set->{simple_update} = 1032 OpenBSD::Replace::set_has_no_exec($set, $state); 1033 } else { 1034 $set->{simple_update} = 1; 1035 } 1036 if ($state->verbose && !$set->{simple_update}) { 1037 $state->say("Update Set #1 runs exec commands", $set->print); 1038 } 1039 if ($set->newer > 0 || $set->older_to_do > 0) { 1040 if ($state->{not}) { 1041 $state->status->what("Pretending to add"); 1042 } else { 1043 $state->status->what("Adding"); 1044 } 1045 for my $h ($set->newer) { 1046 $h->plist->set_infodir($h->location->info); 1047 delete $h->location->{contents}; 1048 } 1049 1050 may_tie_files($set, $state); 1051 if (!$set->validate_plists($state)) { 1052 $state->{bad}++; 1053 $set->cleanup(OpenBSD::Handle::CANT_INSTALL, 1054 "file issues"); 1055 $state->tracker->cant($set); 1056 return (); 1057 } 1058 1059 really_add($set, $state); 1060 } 1061 $set->cleanup; 1062 $state->tracker->done($set); 1063 if (defined $state->debug_cache_directory) { 1064 for my $p ($set->newer_names) { 1065 $self->may_grab_debug_for($p, 0, $state); 1066 } 1067 } 1068 return (); 1069} 1070 1071sub may_grab_debug_for($class, $orig, $kept, $state) 1072{ 1073 return if $orig =~ m/^debug\-/; 1074 my $dbg = "debug-$orig"; 1075 return if $state->tracker->is_known($dbg); 1076 return if OpenBSD::PackageInfo::is_installed($dbg); 1077 my $d = $state->debug_cache_directory; 1078 return if $kept && -f "$d/$dbg.tgz"; 1079 $class->grab_debug_package($d, $dbg, $state); 1080} 1081 1082sub grab_debug_package($class, $d, $dbg, $state) 1083{ 1084 my $o = $state->locator->find($dbg, $state); 1085 return if !defined $o; 1086 require OpenBSD::Temp; 1087 my ($fh, $name) = OpenBSD::Temp::permanent_file($d, "debug-pkg"); 1088 if (!defined $fh) { 1089 $state->errsay(OpenBSD::Temp->last_error); 1090 return; 1091 } 1092 my $r = fork; 1093 if (!defined $r) { 1094 $state->fatal("Cannot fork: #1", $!); 1095 } elsif ($r == 0) { 1096 $DB::inhibit_exit = 0; 1097 open(STDOUT, '>&', $fh); 1098 open(STDERR, '>>', $o->{errors}); 1099 $o->{repository}->grab_object($o); 1100 } else { 1101 close($fh); 1102 waitpid($r, 0); 1103 my $c = $?; 1104 $o->{repository}->parse_problems($o->{errors}, 1, $o); 1105 if ($c == 0) { 1106 rename($name, "$d/$dbg.tgz"); 1107 } else { 1108 unlink($name); 1109 $state->errsay("Grabbing debug package failed: #1", 1110 $state->child_error($c)); 1111 } 1112 } 1113} 1114 1115sub report_cantupdate($state, $cantupdate) 1116{ 1117 if ($state->tracker->did_something) { 1118 $state->say("Couldn't find updates for #1", 1119 join(' ', sort @$cantupdate)); 1120 } else { 1121 $state->say("Couldn't find any update"); 1122 } 1123} 1124 1125sub inform_user_of_problems($state) 1126{ 1127 my @cantupdate = $state->tracker->cant_list; 1128 if (@cantupdate > 0) { 1129 $state->run_quirks( 1130 sub($quirks) { 1131 $quirks->filter_obsolete(\@cantupdate, $state); 1132 }); 1133 if (@cantupdate > 0) { 1134 report_cantupdate($state, \@cantupdate); 1135 $state->{bad}++; 1136 } 1137 } 1138 if (defined $state->{issues}) { 1139 $state->say("There were some ambiguities. ". 1140 "Please run in interactive mode again."); 1141 } 1142 my @install = $state->tracker->cant_install_list; 1143 if (@install > 0) { 1144 $state->say("Couldn't install #1", 1145 join(' ', sort @install)); 1146 $state->{bad}++; 1147 } 1148} 1149 1150# if we already have quirks, we update it. If not, we try to install it. 1151sub quirk_set($state) 1152{ 1153 require OpenBSD::Search; 1154 1155 my $set = $state->updateset; 1156 $set->{quirks} = 1; 1157 my $l = $state->repo->installed->match_locations(OpenBSD::Search::Stem->new('quirks')); 1158 if (@$l > 0) { 1159 $set->add_older(map {OpenBSD::Handle->from_location($_)} @$l); 1160 } else { 1161 $set->add_hints2('quirks'); 1162 } 1163 return $set; 1164} 1165 1166sub do_quirks($self, $state) 1167{ 1168 my $list = [quirk_set($state)]; 1169 $state->tracker->todo(@$list); 1170 while (my $set = shift @$list) { 1171 $state->status->what->set($set); 1172 $set = $set->real_set; 1173 next if $set->{finished}; 1174 $state->progress->set_header('Checking packages'); 1175 unshift(@$list, $self->process_set($set, $state)); 1176 } 1177} 1178 1179sub process_parameters($self, $state) 1180{ 1181 my $add_hints = $state->{fuzzy} ? "add_hints" : "add_hints2"; 1182 1183 $state->{did_something} = 0; 1184 1185 # match against a list 1186 if ($state->{pkglist}) { 1187 open my $f, '<', $state->{pkglist} or 1188 $state->fatal("bad list #1: #2", $state->{pkglist}, $!); 1189 while (<$f>) { 1190 chomp; 1191 s/\s.*//; 1192 s/\.tgz$//; 1193 push(@{$state->{setlist}}, 1194 $state->updateset->$add_hints($_)); 1195 } 1196 } 1197 1198 # update existing stuff 1199 if ($state->{update}) { 1200 if (@ARGV == 0) { 1201 @ARGV = sort(installed_packages()); 1202 } 1203 my $inst = $state->repo->installed; 1204 for my $pkgname (@ARGV) { 1205 my $l; 1206 1207 next if $pkgname =~ m/^quirks\-\d/; 1208 if (OpenBSD::PackageName::is_stem($pkgname)) { 1209 $l = $state->updater->stem2location($inst, $pkgname, $state); 1210 } else { 1211 $l = $inst->find($pkgname); 1212 } 1213 if (!defined $l) { 1214 $state->say("Problem finding #1", $pkgname); 1215 } else { 1216 push(@{$state->{setlist}}, 1217 $state->updateset->add_older(OpenBSD::Handle->from_location($l))); 1218 } 1219 } 1220 } else { 1221 1222 # actual names 1223 for my $pkgname (@ARGV) { 1224 next if $pkgname =~ m/^quirks\-\d/; 1225 push(@{$state->{setlist}}, 1226 $state->updateset->$add_hints($pkgname)); 1227 } 1228 } 1229} 1230 1231sub finish_display($self, $state) 1232{ 1233 OpenBSD::Add::manpages_index($state); 1234 1235 # and display delayed thingies. 1236 if (defined $state->{updatedepends} && %{$state->{updatedepends}}) { 1237 $state->say("Forced updates, bogus dependencies for ", 1238 join(' ', sort(keys %{$state->{updatedepends}})), 1239 " may remain"); 1240 } 1241 inform_user_of_problems($state); 1242} 1243 1244sub tweak_list($self, $state) 1245{ 1246 $state->run_quirks( 1247 sub($quirks) { 1248 $quirks->tweak_list($state->{setlist}, $state); 1249 }); 1250} 1251 1252sub main($self, $state) 1253{ 1254 $state->progress->set_header(''); 1255 $self->do_quirks($state); 1256 1257 $self->process_setlist($state); 1258} 1259 1260sub exit_code($self, $state) 1261{ 1262 my $rc = $self->SUPER::exit_code($state); 1263 if ($rc == 0 && $state->defines("SYSPATCH_LIKE")) { 1264 if (!$state->{did_something}) { 1265 $rc = 2; 1266 } 1267 } 1268 return $rc; 1269} 1270 1271sub new_state($self, $cmd) 1272{ 1273 return OpenBSD::PkgAdd::State->new($cmd); 1274} 1275 12761; 1277