1# ex:ts=8 sw=4: 2# $OpenBSD: AddDelete.pm,v 1.74 2016/06/15 15:40:13 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 19use strict; 20use warnings; 21 22# common framework, let's place most everything in there 23package main; 24our $not; 25 26package OpenBSD::AddDelete; 27use OpenBSD::Error; 28use OpenBSD::Paths; 29use OpenBSD::PackageInfo; 30use OpenBSD::AddCreateDelete; 31 32sub do_the_main_work 33{ 34 my ($self, $state) = @_; 35 36 if ($state->{bad}) { 37 return; 38 } 39 40 my $handler = sub { $state->fatal("Caught SIG#1", shift); }; 41 local $SIG{'INT'} = $handler; 42 local $SIG{'QUIT'} = $handler; 43 local $SIG{'HUP'} = $handler; 44 local $SIG{'KILL'} = $handler; 45 local $SIG{'TERM'} = $handler; 46 47 if ($state->defines('debug')) { 48 $self->main($state); 49 } else { 50 eval { $self->main($state); }; 51 } 52 my $dielater = $@; 53 return $dielater; 54} 55 56sub framework 57{ 58 my ($self, $state) = @_; 59 60 my $do = sub { 61 lock_db($state->{not}, $state) unless $state->defines('nolock'); 62 $state->check_root; 63 $self->process_parameters($state); 64 my $dielater = $self->do_the_main_work($state); 65 # cleanup various things 66 $state->{recorder}->cleanup($state); 67 $state->ldconfig->ensure; 68 OpenBSD::PackingElement->finish($state); 69 $state->progress->clear; 70 $state->log->dump; 71 $self->finish_display($state); 72 if ($state->verbose >= 2 || $state->{size_only} || 73 $state->defines('tally')) { 74 $state->vstat->tally; 75 } 76 $state->say("Extracted #1 from #2", 77 $state->{stats}{donesize}, 78 $state->{stats}{totsize}) 79 if defined $state->{stats} and $state->verbose; 80 # show any error, and show why we died... 81 rethrow $dielater; 82 }; 83 if ($state->defines('debug')) { 84 &$do; 85 } else { 86 try { 87 &$do; 88 } catch { 89 $state->errsay("#1: #2", $0, $_); 90 OpenBSD::Handler->reset; 91 if ($_ =~ m/^Caught SIG(\w+)/o) { 92 kill $1, $$; 93 } 94 $state->{bad}++; 95 }; 96 } 97 98} 99 100sub parse_and_run 101{ 102 my ($self, $cmd) = @_; 103 104 my $state = $self->new_state($cmd); 105 $state->handle_options; 106 local $SIG{'INFO'} = sub { $state->status->print($state); }; 107 108 $self->framework($state); 109 return $state->{bad} != 0; 110} 111 112# nothing to do 113sub tweak_list 114{ 115} 116 117sub process_setlist 118{ 119 my ($self, $state) = @_; 120 $state->tracker->todo(@{$state->{setlist}}); 121 # this is the actual very small loop that processes all sets 122 while (my $set = shift @{$state->{setlist}}) { 123 $state->status->what->set($set); 124 $set = $set->real_set; 125 next if $set->{finished}; 126 $state->progress->set_header('Checking packages'); 127 unshift(@{$state->{setlist}}, $self->process_set($set, $state)); 128 $self->tweak_list($state); 129 } 130} 131 132package OpenBSD::SharedItemsRecorder; 133sub new 134{ 135 my $class = shift; 136 return bless {}, $class; 137} 138 139sub is_empty 140{ 141 my $self = shift; 142 return !(defined $self->{dirs} or defined $self->{users} or 143 defined $self->{groups}); 144} 145 146sub cleanup 147{ 148 my ($self, $state) = @_; 149 return if $self->is_empty or $state->{not}; 150 151 require OpenBSD::SharedItems; 152 OpenBSD::SharedItems::cleanup($self, $state); 153} 154 155package OpenBSD::AddDelete::State; 156use OpenBSD::Vstat; 157use OpenBSD::Log; 158our @ISA = qw(OpenBSD::AddCreateDelete::State); 159 160sub handle_options 161{ 162 my ($state, $opt_string, @usage) = @_; 163 164 # backward compatibility 165 $state->{opt}{F} = sub { 166 for my $o (split /\,/o, shift) { 167 $state->{subst}->add($o, 1); 168 } 169 }; 170 $state->{no_exports} = 1; 171 $state->add_interactive_options; 172 $state->SUPER::handle_options($opt_string.'aciInqsB:F:', @usage); 173 174 if ($state->opt('s')) { 175 $state->{not} = 1; 176 } 177 # XXX RequiredBy 178 $main::not = $state->{not}; 179 $state->{localbase} = $state->opt('L') // OpenBSD::Paths->localbase; 180 $ENV{PATH} = join(':', 181 '/bin', 182 '/sbin', 183 '/usr/bin', 184 '/usr/sbin', 185 '/usr/X11R6/bin', 186 "$state->{localbase}/bin", 187 "$state->{localbase}/sbin"); 188 189 $state->{size_only} = $state->opt('s'); 190 $state->{quick} = $state->opt('q') || $state->config->istrue("nochecksum"); 191 $state->{extra} = $state->opt('c'); 192 $state->{automatic} = $state->opt('a') // 0; 193 $ENV{'PKG_DELETE_EXTRA'} = $state->{extra} ? "Yes" : "No"; 194 if ($state->{not}) { 195 $state->{loglevel} = 0; 196 } 197 $state->{loglevel} //= $state->config->value("loglevel") // 1; 198 if ($state->{loglevel}) { 199 require Sys::Syslog; 200 Sys::Syslog::openlog($state->{cmd}, "nofatal"); 201 } 202} 203 204sub init 205{ 206 my $self = shift; 207 $self->{l} = OpenBSD::Log->new($self); 208 $self->{vstat} = OpenBSD::Vstat->new($self); 209 $self->{status} = OpenBSD::Status->new; 210 $self->{recorder} = OpenBSD::SharedItemsRecorder->new; 211 $self->{v} = 0; 212 $self->SUPER::init(@_); 213 $self->{wantntogo} = $self->config->istrue("ntogo"); 214 $self->{export_level}++; 215} 216 217sub syslog 218{ 219 my $self = shift; 220 return unless $self->{loglevel}; 221 Sys::Syslog::syslog('info', $self->f(@_)); 222} 223 224sub ntodo 225{ 226 my ($state, $offset) = @_; 227 return $state->tracker->sets_todo($offset); 228} 229 230# one-level dependencies tree, for nicer printouts 231sub build_deptree 232{ 233 my ($state, $set, @deps) = @_; 234 235 if (defined $state->{deptree}->{$set}) { 236 $set = $state->{deptree}->{$set}; 237 } 238 for my $dep (@deps) { 239 $state->{deptree}->{$dep} = $set unless 240 defined $state->{deptree}->{$dep}; 241 } 242} 243 244sub deptree_header 245{ 246 my ($state, $pkg) = @_; 247 if (defined $state->{deptree}->{$pkg}) { 248 my $s = $state->{deptree}->{$pkg}->real_set; 249 if ($s eq $pkg) { 250 delete $state->{deptree}->{$pkg}; 251 } else { 252 return $s->short_print.':'; 253 } 254 } 255 return ''; 256} 257 258sub vstat 259{ 260 my $self = shift; 261 return $self->{vstat}; 262} 263 264sub log 265{ 266 my $self = shift; 267 if (@_ == 0) { 268 return $self->{l}; 269 } else { 270 $self->{l}->say(@_); 271 } 272} 273 274sub run_quirks 275{ 276 my ($state, $sub) = @_; 277 278 if (!exists $state->{quirks}) { 279 eval { 280 require OpenBSD::Quirks; 281 # interface version number. 282 $state->{quirks} = OpenBSD::Quirks->new(1); 283 }; 284 if ($@) { 285 my $show = $state->verbose >= 2; 286 if (!$show) { 287 my $l = $state->repo->installed->match_locations(OpenBSD::Search::Stem->new('quirks')); 288 $show = @$l > 0; 289 } 290 $state->errsay("Can't load quirk: #1", $@) if $show; 291 # XXX cache that this didn't work 292 $state->{quirks} = undef; 293 } 294 } 295 296 if (defined $state->{quirks}) { 297 eval { 298 &$sub($state->{quirks}); 299 }; 300 if ($@) { 301 $state->errsay("Bad quirk: #1", $@); 302 } 303 } 304} 305 306sub check_root 307{ 308 my $state = shift; 309 if ($< && !$state->defines('nonroot')) { 310 if ($state->{not}) { 311 $state->errsay("#1 should be run as root", 312 $state->{cmd}) if $state->verbose; 313 } else { 314 $state->fatal("#1 must be run as root", $state->{cmd}); 315 } 316 } 317} 318 319sub choose_location 320{ 321 my ($state, $name, $list, $is_quirks) = @_; 322 if (@$list == 0) { 323 if (!$is_quirks) { 324 $state->errsay("Can't find #1", $name); 325 $state->run_quirks( 326 sub { 327 my $quirks = shift; 328 $quirks->filter_obsolete([$name], $state); 329 }); 330 } 331 return undef; 332 } elsif (@$list == 1) { 333 return $list->[0]; 334 } 335 336 my %h = map {($_->name, $_)} @$list; 337 if ($state->is_interactive) { 338 $h{'<None>'} = undef; 339 $state->progress->clear; 340 my $result = $state->ask_list("Ambiguous: choose package for $name", sort keys %h); 341 return $h{$result}; 342 } else { 343 $state->errsay("Ambiguous: #1 could be #2", 344 $name, join(' ', keys %h)); 345 return undef; 346 } 347} 348 349sub status 350{ 351 my $self = shift; 352 353 return $self->{status}; 354} 355 356sub replacing 357{ 358 my $self = shift; 359 return $self->{replacing}; 360} 361 362OpenBSD::Auto::cache(ldconfig, 363 sub { 364 my $self = shift; 365 return OpenBSD::LdConfig->new($self); 366 }); 367 368# if we're not running as root, allow some stuff when not under /usr/local 369sub allow_nonroot 370{ 371 my ($state, $path) = @_; 372 return $state->defines('nonroot') && 373 $path !~ m,^\Q$state->{localbase}/\E,; 374} 375 376sub make_path 377{ 378 my ($state, $path, $fullname) = @_; 379 require File::Path; 380 if ($state->allow_nonroot($fullname)) { 381 eval { 382 File::Path::mkpath($path); 383 }; 384 } else { 385 File::Path::mkpath($path); 386 } 387} 388 389# this is responsible for running ldconfig when needed 390package OpenBSD::LdConfig; 391 392sub new 393{ 394 my ($class, $state) = @_; 395 bless { state => $state, todo => 0 }, $class; 396} 397 398# called once to figure out which directories are actually used 399sub init 400{ 401 my $self = shift; 402 my $state = $self->{state}; 403 my $destdir = $state->{destdir}; 404 405 $self->{ldconfig} = [OpenBSD::Paths->ldconfig]; 406 407 $self->{path} = {}; 408 if ($destdir ne '') { 409 unshift @{$self->{ldconfig}}, OpenBSD::Paths->chroot, '--', 410 $destdir; 411 } 412 open my $fh, "-|", @{$self->{ldconfig}}, "-r"; 413 if (defined $fh) { 414 while (<$fh>) { 415 if (m/^\s*search directories:\s*(.*?)\s*$/o) { 416 for my $d (split(/\:/o, $1)) { 417 $self->{path}{$d} = 1; 418 } 419 last; 420 } 421 } 422 close($fh); 423 } else { 424 $state->errsay("Can't find ldconfig"); 425 } 426} 427 428# called from libs to figure out whether ldconfig should be rerun 429sub mark_directory 430{ 431 my ($self, $name) = @_; 432 if (!defined $self->{path}) { 433 $self->init; 434 } 435 require File::Basename; 436 my $d = File::Basename::dirname($name); 437 if ($self->{path}{$d}) { 438 $self->{todo} = 1; 439 } 440} 441 442# call before running any command (or at end) to run ldconfig just in time 443sub ensure 444{ 445 my $self = shift; 446 if ($self->{todo}) { 447 my $state = $self->{state}; 448 $state->vsystem(@{$self->{ldconfig}}, "-R") 449 unless $state->{not}; 450 $self->{todo} = 0; 451 } 452} 453 454# the object that gets displayed during status updates 455package OpenBSD::Status; 456 457sub print 458{ 459 my ($self, $state) = @_; 460 461 my $what = $self->{what}; 462 $what //= "Processing"; 463 my $object; 464 if (defined $self->{object}) { 465 $object = $self->{object}; 466 } elsif (defined $self->{set}) { 467 $object = $self->{set}->print; 468 } else { 469 $object = "Parameters"; 470 } 471 472 $state->say($what." #1#2", $object, $state->ntogo_string); 473 if ($state->defines('carp')) { 474 require Carp; 475 Carp::cluck("currently here"); 476 } 477} 478 479sub set 480{ 481 my ($self, $set) = @_; 482 delete $self->{object}; 483 $self->{set} = $set; 484 return $self; 485} 486 487sub object 488{ 489 my ($self, $object) = @_; 490 delete $self->{set}; 491 $self->{object} = $object; 492 return $self; 493} 494 495sub what 496{ 497 my ($self, $what) = @_; 498 $self->{what} = $what; 499 return $self; 500} 501 502sub new 503{ 504 my $class = shift; 505 506 bless {}, $class; 507} 508 5091; 510