# ex:ts=8 sw=4: # $OpenBSD: UpdateSet.pm,v 1.85 2019/07/04 09:47:09 espie Exp $ # # Copyright (c) 2007-2010 Marc Espie # # Permission to use, copy, modify, and distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # an UpdateSet is a list of packages to remove/install. # it contains several things: # -> a list of older packages to remove (installed locations) # -> a list of newer packages to add (might be very simple locations) # -> a list of "hints", as package names to install # -> a list of packages that are kept throughout an update # every add/remove operations manipulate UpdateSet. # # Since older packages are always installed, they're organized as a hash. # # XXX: an UpdateSet succeeds or fails "together". # if several packages should be removed/added, then not being able # to do stuff on ONE of them is enough to invalidate the whole set. # # Normal UpdateSets contain one newer package at most. # Bigger UpdateSets can be created through the merge operation, which # will be used only when necessary. # # kept packages are needed after merges, where some dependencies may # not need updating, and to distinguish from old packages that will be # removed. # # for instance, package installation will check UpdateSets for internal # dependencies and for conflicts. For that to work, we need kept stuff # use strict; use warnings; # hints should behave like locations package OpenBSD::hint; sub new { my ($class, $name) = @_; bless {name => $name}, $class; } sub pkgname { return shift->{name}; } package OpenBSD::hint2; our @ISA = qw(OpenBSD::hint); package OpenBSD::DeleteSet; use OpenBSD::Error; sub new { my ($class, $state) = @_; return bless {older => {}}, $class; } sub add_older { my $self = shift; for my $h (@_) { $self->{older}{$h->pkgname} = $h; $h->{is_old} = 1; } return $self; } sub older { my $self = shift; return values %{$self->{older}}; } sub older_names { my $self = shift; return keys %{$self->{older}}; } sub all_handles { &older; } sub changed_handles { &older; } sub mark_as_finished { my $self = shift; $self->{finished} = 1; } sub cleanup { my ($self, $error, $errorinfo) = @_; for my $h ($self->all_handles) { $h->cleanup($error, $errorinfo); } if (defined $error) { $self->{error} //= $error; $self->{errorinfo} //= $errorinfo; } delete $self->{solver}; delete $self->{known_mandirs}; delete $self->{known_displays}; $self->mark_as_finished; } sub has_error { &OpenBSD::Handle::has_error; } sub smart_join { my $self = shift; if (@_ <= 1) { return join('+', @_); } my ($k, @stems); for my $l (@_) { my ($stem, @rest) = OpenBSD::PackageName::splitname($l); my $k2 = join('-', @rest); $k //= $k2; if ($k2 ne $k) { return join('+', sort @_); } push(@stems, $stem); } return join('+', sort @stems).'-'.$k; } sub print { my $self = shift; return $self->smart_join($self->older_names); } sub todo_names { &older_names; } sub short_print { my $self = shift; my $result = $self->smart_join($self->todo_names); if (length $result > 30) { return substr($result, 0, 27)."..."; } else { return $result; } } sub real_set { my $set = shift; while (defined $set->{merged}) { $set = $set->{merged}; } return $set; } sub merge_set { my ($self, $set) = @_; $self->add_older($set->older); $set->mark_as_finished; # XXX and mark it as merged, for eventual updates $set->{merged} = $self; } # Merge several deletesets together sub merge { my ($self, $tracker, @sets) = @_; # Apparently simple, just add the missing parts for my $set (@sets) { next if $set eq $self; $self->merge_set($set); $tracker->handle_set($set); } # then regen tracker info for $self $tracker->todo($self); return $self; } sub match_locations { return []; } OpenBSD::Auto::cache(solver, sub { require OpenBSD::Dependencies; return OpenBSD::Dependencies::Solver->new(shift); }); OpenBSD::Auto::cache(conflict_cache, sub { require OpenBSD::Dependencies; return OpenBSD::ConflictCache->new; }); package OpenBSD::UpdateSet; our @ISA = qw(OpenBSD::DeleteSet); sub new { my ($class, $state) = @_; my $o = $class->SUPER::new($state); $o->{newer} = {}; $o->{kept} = {}; $o->{repo} = $state->repo; $o->{hints} = []; $o->{updates} = 0; return $o; } sub path { my $set = shift; return $set->{path}; } sub add_repositories { my ($set, @repos) = @_; if (!defined $set->{path}) { $set->{path} = $set->{repo}->path; } $set->{path}->add(@repos); } sub merge_paths { my ($set, $other) = @_; if (defined $other->path) { if (!defined $set->path) { $set->{path} = $other->path; } elsif ($set->{path} ne $other->path) { $set->add_path(@{$other->{path}}); } } } sub match_locations { my ($set, @spec) = @_; my $r = []; if (defined $set->{path}) { $r = $set->{path}->match_locations(@spec); } if (@$r == 0) { $r = $set->{repo}->match_locations(@spec); } return $r; } sub add_newer { my $self = shift; for my $h (@_) { $self->{newer}{$h->pkgname} = $h; $self->{updates}++; } return $self; } sub add_kept { my $self = shift; for my $h (@_) { $self->{kept}->{$h->pkgname} = $h; } return $self; } sub move_kept { my $self = shift; for my $h (@_) { delete $self->{older}{$h->pkgname}; delete $self->{newer}{$h->pkgname}; $self->{kept}{$h->pkgname} = $h; if (!defined $h->{location}) { $h->{location} = $self->{repo}->installed->find($h->pkgname); } $h->complete_dependency_info; $h->{update_found} = $h; } return $self; } sub add_hints { my $self = shift; for my $h (@_) { push(@{$self->{hints}}, OpenBSD::hint->new($h)); } return $self; } sub add_hints2 { my $self = shift; for my $h (@_) { push(@{$self->{hints}}, OpenBSD::hint2->new($h)); } return $self; } sub newer { my $self = shift; return values %{$self->{newer}}; } sub kept { my $self = shift; return values %{$self->{kept}}; } sub hints { my $self = shift; return @{$self->{hints}}; } sub newer_names { my $self = shift; return keys %{$self->{newer}}; } sub kept_names { my $self = shift; return keys %{$self->{kept}}; } sub all_handles { my $self = shift; return ($self->older, $self->newer, $self->kept); } sub changed_handles { my $self = shift; return ($self->older, $self->newer); } sub hint_names { my $self = shift; return map {$_->pkgname} $self->hints; } sub older_to_do { my $self = shift; # XXX in `combined' updates, some dependencies may remove extra # packages, so we do a double-take on the list of packages we # are actually replacing... for now, until we merge update sets. require OpenBSD::PackageInfo; my @l = (); for my $h ($self->older) { if (OpenBSD::PackageInfo::is_installed($h->pkgname)) { push(@l, $h); } } return @l; } sub print { my $self = shift; my $result = ""; if ($self->kept > 0) { $result = "[".$self->smart_join($self->kept_names)."]"; } my ($old, $new); if ($self->older > 0) { $old = $self->SUPER::print; } if ($self->newer > 0) { $new = $self->smart_join($self->newer_names); } # XXX common case if (defined $old && defined $new) { my ($stema, @resta) = OpenBSD::PackageName::splitname($old); my $resta = join('-', @resta); my ($stemb, @restb) = OpenBSD::PackageName::splitname($new); my $restb = join('-', @restb); if ($stema eq $stemb && $resta !~ /\+/ && $restb !~ /\+/) { return $result .$old."->".$restb; } } if (defined $old) { $result .= $old."->"; } if (defined $new) { $result .= $new; } elsif ($self->hints > 0) { $result .= $self->smart_join($self->hint_names); } return $result; } sub todo_names { my $self = shift; if ($self->newer > 0) { return $self->newer_names; } else { return $self->kept_names; } } sub validate_plists { my ($self, $state) = @_; $state->{problems} = 0; delete $state->{overflow}; $state->{current_set} = $self; for my $o ($self->older_to_do) { require OpenBSD::Delete; OpenBSD::Delete::validate_plist($o->{plist}, $state); } $state->{colliding} = []; for my $n ($self->newer) { require OpenBSD::Add; OpenBSD::Add::validate_plist($n->{plist}, $state, $self); } if (@{$state->{colliding}} > 0) { require OpenBSD::CollisionReport; OpenBSD::CollisionReport::collision_report($state->{colliding}, $state, $self); } if (defined $state->{overflow}) { $state->vstat->tally; $state->vstat->drop_changes; # nothing to try if we don't have existing stuff to remove return 0 if $self->older == 0; # we already tried the other way around... return 0 if $state->{delete_first}; if ($state->defines('deletefirst') || $state->confirm_defaults_to_no( "Delete older packages first")) { # okay we recurse doing things the other way around $state->{delete_first} = 1; return $self->validate_plists($state); } } if ($state->{problems}) { $state->vstat->drop_changes; return 0; } else { $state->vstat->synchronize; return 1; } } sub cleanup_old_shared { my ($set, $state) = @_; my $h = $set->{old_shared}; for my $d (sort {$b cmp $a} keys %$h) { OpenBSD::SharedItems::wipe_directory($state, $h, $d) || $state->fatal("Can't continue"); delete $state->{recorder}{dirs}{$d}; } } my @extra = qw(solver conflict_cache); sub mark_as_finished { my $self = shift; for my $i (@extra, 'sha') { delete $self->{$i}; } $self->SUPER::mark_as_finished; } sub merge_if_exists { my ($self, $k, @extra) = @_; my @list = (); for my $s (@extra) { if ($s ne $self && defined $s->{$k}) { push(@list, $s->{$k}); } } $self->$k->merge(@list); } sub merge_set { my ($self, $set) = @_; $self->SUPER::merge_set($set); $self->add_newer($set->newer); $self->add_kept($set->kept); $self->merge_paths($set); $self->{updates} += $set->{updates}; $set->{updates} = 0; } # Merge several updatesets together sub merge { my ($self, $tracker, @sets) = @_; for my $i (@extra) { $self->merge_if_exists($i, @sets); } return $self->SUPER::merge($tracker, @sets); } 1;