1# ex:ts=8 sw=4: 2# $OpenBSD: UpdateSet.pm,v 1.85 2019/07/04 09:47:09 espie Exp $ 3# 4# Copyright (c) 2007-2010 Marc Espie <espie@openbsd.org> 5# 6# Permission to use, copy, modify, and distribute this software for any 7# purpose with or without fee is hereby granted, provided that the above 8# copyright notice and this permission notice appear in all copies. 9# 10# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 18 19# an UpdateSet is a list of packages to remove/install. 20# it contains several things: 21# -> a list of older packages to remove (installed locations) 22# -> a list of newer packages to add (might be very simple locations) 23# -> a list of "hints", as package names to install 24# -> a list of packages that are kept throughout an update 25# every add/remove operations manipulate UpdateSet. 26# 27# Since older packages are always installed, they're organized as a hash. 28# 29# XXX: an UpdateSet succeeds or fails "together". 30# if several packages should be removed/added, then not being able 31# to do stuff on ONE of them is enough to invalidate the whole set. 32# 33# Normal UpdateSets contain one newer package at most. 34# Bigger UpdateSets can be created through the merge operation, which 35# will be used only when necessary. 36# 37# kept packages are needed after merges, where some dependencies may 38# not need updating, and to distinguish from old packages that will be 39# removed. 40# 41# for instance, package installation will check UpdateSets for internal 42# dependencies and for conflicts. For that to work, we need kept stuff 43# 44use strict; 45use warnings; 46 47# hints should behave like locations 48package OpenBSD::hint; 49sub new 50{ 51 my ($class, $name) = @_; 52 bless {name => $name}, $class; 53} 54 55sub pkgname 56{ 57 return shift->{name}; 58} 59 60package OpenBSD::hint2; 61our @ISA = qw(OpenBSD::hint); 62 63package OpenBSD::DeleteSet; 64use OpenBSD::Error; 65 66sub new 67{ 68 my ($class, $state) = @_; 69 return bless {older => {}}, $class; 70} 71 72sub add_older 73{ 74 my $self = shift; 75 for my $h (@_) { 76 $self->{older}{$h->pkgname} = $h; 77 $h->{is_old} = 1; 78 } 79 return $self; 80} 81 82sub older 83{ 84 my $self = shift; 85 return values %{$self->{older}}; 86} 87 88sub older_names 89{ 90 my $self = shift; 91 return keys %{$self->{older}}; 92} 93 94sub all_handles 95{ 96 &older; 97} 98 99sub changed_handles 100{ 101 &older; 102} 103 104sub mark_as_finished 105{ 106 my $self = shift; 107 $self->{finished} = 1; 108} 109 110sub cleanup 111{ 112 my ($self, $error, $errorinfo) = @_; 113 for my $h ($self->all_handles) { 114 $h->cleanup($error, $errorinfo); 115 } 116 if (defined $error) { 117 $self->{error} //= $error; 118 $self->{errorinfo} //= $errorinfo; 119 } 120 delete $self->{solver}; 121 delete $self->{known_mandirs}; 122 delete $self->{known_displays}; 123 $self->mark_as_finished; 124} 125 126sub has_error 127{ 128 &OpenBSD::Handle::has_error; 129} 130 131sub smart_join 132{ 133 my $self = shift; 134 if (@_ <= 1) { 135 return join('+', @_); 136 } 137 my ($k, @stems); 138 for my $l (@_) { 139 my ($stem, @rest) = OpenBSD::PackageName::splitname($l); 140 my $k2 = join('-', @rest); 141 $k //= $k2; 142 if ($k2 ne $k) { 143 return join('+', sort @_); 144 } 145 push(@stems, $stem); 146 } 147 return join('+', sort @stems).'-'.$k; 148} 149 150sub print 151{ 152 my $self = shift; 153 return $self->smart_join($self->older_names); 154} 155 156sub todo_names 157{ 158 &older_names; 159} 160 161sub short_print 162{ 163 my $self = shift; 164 my $result = $self->smart_join($self->todo_names); 165 if (length $result > 30) { 166 return substr($result, 0, 27)."..."; 167 } else { 168 return $result; 169 } 170} 171 172sub real_set 173{ 174 my $set = shift; 175 while (defined $set->{merged}) { 176 $set = $set->{merged}; 177 } 178 return $set; 179} 180 181sub merge_set 182{ 183 my ($self, $set) = @_; 184 $self->add_older($set->older); 185 $set->mark_as_finished; 186 # XXX and mark it as merged, for eventual updates 187 $set->{merged} = $self; 188} 189 190# Merge several deletesets together 191sub merge 192{ 193 my ($self, $tracker, @sets) = @_; 194 195 # Apparently simple, just add the missing parts 196 for my $set (@sets) { 197 next if $set eq $self; 198 $self->merge_set($set); 199 $tracker->handle_set($set); 200 } 201 # then regen tracker info for $self 202 $tracker->todo($self); 203 return $self; 204} 205 206sub match_locations 207{ 208 return []; 209} 210 211OpenBSD::Auto::cache(solver, 212 sub { 213 require OpenBSD::Dependencies; 214 return OpenBSD::Dependencies::Solver->new(shift); 215 }); 216 217OpenBSD::Auto::cache(conflict_cache, 218 sub { 219 require OpenBSD::Dependencies; 220 return OpenBSD::ConflictCache->new; 221 }); 222 223package OpenBSD::UpdateSet; 224our @ISA = qw(OpenBSD::DeleteSet); 225 226sub new 227{ 228 my ($class, $state) = @_; 229 my $o = $class->SUPER::new($state); 230 $o->{newer} = {}; 231 $o->{kept} = {}; 232 $o->{repo} = $state->repo; 233 $o->{hints} = []; 234 $o->{updates} = 0; 235 return $o; 236} 237 238sub path 239{ 240 my $set = shift; 241 242 return $set->{path}; 243} 244 245sub add_repositories 246{ 247 my ($set, @repos) = @_; 248 249 if (!defined $set->{path}) { 250 $set->{path} = $set->{repo}->path; 251 } 252 $set->{path}->add(@repos); 253} 254 255sub merge_paths 256{ 257 my ($set, $other) = @_; 258 259 if (defined $other->path) { 260 if (!defined $set->path) { 261 $set->{path} = $other->path; 262 } elsif ($set->{path} ne $other->path) { 263 $set->add_path(@{$other->{path}}); 264 } 265 } 266} 267 268sub match_locations 269{ 270 my ($set, @spec) = @_; 271 my $r = []; 272 if (defined $set->{path}) { 273 $r = $set->{path}->match_locations(@spec); 274 } 275 if (@$r == 0) { 276 $r = $set->{repo}->match_locations(@spec); 277 } 278 return $r; 279} 280 281sub add_newer 282{ 283 my $self = shift; 284 for my $h (@_) { 285 $self->{newer}{$h->pkgname} = $h; 286 $self->{updates}++; 287 } 288 return $self; 289} 290 291sub add_kept 292{ 293 my $self = shift; 294 for my $h (@_) { 295 $self->{kept}->{$h->pkgname} = $h; 296 } 297 return $self; 298} 299 300sub move_kept 301{ 302 my $self = shift; 303 for my $h (@_) { 304 delete $self->{older}{$h->pkgname}; 305 delete $self->{newer}{$h->pkgname}; 306 $self->{kept}{$h->pkgname} = $h; 307 if (!defined $h->{location}) { 308 $h->{location} = 309 $self->{repo}->installed->find($h->pkgname); 310 } 311 $h->complete_dependency_info; 312 $h->{update_found} = $h; 313 } 314 return $self; 315} 316 317sub add_hints 318{ 319 my $self = shift; 320 for my $h (@_) { 321 push(@{$self->{hints}}, OpenBSD::hint->new($h)); 322 } 323 return $self; 324} 325 326sub add_hints2 327{ 328 my $self = shift; 329 for my $h (@_) { 330 push(@{$self->{hints}}, OpenBSD::hint2->new($h)); 331 } 332 return $self; 333} 334 335sub newer 336{ 337 my $self = shift; 338 return values %{$self->{newer}}; 339} 340 341sub kept 342{ 343 my $self = shift; 344 return values %{$self->{kept}}; 345} 346 347sub hints 348{ 349 my $self = shift; 350 return @{$self->{hints}}; 351} 352 353sub newer_names 354{ 355 my $self = shift; 356 return keys %{$self->{newer}}; 357} 358 359sub kept_names 360{ 361 my $self = shift; 362 return keys %{$self->{kept}}; 363} 364 365sub all_handles 366{ 367 my $self = shift; 368 return ($self->older, $self->newer, $self->kept); 369} 370 371sub changed_handles 372{ 373 my $self = shift; 374 return ($self->older, $self->newer); 375} 376 377sub hint_names 378{ 379 my $self = shift; 380 return map {$_->pkgname} $self->hints; 381} 382 383sub older_to_do 384{ 385 my $self = shift; 386 # XXX in `combined' updates, some dependencies may remove extra 387 # packages, so we do a double-take on the list of packages we 388 # are actually replacing... for now, until we merge update sets. 389 require OpenBSD::PackageInfo; 390 my @l = (); 391 for my $h ($self->older) { 392 if (OpenBSD::PackageInfo::is_installed($h->pkgname)) { 393 push(@l, $h); 394 } 395 } 396 return @l; 397} 398 399sub print 400{ 401 my $self = shift; 402 my $result = ""; 403 if ($self->kept > 0) { 404 $result = "[".$self->smart_join($self->kept_names)."]"; 405 } 406 my ($old, $new); 407 if ($self->older > 0) { 408 $old = $self->SUPER::print; 409 } 410 if ($self->newer > 0) { 411 $new = $self->smart_join($self->newer_names); 412 } 413 # XXX common case 414 if (defined $old && defined $new) { 415 my ($stema, @resta) = OpenBSD::PackageName::splitname($old); 416 my $resta = join('-', @resta); 417 my ($stemb, @restb) = OpenBSD::PackageName::splitname($new); 418 my $restb = join('-', @restb); 419 if ($stema eq $stemb && $resta !~ /\+/ && $restb !~ /\+/) { 420 return $result .$old."->".$restb; 421 } 422 } 423 424 if (defined $old) { 425 $result .= $old."->"; 426 } 427 if (defined $new) { 428 $result .= $new; 429 } elsif ($self->hints > 0) { 430 $result .= $self->smart_join($self->hint_names); 431 } 432 return $result; 433} 434 435sub todo_names 436{ 437 my $self = shift; 438 if ($self->newer > 0) { 439 return $self->newer_names; 440 } else { 441 return $self->kept_names; 442 } 443} 444 445sub validate_plists 446{ 447 my ($self, $state) = @_; 448 $state->{problems} = 0; 449 delete $state->{overflow}; 450 451 $state->{current_set} = $self; 452 453 for my $o ($self->older_to_do) { 454 require OpenBSD::Delete; 455 OpenBSD::Delete::validate_plist($o->{plist}, $state); 456 } 457 $state->{colliding} = []; 458 for my $n ($self->newer) { 459 require OpenBSD::Add; 460 OpenBSD::Add::validate_plist($n->{plist}, $state, $self); 461 } 462 if (@{$state->{colliding}} > 0) { 463 require OpenBSD::CollisionReport; 464 465 OpenBSD::CollisionReport::collision_report($state->{colliding}, $state, $self); 466 } 467 if (defined $state->{overflow}) { 468 $state->vstat->tally; 469 $state->vstat->drop_changes; 470 # nothing to try if we don't have existing stuff to remove 471 return 0 if $self->older == 0; 472 # we already tried the other way around... 473 return 0 if $state->{delete_first}; 474 if ($state->defines('deletefirst') || 475 $state->confirm_defaults_to_no( 476 "Delete older packages first")) { 477 # okay we recurse doing things the other way around 478 $state->{delete_first} = 1; 479 return $self->validate_plists($state); 480 } 481 } 482 if ($state->{problems}) { 483 $state->vstat->drop_changes; 484 return 0; 485 } else { 486 $state->vstat->synchronize; 487 return 1; 488 } 489} 490 491sub cleanup_old_shared 492{ 493 my ($set, $state) = @_; 494 my $h = $set->{old_shared}; 495 496 for my $d (sort {$b cmp $a} keys %$h) { 497 OpenBSD::SharedItems::wipe_directory($state, $h, $d) || 498 $state->fatal("Can't continue"); 499 delete $state->{recorder}{dirs}{$d}; 500 } 501} 502 503my @extra = qw(solver conflict_cache); 504sub mark_as_finished 505{ 506 my $self = shift; 507 for my $i (@extra, 'sha') { 508 delete $self->{$i}; 509 } 510 $self->SUPER::mark_as_finished; 511} 512 513sub merge_if_exists 514{ 515 my ($self, $k, @extra) = @_; 516 517 my @list = (); 518 for my $s (@extra) { 519 if ($s ne $self && defined $s->{$k}) { 520 push(@list, $s->{$k}); 521 } 522 } 523 $self->$k->merge(@list); 524} 525 526sub merge_set 527{ 528 my ($self, $set) = @_; 529 $self->SUPER::merge_set($set); 530 $self->add_newer($set->newer); 531 $self->add_kept($set->kept); 532 $self->merge_paths($set); 533 $self->{updates} += $set->{updates}; 534 $set->{updates} = 0; 535} 536 537# Merge several updatesets together 538sub merge 539{ 540 my ($self, $tracker, @sets) = @_; 541 542 for my $i (@extra) { 543 $self->merge_if_exists($i, @sets); 544 } 545 return $self->SUPER::merge($tracker, @sets); 546} 547 5481; 549