# ex:ts=8 sw=4: # $OpenBSD: AddDelete.pm,v 1.78 2017/07/01 12:23:22 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. # use strict; use warnings; # common framework, let's place most everything in there package main; our $not; package OpenBSD::AddDelete; use OpenBSD::Error; use OpenBSD::Paths; use OpenBSD::PackageInfo; use OpenBSD::AddCreateDelete; sub do_the_main_work { my ($self, $state) = @_; if ($state->{bad}) { return; } my $handler = sub { $state->fatal("Caught SIG#1", shift); }; local $SIG{'INT'} = $handler; local $SIG{'QUIT'} = $handler; local $SIG{'HUP'} = $handler; local $SIG{'KILL'} = $handler; local $SIG{'TERM'} = $handler; if ($state->defines('debug')) { $self->main($state); } else { eval { $self->main($state); }; } my $dielater = $@; return $dielater; } sub framework { my ($self, $state) = @_; my $do = sub { lock_db($state->{not}, $state) unless $state->defines('nolock'); $state->check_root; $self->process_parameters($state); my $dielater = $self->do_the_main_work($state); # cleanup various things $state->{recorder}->cleanup($state); $state->ldconfig->ensure; OpenBSD::PackingElement->finish($state); $state->progress->clear; $state->log->dump; $self->finish_display($state); if ($state->verbose >= 2 || $state->{size_only} || $state->defines('tally')) { $state->vstat->tally; } $state->say("Extracted #1 from #2", $state->{stats}{donesize}, $state->{stats}{totsize}) if defined $state->{stats} and $state->verbose; # show any error, and show why we died... rethrow $dielater; }; if ($state->defines('debug')) { &$do; } else { try { &$do; } catch { $state->errsay("#1: #2", $0, $_); OpenBSD::Handler->reset; if ($_ =~ m/^Caught SIG(\w+)/o) { kill $1, $$; } $state->{bad}++; }; } } sub parse_and_run { my ($self, $cmd) = @_; my $state = $self->new_state($cmd); $state->handle_options; require POSIX; my $termios = POSIX::Termios->new; my $lflag; if (defined $termios->getattr) { $lflag = $termios->getlflag; } if (defined $lflag) { my $NOKERNINFO = 0x02000000; # not defined in POSIX $termios->setlflag($lflag | $NOKERNINFO); $termios->setattr; } local $SIG{'INFO'} = sub { $state->status->print($state); }; $self->framework($state); if (defined $lflag) { $termios->setlflag($lflag); $termios->setattr; } return $state->{bad} != 0; } # nothing to do sub tweak_list { } sub process_setlist { my ($self, $state) = @_; $state->tracker->todo(@{$state->{setlist}}); # this is the actual very small loop that processes all sets while (my $set = shift @{$state->{setlist}}) { $state->status->what->set($set); $set = $set->real_set; next if $set->{finished}; $state->progress->set_header('Checking packages'); unshift(@{$state->{setlist}}, $self->process_set($set, $state)); $self->tweak_list($state); } } package OpenBSD::SharedItemsRecorder; sub new { my $class = shift; return bless {}, $class; } sub is_empty { my $self = shift; return !(defined $self->{dirs} or defined $self->{users} or defined $self->{groups}); } sub cleanup { my ($self, $state) = @_; return if $self->is_empty or $state->{not}; require OpenBSD::SharedItems; OpenBSD::SharedItems::cleanup($self, $state); } package OpenBSD::AddDelete::State; use OpenBSD::Vstat; use OpenBSD::Log; our @ISA = qw(OpenBSD::AddCreateDelete::State); sub handle_options { my ($state, $opt_string, @usage) = @_; $state->{extra_stats} = 0; $state->{opt}{V} = sub { $state->{extra_stats}++; }; $state->{no_exports} = 1; $state->add_interactive_options; $state->SUPER::handle_options($opt_string.'aciInqsVB:', @usage); if ($state->opt('s')) { $state->{not} = 1; } # XXX RequiredBy $main::not = $state->{not}; $state->{localbase} = $state->opt('L') // OpenBSD::Paths->localbase; $ENV{PATH} = join(':', '/bin', '/sbin', '/usr/bin', '/usr/sbin', '/usr/X11R6/bin', "$state->{localbase}/bin", "$state->{localbase}/sbin"); $state->{size_only} = $state->opt('s'); $state->{quick} = $state->opt('q'); $state->{extra} = $state->opt('c'); $state->{automatic} = $state->opt('a') // 0; $ENV{'PKG_DELETE_EXTRA'} = $state->{extra} ? "Yes" : "No"; if ($state->{not}) { $state->{loglevel} = 0; } $state->{loglevel} //= 1; if ($state->{loglevel}) { require Sys::Syslog; Sys::Syslog::openlog($state->{cmd}, "nofatal"); } $state->{wantntogo} = $state->{extra_stats}; if (defined $ENV{PKG_CHECKSUM}) { $state->{subst}->add('checksum', 1); } } sub init { my $self = shift; $self->{l} = OpenBSD::Log->new($self); $self->{vstat} = OpenBSD::Vstat->new($self); $self->{status} = OpenBSD::Status->new; $self->{recorder} = OpenBSD::SharedItemsRecorder->new; $self->{v} = 0; $self->SUPER::init(@_); $self->{export_level}++; } sub syslog { my $self = shift; return unless $self->{loglevel}; Sys::Syslog::syslog('info', $self->f(@_)); } sub ntodo { my ($state, $offset) = @_; return $state->tracker->sets_todo($offset); } # one-level dependencies tree, for nicer printouts sub build_deptree { my ($state, $set, @deps) = @_; if (defined $state->{deptree}->{$set}) { $set = $state->{deptree}->{$set}; } for my $dep (@deps) { $state->{deptree}->{$dep} = $set unless defined $state->{deptree}->{$dep}; } } sub deptree_header { my ($state, $pkg) = @_; if (defined $state->{deptree}->{$pkg}) { my $s = $state->{deptree}->{$pkg}->real_set; if ($s eq $pkg) { delete $state->{deptree}->{$pkg}; } else { return $s->short_print.':'; } } return ''; } sub vstat { my $self = shift; return $self->{vstat}; } sub log { my $self = shift; if (@_ == 0) { return $self->{l}; } else { $self->{l}->say(@_); } } sub run_quirks { my ($state, $sub) = @_; if (!exists $state->{quirks}) { eval { require OpenBSD::Quirks; # interface version number. $state->{quirks} = OpenBSD::Quirks->new(1); }; if ($@) { my $show = $state->verbose >= 2; if (!$show) { my $l = $state->repo->installed->match_locations(OpenBSD::Search::Stem->new('quirks')); $show = @$l > 0; } $state->errsay("Can't load quirk: #1", $@) if $show; # XXX cache that this didn't work $state->{quirks} = undef; } } if (defined $state->{quirks}) { eval { &$sub($state->{quirks}); }; if ($@) { $state->errsay("Bad quirk: #1", $@); } } } sub check_root { my $state = shift; if ($< && !$state->defines('nonroot')) { if ($state->{not}) { $state->errsay("#1 should be run as root", $state->{cmd}) if $state->verbose; } else { $state->fatal("#1 must be run as root", $state->{cmd}); } } } sub choose_location { my ($state, $name, $list, $is_quirks) = @_; if (@$list == 0) { if (!$is_quirks) { $state->errsay("Can't find #1", $name); $state->run_quirks( sub { my $quirks = shift; $quirks->filter_obsolete([$name], $state); }); } return undef; } elsif (@$list == 1) { return $list->[0]; } my %h = map {($_->name, $_)} @$list; if ($state->is_interactive) { $h{''} = undef; $state->progress->clear; my $result = $state->ask_list("Ambiguous: choose package for $name", sort keys %h); return $h{$result}; } else { $state->errsay("Ambiguous: #1 could be #2", $name, join(' ', keys %h)); return undef; } } sub status { my $self = shift; return $self->{status}; } sub replacing { my $self = shift; return $self->{replacing}; } OpenBSD::Auto::cache(ldconfig, sub { my $self = shift; return OpenBSD::LdConfig->new($self); }); # if we're not running as root, allow some stuff when not under /usr/local sub allow_nonroot { my ($state, $path) = @_; return $state->defines('nonroot') && $path !~ m,^\Q$state->{localbase}/\E,; } sub make_path { my ($state, $path, $fullname) = @_; require File::Path; if ($state->allow_nonroot($fullname)) { eval { File::Path::mkpath($path); }; } else { File::Path::mkpath($path); } } # this is responsible for running ldconfig when needed package OpenBSD::LdConfig; sub new { my ($class, $state) = @_; bless { state => $state, todo => 0 }, $class; } # called once to figure out which directories are actually used sub init { my $self = shift; my $state = $self->{state}; my $destdir = $state->{destdir}; $self->{ldconfig} = [OpenBSD::Paths->ldconfig]; $self->{path} = {}; if ($destdir ne '') { unshift @{$self->{ldconfig}}, OpenBSD::Paths->chroot, '--', $destdir; } open my $fh, "-|", @{$self->{ldconfig}}, "-r"; if (defined $fh) { while (<$fh>) { if (m/^\s*search directories:\s*(.*?)\s*$/o) { for my $d (split(/\:/o, $1)) { $self->{path}{$d} = 1; } last; } } close($fh); } else { $state->errsay("Can't find ldconfig"); } } # called from libs to figure out whether ldconfig should be rerun sub mark_directory { my ($self, $name) = @_; if (!defined $self->{path}) { $self->init; } require File::Basename; my $d = File::Basename::dirname($name); if ($self->{path}{$d}) { $self->{todo} = 1; } } # call before running any command (or at end) to run ldconfig just in time sub ensure { my $self = shift; if ($self->{todo}) { my $state = $self->{state}; $state->vsystem(@{$self->{ldconfig}}, "-R") unless $state->{not}; $self->{todo} = 0; } } # the object that gets displayed during status updates package OpenBSD::Status; sub print { my ($self, $state) = @_; my $what = $self->{what}; $what //= "Processing"; my $object; if (defined $self->{object}) { $object = $self->{object}; } elsif (defined $self->{set}) { $object = $self->{set}->print; } else { $object = "Parameters"; } $state->say($what." #1#2", $object, $state->ntogo_string); if ($state->defines('carp')) { require Carp; Carp::cluck("currently here"); } } sub set { my ($self, $set) = @_; delete $self->{object}; $self->{set} = $set; return $self; } sub object { my ($self, $object) = @_; delete $self->{set}; $self->{object} = $object; return $self; } sub what { my ($self, $what) = @_; $self->{what} = $what; return $self; } sub new { my $class = shift; bless {}, $class; } 1;