1# ex:ts=8 sw=4: 2# $OpenBSD: State.pm,v 1.38 2016/09/14 14:14:22 espie Exp $ 3# 4# Copyright (c) 2007-2014 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 22package OpenBSD::Configuration; 23sub new 24{ 25 my ($class, $state) = @_; 26 my $self = bless {}, $class; 27 require OpenBSD::Paths; 28 $self->read_file(OpenBSD::Paths->pkgconf, $state); 29 return $self; 30} 31 32sub read_file 33{ 34 my ($self, $filename, $state) = @_; 35 open(my $fh, '<', $filename) or return; 36 while (<$fh>) { 37 chomp; 38 next if m/^\s*\#/; 39 next if m/^\s*$/; 40 my ($cmd, $k, $v, $add); 41 my $h = $self; 42 if (($cmd, $k, $add, $v) = m/^\s*(.*?)\.(.*?)\s*(\+?)\=\s*(.*)\s*$/) { 43 next unless $cmd eq $state->{cmd}; 44 my $h = $self->{cmd} = {}; 45 } elsif (($k, $add, $v) = m/^\s*(.*?)\s*(\+?)\=\s*(.*)\s*$/) { 46 } else { 47 # bad line: should we say so ? 48 $state->errsay("Bad line in #1: #2 (#3)", 49 $filename, $_, $.); 50 next; 51 } 52 # remove caps 53 $k =~ tr/A-Z/a-z/; 54 if ($add eq '') { 55 $h->{$k} = [$v]; 56 } else { 57 push(@{$h->{$k}}, $v); 58 } 59 } 60} 61 62sub ref 63{ 64 my ($self, $k) = @_; 65 if (defined $self->{cmd}{$k}) { 66 return $self->{cmd}{$k}; 67 } else { 68 return $self->{$k}; 69 } 70} 71 72sub value 73{ 74 my ($self, $k) = @_; 75 my $r = $self->ref($k); 76 if (!defined $r) { 77 return $r; 78 } 79 if (wantarray) { 80 return @$r; 81 } else { 82 return $r->[0]; 83 } 84} 85 86sub istrue 87{ 88 my ($self, $k) = @_; 89 my $v = $self->value($k); 90 if (defined $v && $v =~ /^yes$/i) { 91 return 1; 92 } else { 93 return 0; 94 } 95} 96 97package OpenBSD::PackageRepositoryFactory; 98sub new 99{ 100 my ($class, $state) = @_; 101 bless {state => $state}, $class; 102} 103 104sub installed 105{ 106 my ($self, $all) = @_; 107 require OpenBSD::PackageRepository::Installed; 108 109 return OpenBSD::PackageRepository::Installed->new($all, $self->{state}); 110} 111 112sub path_parse 113{ 114 my ($self, $pkgname) = @_; 115 require OpenBSD::PackageLocator; 116 117 return OpenBSD::PackageLocator->path_parse($pkgname, $self->{state}); 118} 119 120sub find 121{ 122 my ($self, $pkg) = @_; 123 require OpenBSD::PackageLocator; 124 125 return OpenBSD::PackageLocator->find($pkg, $self->{state}); 126} 127 128sub reinitialize 129{ 130} 131 132sub match_locations 133{ 134 my $self = shift; 135 require OpenBSD::PackageLocator; 136 137 return OpenBSD::PackageLocator->match_locations(@_, $self->{state}); 138} 139 140sub grabPlist 141{ 142 my ($self, $url, $code) = @_; 143 require OpenBSD::PackageLocator; 144 145 return OpenBSD::PackageLocator->grabPlist($url, $code, $self->{state}); 146} 147 148sub path 149{ 150 my $self = shift; 151 require OpenBSD::PackageRepositoryList; 152 153 return OpenBSD::PackageRepositoryList->new($self->{state}); 154} 155 156# common routines to everything state. 157# in particular, provides "singleton-like" access to UI. 158package OpenBSD::State; 159use Carp; 160use OpenBSD::Subst; 161use OpenBSD::Error; 162require Exporter; 163our @ISA = qw(Exporter); 164our @EXPORT = (); 165 166sub new 167{ 168 my $class = shift; 169 my $cmd = shift; 170 my $o = bless {cmd => $cmd}, $class; 171 $o->init(@_); 172 return $o; 173} 174 175sub init 176{ 177 my $self = shift; 178 $self->{subst} = OpenBSD::Subst->new; 179 $self->{repo} = OpenBSD::PackageRepositoryFactory->new($self); 180 $self->{export_level} = 1; 181} 182 183sub repo 184{ 185 my $self = shift; 186 return $self->{repo}; 187} 188 189sub sync_display 190{ 191} 192 193OpenBSD::Auto::cache(config, 194 sub { 195 return OpenBSD::Configuration->new(shift); 196 }); 197 198sub usage_is 199{ 200 my ($self, @usage) = @_; 201 $self->{usage} = \@usage; 202} 203 204sub verbose 205{ 206 my $self = shift; 207 return $self->{v}; 208} 209 210sub opt 211{ 212 my ($self, $k) = @_; 213 return $self->{opt}{$k}; 214} 215 216sub usage 217{ 218 my $self = shift; 219 my $code = 0; 220 if (@_) { 221 print STDERR "$self->{cmd}: ", $self->f(@_), "\n"; 222 $code = 1; 223 } 224 print STDERR "Usage: $self->{cmd} ", shift(@{$self->{usage}}), "\n"; 225 for my $l (@{$self->{usage}}) { 226 print STDERR " $l\n"; 227 } 228 exit($code); 229} 230 231sub f 232{ 233 my $self = shift; 234 if (@_ == 0) { 235 return undef; 236 } 237 my ($fmt, @l) = @_; 238 # make it so that #0 is # 239 unshift(@l, '#'); 240 $fmt =~ s,\#(\d+),($l[$1] // "<Undefined #$1>"),ge; 241 return $fmt; 242} 243 244sub _fatal 245{ 246 my $self = shift; 247 # implementation note: to print "fatal errors" elsewhere, 248 # the way is to eval { croak @_}; and decide what to do with $@. 249 delete $SIG{__DIE__}; 250 $self->sync_display; 251 croak "Fatal error: ", @_, "\n"; 252} 253 254sub fatal 255{ 256 my $self = shift; 257 $self->_fatal($self->f(@_)); 258} 259 260sub _fhprint 261{ 262 my $self = shift; 263 my $fh = shift; 264 $self->sync_display; 265 print $fh @_; 266} 267sub _print 268{ 269 my $self = shift; 270 $self->_fhprint(\*STDOUT, @_); 271} 272 273sub _errprint 274{ 275 my $self = shift; 276 $self->_fhprint(\*STDERR, @_); 277} 278 279sub fhprint 280{ 281 my $self = shift; 282 my $fh = shift; 283 $self->_fhprint($fh, $self->f(@_)); 284} 285 286sub fhsay 287{ 288 my $self = shift; 289 my $fh = shift; 290 if (@_ == 0) { 291 $self->_fhprint($fh, "\n"); 292 } else { 293 $self->_fhprint($fh, $self->f(@_), "\n"); 294 } 295} 296 297sub print 298{ 299 my $self = shift; 300 $self->fhprint(\*STDOUT, @_); 301} 302 303sub say 304{ 305 my $self = shift; 306 $self->fhsay(\*STDOUT, @_); 307} 308 309sub errprint 310{ 311 my $self = shift; 312 $self->fhprint(\*STDERR, @_); 313} 314 315sub errsay 316{ 317 my $self = shift; 318 $self->fhsay(\*STDERR, @_); 319} 320 321sub do_options 322{ 323 my ($state, $sub) = @_; 324 # this could be nicer... 325 326 try { 327 &$sub; 328 } catchall { 329 $state->usage("#1", $_); 330 }; 331} 332 333sub handle_options 334{ 335 my ($state, $opt_string, @usage) = @_; 336 require OpenBSD::Getopt; 337 338 $state->{opt}{v} = 0 unless $opt_string =~ m/v/; 339 $state->{opt}{h} = sub { $state->usage; } unless $opt_string =~ m/h/; 340 $state->{opt}{D} = sub { 341 $state->{subst}->parse_option(shift); 342 } unless $opt_string =~ m/D/; 343 $state->usage_is(@usage); 344 $state->do_options(sub { 345 OpenBSD::Getopt::getopts($opt_string.'hvD:', $state->{opt}); 346 }); 347 $state->{v} = $state->opt('v'); 348 349 # XXX switch not flipped 350 if ($state->defines('newsign')) { 351 $state->{signature_style} //= 'new'; 352 } elsif ($state->defines('unsigned')) { 353 $state->{signature_style} //= 'unsigned'; 354 } elsif ($state->defines('oldsign')) { 355 $state->{signature_style} //= 'old'; 356 } else { 357 $state->{signature_style} //= 'old'; 358 } 359 360 return if $state->{no_exports}; 361 # XXX 362 no strict "refs"; 363 no strict "vars"; 364 for my $k (keys %{$state->{opt}}) { 365 ${"opt_$k"} = $state->opt($k); 366 push(@EXPORT, "\$opt_$k"); 367 } 368 local $Exporter::ExportLevel = $state->{export_level}; 369 import OpenBSD::State; 370} 371 372sub defines 373{ 374 my ($self, $k) = @_; 375 return $self->{subst}->value($k); 376} 377 378sub width 379{ 380 my $self = shift; 381 if (!defined $self->{width}) { 382 $self->find_window_size; 383 } 384 return $self->{width}; 385} 386 387sub height 388{ 389 my $self = shift; 390 if (!defined $self->{height}) { 391 $self->find_window_size; 392 } 393 return $self->{height}; 394} 395 396sub find_window_size 397{ 398 my $self = shift; 399 require Term::ReadKey; 400 my @l = Term::ReadKey::GetTermSizeGWINSZ(\*STDOUT); 401 if (@l != 4) { 402 $self->{width} = 80; 403 $self->{height} = 24; 404 } else { 405 $self->{width} = $l[0]; 406 $self->{height} = $l[1]; 407 $SIG{'WINCH'} = sub { 408 $self->find_window_size; 409 }; 410 } 411 $SIG{'CONT'} = sub { 412 $self->find_window_size(1); 413 } 414} 415 416OpenBSD::Auto::cache(signer_list, 417 sub { 418 my $self = shift; 419 if ($self->defines('SIGNER')) { 420 return [split /,/, $self->{subst}->value('SIGNER')]; 421 } else { 422 if ($self->defines('FW_UPDATE')) { 423 return [qr{^.*fw$}]; 424 } else { 425 return [qr{^.*pkg$}]; 426 } 427 } 428 }); 429 430my @signal_name = (); 431sub fillup_names 432{ 433 { 434 # XXX force autoload 435 package verylocal; 436 437 require POSIX; 438 POSIX->import(qw(signal_h)); 439 } 440 441 for my $sym (keys %POSIX::) { 442 next unless $sym =~ /^SIG([A-Z].*)/; 443 $signal_name[eval "&POSIX::$sym()"] = $1; 444 } 445 # extra BSD signals 446 $signal_name[5] = 'TRAP'; 447 $signal_name[7] = 'IOT'; 448 $signal_name[10] = 'BUS'; 449 $signal_name[12] = 'SYS'; 450 $signal_name[16] = 'URG'; 451 $signal_name[23] = 'IO'; 452 $signal_name[24] = 'XCPU'; 453 $signal_name[25] = 'XFSZ'; 454 $signal_name[26] = 'VTALRM'; 455 $signal_name[27] = 'PROF'; 456 $signal_name[28] = 'WINCH'; 457 $signal_name[29] = 'INFO'; 458} 459 460sub find_signal 461{ 462 my $number = shift; 463 464 if (@signal_name == 0) { 465 fillup_names(); 466 } 467 468 return $signal_name[$number] || $number; 469} 470 471sub child_error 472{ 473 my $self = shift; 474 my $error = $?; 475 476 my $extra = ""; 477 478 if ($error & 128) { 479 $extra = $self->f(" (core dumped)"); 480 } 481 if ($error & 127) { 482 return $self->f("killed by signal #1#2", 483 find_signal($error & 127), $extra); 484 } else { 485 return $self->f("exit(#1)#2", ($error >> 8), $extra); 486 } 487} 488 489sub _system 490{ 491 my $self = shift; 492 $self->sync_display; 493 my $r = fork; 494 my ($todo, $todo2); 495 if (ref $_[0] eq 'CODE') { 496 $todo = shift; 497 } else { 498 $todo = sub {}; 499 } 500 if (ref $_[0] eq 'CODE') { 501 $todo2 = shift; 502 } else { 503 $todo2 = sub {}; 504 } 505 if (!defined $r) { 506 return 1; 507 } elsif ($r == 0) { 508 &$todo; 509 exec {$_[0]} @_ or return 1; 510 } else { 511 &$todo2; 512 waitpid($r, 0); 513 return $?; 514 } 515} 516 517sub system 518{ 519 my $self = shift; 520 my $r = $self->_system(@_); 521 if ($r != 0) { 522 if (ref $_[0] eq 'CODE') { 523 shift; 524 } 525 if (ref $_[0] eq 'CODE') { 526 shift; 527 } 528 $self->say("system(#1) failed: #2", 529 join(", ", @_), $self->child_error); 530 } 531 return $r; 532} 533 534sub verbose_system 535{ 536 my $self = shift; 537 my @p = @_; 538 if (ref $p[0]) { 539 shift @p; 540 } 541 if (ref $p[0]) { 542 shift @p; 543 } 544 545 $self->print("Running #1", join(' ', @p)); 546 my $r = $self->_system(@_); 547 if ($r != 0) { 548 $self->say("... failed: #1", $self->child_error); 549 } else { 550 $self->say; 551 } 552} 553 554sub copy_file 555{ 556 my $self = shift; 557 require File::Copy; 558 559 my $r = File::Copy::copy(@_); 560 if (!$r) { 561 $self->say("copy(#1) failed: #2", join(',', @_), $!); 562 } 563 return $r; 564} 565 566sub unlink 567{ 568 my $self = shift; 569 my $verbose = shift; 570 my $r = unlink @_; 571 if ($r != @_) { 572 $self->say("rm #1 failed: removed only #2 targets, #3", 573 join(' ', @_), $r, $!); 574 } elsif ($verbose) { 575 $self->say("rm #1", join(' ', @_)); 576 } 577 return $r; 578} 579 580sub copy 581{ 582 my $self = shift; 583 require File::Copy; 584 585 my $r = File::Copy::copy(@_); 586 if (!$r) { 587 $self->say("copy(#1) failed: #2", join(',', @_), $!); 588 } 589 return $r; 590} 591 5921; 593