1#! /usr/bin/perl 2# ex:ts=8 sw=4: 3# $OpenBSD: PkgCreate.pm,v 1.122 2016/09/06 10:41:51 espie Exp $ 4# 5# Copyright (c) 2003-2014 Marc Espie <espie@openbsd.org> 6# 7# Permission to use, copy, modify, and distribute this software for any 8# purpose with or without fee is hereby granted, provided that the above 9# copyright notice and this permission notice appear in all copies. 10# 11# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 19use strict; 20use warnings; 21 22use OpenBSD::AddCreateDelete; 23use OpenBSD::Dependencies; 24use OpenBSD::SharedLibs; 25use OpenBSD::Signer; 26 27package OpenBSD::PkgCreate::State; 28our @ISA = qw(OpenBSD::CreateSign::State); 29 30sub init 31{ 32 my $self = shift; 33 34 $self->{stash} = {}; 35 $self->SUPER::init(@_); 36 $self->{simple_status} = 0; 37} 38 39sub stash 40{ 41 my ($self, $key) = @_; 42 return $self->{stash}{$key}; 43} 44 45sub error 46{ 47 my $self = shift; 48 my $msg = shift; 49 $self->{bad}++; 50 $self->progress->disable; 51 $self->errsay("Error: $msg", @_); 52} 53 54sub set_status 55{ 56 my ($self, $status) = @_; 57 if ($self->{simple_status}) { 58 print "\n$status"; 59 } else { 60 if ($self->progress->set_header($status)) { 61 $self->progress->message(''); 62 } else { 63 $| = 1; 64 print "$status..."; 65 $self->{simple_status} = 1; 66 } 67 } 68} 69 70sub end_status 71{ 72 my $self = shift; 73 74 if ($self->{simple_status}) { 75 print "\n"; 76 } else { 77 $self->progress->clear; 78 } 79} 80 81sub handle_options 82{ 83 my $state = shift; 84 85 $state->{opt} = { 86 'f' => 87 sub { 88 push(@{$state->{contents}}, shift); 89 }, 90 'p' => 91 sub { 92 $state->{prefix} = shift; 93 }, 94 'P' => sub { 95 my $d = shift; 96 $state->{dependencies}{$d} = 1; 97 }, 98 'W' => sub { 99 my $w = shift; 100 $state->{wantlib}{$w} = 1; 101 }, 102 }; 103 $state->{no_exports} = 1; 104 $state->SUPER::handle_options('p:f:d:M:U:A:B:P:W:qQ', 105 '[-nQqvx] [-A arches] [-B pkg-destdir] [-D name[=value]]', 106 '[-L localbase] [-M displayfile] [-P pkg-dependency]', 107 '[-U undisplayfile] [-W wantedlib]', 108 '[-d desc -D COMMENT=value -f packinglist -p prefix]', 109 'pkg-name'); 110 111 my $base = '/'; 112 if (defined $state->opt('B')) { 113 $base = $state->opt('B'); 114 } 115 116 $state->{base} = $base; 117 118} 119 120package OpenBSD::PkgCreate; 121 122use OpenBSD::PackingList; 123use OpenBSD::PackageInfo; 124use OpenBSD::Getopt; 125use OpenBSD::Temp; 126use OpenBSD::Error; 127use OpenBSD::Ustar; 128use OpenBSD::ArcCheck; 129use OpenBSD::Paths; 130use File::Basename; 131 132# Extra stuff needed to archive files 133package OpenBSD::PackingElement; 134sub create_package 135{ 136 my ($self, $state) = @_; 137 138 $self->archive($state); 139 if ($state->verbose) { 140 $self->comment_create_package($state); 141 } 142} 143 144sub pretend_to_archive 145{ 146 my ($self, $state) = @_; 147 $self->comment_create_package($state); 148} 149 150sub record_digest {} 151sub archive {} 152sub really_archived { 0 } 153sub comment_create_package {} 154sub grab_manpages {} 155 156sub print_file {} 157 158sub avert_duplicates_and_other_checks 159{ 160 my ($self, $state) = @_; 161 return unless $self->NoDuplicateNames; 162 my $n = $self->fullname; 163 if (defined $state->stash($n)) { 164 $state->error("duplicate item in packing-list #1", $n); 165 } 166 $state->{stash}{$n} = 1; 167} 168 169sub makesum_plist 170{ 171 my ($self, $state, $plist) = @_; 172 $self->add_object($plist); 173} 174 175sub verify_checksum 176{ 177} 178 179sub register_forbidden 180{ 181 my ($self, $state) = @_; 182 if ($self->is_forbidden) { 183 push(@{$state->{forbidden}}, $self); 184 } 185} 186 187sub is_forbidden() { 0 } 188sub resolve_link 189{ 190 my ($filename, $base, $level) = @_; 191 $level //= 0; 192 if (-l $filename) { 193 my $l = readlink($filename); 194 if ($level++ > 14) { 195 return undef; 196 } 197 if ($l =~ m|^/|) { 198 return $base.resolve_link($l, $base, $level); 199 } else { 200 return resolve_link(File::Spec->catfile(File::Basename::dirname($filename),$l), $base, $level); 201 } 202 } else { 203 return $filename; 204 } 205} 206 207sub compute_checksum 208{ 209 my ($self, $result, $state, $base) = @_; 210 my $name = $self->fullname; 211 my $fname = $name; 212 if (defined $base) { 213 $fname = $base.$fname; 214 } 215 for my $field (qw(symlink link size ts)) { # md5 216 if (defined $result->{$field}) { 217 $state->error("User tried to define @#1 for #2", 218 $field, $fname); 219 } 220 } 221 if (defined $self->{wtempname}) { 222 $fname = $self->{wtempname}; 223 } 224 if (-l $fname) { 225 if (!defined $base) { 226 $state->error("special file #1 can't be a symlink", 227 $self->stringize); 228 } 229 my $value = readlink $fname; 230 my $chk = resolve_link($fname, $base); 231 $fname =~ s|^//|/|; # cosmetic 232 if (!defined $chk) { 233 $state->error("bogus symlink: #1 (too deep)", $fname); 234 } elsif (!-e $chk) { 235 push(@{$state->{bad_symlinks}{$chk}}, $fname); 236 } 237 $result->make_symlink($value); 238 } elsif (-f _) { 239 my ($dev, $ino, $size, $mtime) = (stat _)[0,1,7, 9]; 240 # XXX when rebuilding packages, tied updates can produce 241 # spurious hardlinks. We also refer to the installed plist 242 # we're rebuilding to know if we must checksum. 243 if (defined $state->stash("$dev/$ino") && !defined $self->{d}) { 244 $result->make_hardlink($state->stash("$dev/$ino")); 245 } else { 246 $state->{stash}{"$dev/$ino"} = $name; 247 $result->add_digest($self->compute_digest($fname)) 248 unless $state->{bad}; 249 $result->add_size($size); 250 $result->add_timestamp($mtime); 251 } 252 } elsif (-d _) { 253 $state->error("#1 should be a file and not a directory", $fname); 254 } else { 255 $state->error("#1 does not exist", $fname); 256 } 257} 258 259sub makesum_plist_with_base 260{ 261 my ($self, $plist, $state, $base) = @_; 262 $self->compute_checksum($self, $state, $base); 263 $self->add_object($plist); 264} 265 266sub verify_checksum_with_base 267{ 268 my ($self, $state, $base) = @_; 269 my $check = ref($self)->new($self->name); 270 $self->compute_checksum($check, $state, $base); 271 272 for my $field (qw(symlink link size)) { # md5 273 if ((defined $check->{$field} && defined $self->{$field} && 274 $check->{$field} ne $self->{$field}) || 275 (defined $check->{$field} xor defined $self->{$field})) { 276 $state->error("#1 inconsistency for #2", 277 $field, $self->fullname); 278 } 279 } 280 if ((defined $check->{d} && defined $self->{d} && 281 !$check->{d}->equals($self->{d})) || 282 (defined $check->{d} xor defined $self->{d})) { 283 $state->error("checksum inconsistency for #1", 284 $self->fullname); 285 } 286} 287 288 289sub prepare_for_archival 290{ 291 my ($self, $state) = @_; 292 293 my $o = $state->{archive}->prepare_long($self); 294 if (!$o->verify_modes($self)) { 295 $state->error("modes don't match for #1", $self->fullname); 296 } 297 if (!$o->is_allowed) { 298 $state->error("can't package #1", $self->fullname); 299 } 300 return $o; 301} 302 303sub discover_directories 304{ 305} 306 307sub check_version 308{ 309} 310 311package OpenBSD::PackingElement::StreamMarker; 312our @ISA = qw(OpenBSD::PackingElement::Meta); 313sub new 314{ 315 my $class = shift; 316 bless {}, $class; 317} 318 319sub comment_create_package 320{ 321 my ($self, $state) = @_; 322 $self->SUPER::comment_create_package($state); 323 $state->say("Gzip: next chunk"); 324} 325 326sub archive 327{ 328 my ($self, $state) = @_; 329 $state->new_gstream; 330} 331 332package OpenBSD::PackingElement::Meta; 333sub record_digest 334{ 335 my ($self, $original, $entries, $new, $tail) = @_; 336 push(@$new, $self); 337} 338 339package OpenBSD::PackingElement::RcScript; 340sub set_destdir 341{ 342 my ($self, $state) = @_; 343 if ($self->name =~ m/^\//) { 344 $state->{archive}->destdir($state->{base}); 345 } else { 346 $self->SUPER::set_destdir($state); 347 } 348} 349 350package OpenBSD::PackingElement::SpecialFile; 351sub archive 352{ 353 &OpenBSD::PackingElement::FileBase::archive; 354} 355 356sub pretend_to_archive 357{ 358 &OpenBSD::PackingElement::FileBase::pretend_to_archive; 359} 360 361sub set_destdir 362{ 363} 364 365sub may_add 366{ 367 my ($class, $subst, $plist, $opt) = @_; 368 if (defined $opt) { 369 my $o = $class->add($plist); 370 $subst->copy($opt, $o->fullname) if defined $o->fullname; 371 } 372} 373 374sub comment_create_package 375{ 376 my ($self, $state) = @_; 377 $state->say("Adding #1", $self->name); 378} 379 380sub makesum_plist 381{ 382 my ($self, $state, $plist) = @_; 383 $self->makesum_plist_with_base($plist, $state, undef); 384} 385 386sub verify_checksum 387{ 388 my ($self, $state) = @_; 389 $self->verify_checksum_with_base($state, undef); 390} 391 392sub prepare_for_archival 393{ 394 my ($self, $state) = @_; 395 396 my $o = $state->{archive}->prepare_long($self); 397 $o->{uname} = 'root'; 398 $o->{gname} = 'wheel'; 399 $o->{uid} = 0; 400 $o->{gid} = 0; 401 $o->{mode} &= 0555; # zap all write and suid modes 402 return $o; 403} 404 405sub forbidden() { 1 } 406 407# override for CONTENTS: we cannot checksum this. 408package OpenBSD::PackingElement::FCONTENTS; 409sub makesum_plist 410{ 411} 412 413sub verify_checksum 414{ 415} 416 417sub archive 418{ 419 my ($self, $state) = @_; 420 $self->SUPER::archive($state); 421 $state->new_gstream; 422} 423 424sub comment_create_package 425{ 426 my ($self, $state) = @_; 427 $self->SUPER::comment_create_package($state); 428 $state->say("GZIP: END OF SIGNATURE CHUNK"); 429} 430 431package OpenBSD::PackingElement::Cwd; 432sub archive 433{ 434 my ($self, $state) = @_; 435} 436 437sub pretend_to_archive 438{ 439 my ($self, $state) = @_; 440 $self->comment_create_package($state); 441} 442 443sub comment_create_package 444{ 445 my ($self, $state) = @_; 446 $state->say("Cwd: #1", $self->name); 447} 448 449package OpenBSD::PackingElement::FileBase; 450 451sub record_digest 452{ 453 my ($self, $original, $entries, $new, $tail) = @_; 454 if (defined $self->{d}) { 455 my $k = $self->{d}->stringize; 456 push(@{$entries->{$k}}, $self); 457 push(@$original, $k); 458 } else { 459 push(@$tail, $self); 460 } 461} 462 463sub set_destdir 464{ 465 my ($self, $state) = @_; 466 467 $state->{archive}->destdir($state->{base}."/".$self->cwd); 468} 469 470sub archive 471{ 472 my ($self, $state) = @_; 473 474 $self->set_destdir($state); 475 my $o = $self->prepare_for_archival($state); 476 477 $o->write unless $state->{bad}; 478} 479 480sub really_archived { 1 } 481sub pretend_to_archive 482{ 483 my ($self, $state) = @_; 484 485 $self->set_destdir($state); 486 $self->prepare_for_archival($state); 487 $self->comment_create_package($state); 488} 489 490sub comment_create_package 491{ 492 my ($self, $state) = @_; 493 $state->say("Adding #1", $self->name); 494} 495 496sub print_file 497{ 498 my ($item) = @_; 499 print '@', $item->keyword, " ", $item->fullname, "\n"; 500} 501 502sub makesum_plist 503{ 504 my ($self, $state, $plist) = @_; 505 $self->makesum_plist_with_base($plist, $state, $state->{base}); 506} 507 508sub verify_checksum 509{ 510 my ($self, $state) = @_; 511 $self->verify_checksum_with_base($state, $state->{base}); 512} 513 514package OpenBSD::PackingElement::Dir; 515sub discover_directories 516{ 517 my ($self, $state) = @_; 518 $state->{known_dirs}->{$self->fullname} = 1; 519} 520 521package OpenBSD::PackingElement::InfoFile; 522sub makesum_plist 523{ 524 my ($self, $state, $plist) = @_; 525 $self->SUPER::makesum_plist($state, $plist); 526 my $fname = $self->fullname; 527 for (my $i = 1; ; $i++) { 528 if (-e "$state->{base}/$fname-$i") { 529 my $e = OpenBSD::PackingElement::File->add($plist, $self->name."-".$i); 530 $e->compute_checksum($e, $state, $state->{base}); 531 } else { 532 last; 533 } 534 } 535} 536 537package OpenBSD::PackingElement::Manpage; 538use File::Basename; 539 540sub grab_manpages 541{ 542 my ($self, $state) = @_; 543 my $filename; 544 if ($self->{wtempname}) { 545 $filename = $self->{wtempname}; 546 } else { 547 $filename = $state->{base}.$self->fullname; 548 } 549 push(@{$state->{manpages}}, $filename); 550} 551 552sub makesum_plist 553{ 554 my ($self, $state, $plist) = @_; 555 if ($state->{subst}->empty("USE_GROFF") || !$self->is_source) { 556 return $self->SUPER::makesum_plist($state, $plist); 557 } 558 my $dest = $self->source_to_dest; 559 my $fullname = $self->cwd."/".$dest; 560 my $d = dirname($fullname); 561 $state->{mandir} //= OpenBSD::Temp::permanent_dir( 562 $ENV{TMPDIR} // '/tmp', "manpage"); 563 my $tempname = $state->{mandir}."/".$fullname; 564 require File::Path; 565 File::Path::make_path($state->{mandir}."/".$d); 566 open my $fh, ">", $tempname or $state->error("can't create #1: #2", 567 $tempname, $!); 568 chmod 0444, $fh; 569 if (-d $state->{base}.$d) { 570 undef $d; 571 } 572 $self->format($state, $tempname, $fh) or return; 573 if (-z $tempname) { 574 $state->errsay("groff produced empty result for #1", $dest); 575 $state->errsay("\tkeeping source manpage"); 576 return $self->SUPER::makesum_plist($state, $plist); 577 } 578 if (defined $d && !$state->{known_dirs}->{$d}) { 579 $state->{known_dirs}->{$d} = 1; 580 OpenBSD::PackingElement::Dir->add($plist, dirname($dest)); 581 } 582 my $e = OpenBSD::PackingElement::Manpage->add($plist, $dest); 583 $e->{wtempname} = $tempname; 584 $e->compute_checksum($e, $state, $state->{base}); 585} 586 587package OpenBSD::PackingElement::Depend; 588sub avert_duplicates_and_other_checks 589{ 590 my ($self, $state) = @_; 591 if (!$self->spec->is_valid) { 592 $state->error("invalid \@#1 #2 in packing-list", 593 $self->keyword, $self->stringize); 594 } 595 $self->SUPER::avert_duplicates_and_other_checks($state); 596} 597 598sub forbidden() { 1 } 599 600package OpenBSD::PackingElement::Conflict; 601sub avert_duplicates_and_other_checks 602{ 603 $_[1]->{has_conflict}++; 604 &OpenBSD::PackingElement::Depend::avert_duplicates_and_other_checks; 605} 606 607package OpenBSD::PackingElement::AskUpdate; 608sub avert_duplicates_and_other_checks 609{ 610 &OpenBSD::PackingElement::Depend::avert_duplicates_and_other_checks; 611} 612 613package OpenBSD::PackingElement::Dependency; 614sub avert_duplicates_and_other_checks 615{ 616 my ($self, $state) = @_; 617 618 $self->SUPER::avert_duplicates_and_other_checks($state); 619 620 my @issues = OpenBSD::PackageName->from_string($self->{def})->has_issues; 621 if (@issues > 0) { 622 $state->error("\@#1 #2\n #3, #4", 623 $self->keyword, $self->stringize, 624 $self->{def}, join(' ', @issues)); 625 } elsif ($self->spec->is_valid) { 626 my @m = $self->spec->filter($self->{def}); 627 if (@m == 0) { 628 $state->error("\@#1 #2\n pattern #3 doesn't match default #4\n", 629 $self->keyword, $self->stringize, 630 $self->{pattern}, $self->{def}); 631 } 632 } 633} 634 635package OpenBSD::PackingElement::Name; 636sub avert_duplicates_and_other_checks 637{ 638 my ($self, $state) = @_; 639 640 my @issues = OpenBSD::PackageName->from_string($self->name)->has_issues; 641 if (@issues > 0) { 642 $state->error("bad package name #1: ", $self->name, 643 join(' ', @issues)); 644 } 645 $self->SUPER::avert_duplicates_and_other_checks($state); 646} 647 648sub forbidden() { 1 } 649 650package OpenBSD::PackingElement::NoDefaultConflict; 651sub avert_duplicates_and_other_checks 652{ 653 my ($self, $state) = @_; 654 $state->{has_no_default_conflict}++; 655} 656 657 658package OpenBSD::PackingElement::Lib; 659sub check_version 660{ 661 my ($self, $state, $unsubst) = @_; 662 my @l = $self->parse($self->name); 663 if (defined $l[0]) { 664 if (!$unsubst =~ m/\$\{LIB$l[0]_VERSION\}/) { 665 $state->error("Incorrectly versioned shared library: #1", $unsubst); 666 } 667 } else { 668 $state->error("Invalid shared library #1", $unsubst); 669 } 670 $state->{has_libraries} = 1; 671} 672 673package OpenBSD::PackingElement::DigitalSignature; 674sub is_forbidden() { 1 } 675 676package OpenBSD::PackingElement::Signer; 677sub is_forbidden() { 1 } 678 679package OpenBSD::PackingElement::ExtraInfo; 680sub is_forbidden() { 1 } 681 682package OpenBSD::PackingElement::ManualInstallation; 683sub is_forbidden() { 1 } 684 685package OpenBSD::PackingElement::Firmware; 686sub is_forbidden() { 1 } 687 688package OpenBSD::PackingElement::Url; 689sub is_forbidden() { 1 } 690 691package OpenBSD::PackingElement::Arch; 692sub is_forbidden() { 1 } 693 694package OpenBSD::PackingElement::LocalBase; 695sub is_forbidden() { 1 } 696 697package OpenBSD::PackingElement::Fragment; 698our @ISA=qw(OpenBSD::PackingElement); 699 700sub needs_keyword() { 0 } 701 702sub stringize 703{ 704 return '%%'.shift->{name}.'%%'; 705} 706 707package OpenBSD::PackingElement::NoFragment; 708our @ISA=qw(OpenBSD::PackingElement::Fragment); 709sub stringize 710{ 711 return '!%%'.shift->{name}.'%%'; 712} 713 714# put together file and filename, in order to handle fragments simply 715package MyFile; 716sub new 717{ 718 my ($class, $filename) = @_; 719 720 open(my $fh, '<', $filename) or die "Missing file $filename"; 721 722 bless { fh => $fh, name => $filename }, (ref($class) || $class); 723} 724 725sub readline 726{ 727 my $self = shift; 728 return readline $self->{fh}; 729} 730 731sub name 732{ 733 my $self = shift; 734 return $self->{name}; 735} 736 737sub close 738{ 739 my $self = shift; 740 close($self->{fh}); 741} 742 743sub deduce_name 744{ 745 my ($self, $frag, $not) = @_; 746 747 my $o = $self->name; 748 my $noto = $o; 749 my $nofrag = "no-$frag"; 750 751 $o =~ s/PFRAG\./PFRAG.$frag-/o or 752 $o =~ s/PLIST/PFRAG.$frag/o; 753 754 $noto =~ s/PFRAG\./PFRAG.no-$frag-/o or 755 $noto =~ s/PLIST/PFRAG.no-$frag/o; 756 unless (-e $o or -e $noto) { 757 die "Missing fragments for $frag: $o and $noto don't exist"; 758 } 759 if ($not) { 760 return $noto if -e $noto; 761 } else { 762 return $o if -e $o; 763 } 764 return; 765} 766 767# special solver class for PkgCreate 768package OpenBSD::Dependencies::CreateSolver; 769our @ISA = qw(OpenBSD::Dependencies::SolverBase); 770 771# we need to "hack" a special set 772sub new 773{ 774 my ($class, $plist) = @_; 775 bless { set => OpenBSD::PseudoSet->new($plist), bad => [] }, $class; 776} 777 778sub solve_all_depends 779{ 780 my ($solver, $state) = @_; 781 782 while (1) { 783 my @todo = $solver->solve_depends($state); 784 if (@todo == 0) { 785 return; 786 } 787 if ($solver->solve_wantlibs($state, 0)) { 788 return; 789 } 790 $solver->{set}->add_new(@todo); 791 } 792} 793 794sub solve_wantlibs 795{ 796 my ($solver, $state, $final) = @_; 797 798 my $okay = 1; 799 my $lib_finder = OpenBSD::lookup::library->new($solver); 800 my $h = $solver->{set}->{new}[0]; 801 for my $lib (@{$h->{plist}->{wantlib}}) { 802 $solver->{localbase} = $h->{plist}->localbase; 803 next if $lib_finder->lookup($solver, 804 $solver->{to_register}->{$h}, $state, 805 $lib->spec); 806 $okay = 0; 807 OpenBSD::SharedLibs::report_problem($state, 808 $lib->spec) if $final; 809 } 810 if (!$okay && $final) { 811 $solver->dump($state); 812 $lib_finder->dump($state); 813 } 814 return $okay; 815} 816 817sub really_solve_dependency 818{ 819 my ($self, $state, $dep, $package) = @_; 820 821 $state->progress->message($dep->{pkgpath}); 822 823 # look in installed packages 824 my $v = $self->find_dep_in_installed($state, $dep); 825 if (!defined $v) { 826 $v = $self->find_dep_in_self($state, $dep); 827 } 828 829 # and in portstree otherwise 830 if (!defined $v) { 831 $v = $self->solve_from_ports($state, $dep, $package); 832 } 833 return $v; 834} 835 836sub diskcachename 837{ 838 my ($self, $dep) = @_; 839 840 if ($ENV{_DEPENDS_CACHE}) { 841 my $diskcache = $dep->{pkgpath}; 842 $diskcache =~ s/\//--/g; 843 return $ENV{_DEPENDS_CACHE}."/pkgcreate-".$diskcache; 844 } else { 845 return undef; 846 } 847} 848 849sub to_cache 850{ 851 my ($self, $plist, $final) = @_; 852 # try to cache atomically. 853 # no error if it doesn't work 854 require OpenBSD::MkTemp; 855 my ($fh, $tmp) = OpenBSD::MkTemp::mkstemp( 856 "$ENV{_DEPENDS_CACHE}/my.XXXXXXXXXXX") or return; 857 chmod 0644, $fh; 858 $plist->write($fh); 859 close($fh); 860 rename($tmp, $final); 861 unlink($tmp); 862} 863 864sub ask_tree 865{ 866 my ($self, $state, $dep, $portsdir, @action) = @_; 867 868 my $make = OpenBSD::Paths->make; 869 my $pid = open(my $fh, "-|"); 870 if (!defined $pid) { 871 $state->fatal("cannot fork: $!"); 872 } 873 if ($pid == 0) { 874 chdir $portsdir or exit 2; 875 open STDERR, '>', '/dev/null'; 876 $ENV{FULLPATH} = 'Yes'; 877 delete $ENV{FLAVOR}; 878 delete $ENV{SUBPACKAGE}; 879 $ENV{SUBDIR} = $dep->{pkgpath}; 880 $ENV{ECHO_MSG} = ':'; 881 exec $make ('make', @action); 882 } 883 my $plist = OpenBSD::PackingList->read($fh, 884 \&OpenBSD::PackingList::PrelinkStuffOnly); 885 close($fh); 886 return $plist; 887} 888 889sub really_solve_from_ports 890{ 891 my ($self, $state, $dep, $portsdir) = @_; 892 893 my $diskcache = $self->diskcachename($dep); 894 my $plist; 895 896 if (defined $diskcache && -f $diskcache) { 897 $plist = OpenBSD::PackingList->fromfile($diskcache); 898 } else { 899 $plist = $self->ask_tree($state, $dep, $portsdir, 900 'print-plist-libs-with-depends', 901 'wantlib_args=no-wantlib-args'); 902 if ($? != 0 || !defined $plist->pkgname) { 903 return undef; 904 } 905 if (defined $diskcache) { 906 $self->to_cache($plist, $diskcache); 907 } 908 } 909 OpenBSD::SharedLibs::add_libs_from_plist($plist, $state); 910 $self->add_dep($plist); 911 return $plist->pkgname; 912} 913 914my $cache = {}; 915 916sub solve_from_ports 917{ 918 my ($self, $state, $dep, $package) = @_; 919 920 my $portsdir = $state->defines('PORTSDIR'); 921 return undef unless defined $portsdir; 922 my $pkgname; 923 if (defined $cache->{$dep->{pkgpath}}) { 924 $pkgname = $cache->{$dep->{pkgpath}}; 925 } else { 926 $pkgname = $self->really_solve_from_ports($state, $dep, 927 $portsdir); 928 $cache->{$dep->{pkgpath}} = $pkgname; 929 } 930 if (!defined $pkgname) { 931 $state->error("Can't obtain dependency #1 from ports tree", 932 $dep->{pattern}); 933 return undef; 934 } 935 if ($dep->spec->filter($pkgname) == 0) { 936 $state->error("Dependency #1 doesn't match FULLPKGNAME: #2", 937 $dep->{pattern}, $pkgname); 938 return undef; 939 } 940 941 return $pkgname; 942} 943 944# we don't want old libs 945sub find_old_lib 946{ 947 return undef; 948} 949 950package OpenBSD::PseudoHandle; 951sub new 952{ 953 my ($class, $plist) = @_; 954 bless { plist => $plist}, $class; 955} 956 957sub pkgname 958{ 959 my $self = shift; 960 961 return $self->{plist}->pkgname; 962} 963 964sub dependency_info 965{ 966 my $self = shift; 967 return $self->{plist}; 968} 969 970package OpenBSD::PseudoSet; 971sub new 972{ 973 my ($class, @elements) = @_; 974 975 my $o = bless {}, $class; 976 $o->add_new(@elements); 977} 978 979sub add_new 980{ 981 my ($self, @elements) = @_; 982 for my $i (@elements) { 983 push(@{$self->{new}}, OpenBSD::PseudoHandle->new($i)); 984 } 985 return $self; 986} 987 988sub newer 989{ 990 return @{shift->{new}}; 991} 992 993 994sub newer_names 995{ 996 return map {$_->pkgname} @{shift->{new}}; 997} 998 999sub older 1000{ 1001 return (); 1002} 1003 1004sub older_names 1005{ 1006 return (); 1007} 1008 1009sub kept 1010{ 1011 return (); 1012} 1013 1014sub kept_names 1015{ 1016 return (); 1017} 1018 1019sub print 1020{ 1021 my $self = shift; 1022 return $self->{new}[0]->pkgname; 1023} 1024 1025package OpenBSD::PkgCreate; 1026our @ISA = qw(OpenBSD::AddCreateDelete); 1027 1028sub handle_fragment 1029{ 1030 my ($self, $state, $old, $not, $frag, undef, $cont, $msg) = @_; 1031 my $def = $frag; 1032 if ($state->{subst}->has_fragment($def, $frag, $msg)) { 1033 return undef if defined $not; 1034 } else { 1035 return undef unless defined $not; 1036 } 1037 my $newname = $old->deduce_name($frag, $not); 1038 if (defined $newname) { 1039 $state->set_status("switching to $newname") 1040 if !defined $state->opt('q'); 1041 return $old->new($newname); 1042 } 1043 return undef; 1044} 1045 1046sub FileClass 1047{ 1048 return "MyFile"; 1049} 1050 1051sub read_fragments 1052{ 1053 my ($self, $state, $plist, $filename) = @_; 1054 1055 my $stack = []; 1056 my $subst = $state->{subst}; 1057 push(@$stack, $self->FileClass->new($filename)); 1058 my $fast = $subst->value("LIBS_ONLY"); 1059 1060 return $plist->read($stack, 1061 sub { 1062 my ($stack, $cont) = @_; 1063 while(my $file = pop @$stack) { 1064 while (my $l = $file->readline) { 1065 $state->progress->working(2048) unless $state->opt('q'); 1066 if ($l =~m/^(\@comment\s+\$(?:Open)BSD\$)$/o) { 1067 $l = '@comment $'.'OpenBSD: '.basename($file->name).',v$'; 1068 } 1069 if ($l =~ m/^(\!)?\%\%(.*)\%\%$/) { 1070 if (my $f2 = $self->handle_fragment($state, $file, $1, $2, $l, $cont, $filename)) { 1071 push(@$stack, $file); 1072 $file = $f2; 1073 } 1074 next; 1075 } 1076 my $s = $subst->do($l); 1077 if ($fast) { 1078 next unless $s =~ m/^\@(?:cwd|lib|depend|wantlib)\b/o || $s =~ m/lib.*\.a$/o; 1079 } 1080 # XXX some things, like @comment no checksum, don't produce an object 1081 my $o = &$cont($s); 1082 if (defined $o) { 1083 $o->check_version($state, $s); 1084 $self->annotate($o, $l, $file); 1085 } 1086 } 1087 } 1088 }); 1089} 1090 1091sub annotate 1092{ 1093} 1094 1095sub add_description 1096{ 1097 my ($state, $plist, $name, $opt_d) = @_; 1098 my $o = OpenBSD::PackingElement::FDESC->add($plist, $name); 1099 my $subst = $state->{subst}; 1100 my $comment = $subst->value('COMMENT'); 1101 if (defined $comment) { 1102 if (length $comment > 60) { 1103 $state->fatal("comment is too long\n#1\n#2\n", 1104 $comment, ' 'x60 . "^" x (length($comment)-60)); 1105 } 1106 } else { 1107 $state->usage("Comment required"); 1108 } 1109 if (!defined $opt_d) { 1110 $state->usage("Description required"); 1111 } 1112 return if $state->opt('q'); 1113 1114 open(my $fh, '>', $o->fullname) or die "Can't write to DESC: $!"; 1115 if (defined $comment) { 1116 print $fh $subst->do($comment), "\n"; 1117 } 1118 if ($opt_d =~ /^\-(.*)$/o) { 1119 print $fh $1, "\n"; 1120 } else { 1121 $subst->copy_fh($opt_d, $fh); 1122 } 1123 if (defined $comment) { 1124 if ($subst->empty('MAINTAINER')) { 1125 $state->errsay("no MAINTAINER"); 1126 } else { 1127 print $fh "\n", 1128 $subst->do('Maintainer: ${MAINTAINER}'), "\n"; 1129 } 1130 if (!$subst->empty('HOMEPAGE')) { 1131 print $fh "\n", $subst->do('WWW: ${HOMEPAGE}'), "\n"; 1132 } 1133 } 1134 close($fh); 1135} 1136 1137sub add_extra_info 1138{ 1139 my ($self, $plist, $state) = @_; 1140 1141 my $subst = $state->{subst}; 1142 my $fullpkgpath = $subst->value('FULLPKGPATH'); 1143 my $cdrom = $subst->value('PERMIT_PACKAGE_CDROM') || 1144 $subst->value('CDROM');; 1145 my $ftp = $subst->value('PERMIT_PACKAGE_FTP') || 1146 $subst->value('FTP'); 1147 if (defined $fullpkgpath || defined $cdrom || defined $ftp) { 1148 $fullpkgpath //= ''; 1149 $cdrom //= 'no'; 1150 $ftp //= 'no'; 1151 $cdrom = 'yes' if $cdrom =~ m/^yes$/io; 1152 $ftp = 'yes' if $ftp =~ m/^yes$/io; 1153 1154 OpenBSD::PackingElement::ExtraInfo->add($plist, 1155 $fullpkgpath, $cdrom, $ftp); 1156 } else { 1157 $state->errsay("Package without FULLPKGPATH"); 1158 } 1159} 1160 1161sub add_elements 1162{ 1163 my ($self, $plist, $state) = @_; 1164 1165 my $subst = $state->{subst}; 1166 add_description($state, $plist, DESC, $state->opt('d')); 1167 OpenBSD::PackingElement::FDISPLAY->may_add($subst, $plist, 1168 $state->opt('M')); 1169 OpenBSD::PackingElement::FUNDISPLAY->may_add($subst, $plist, 1170 $state->opt('U')); 1171 for my $d (sort keys %{$state->{dependencies}}) { 1172 OpenBSD::PackingElement::Dependency->add($plist, $d); 1173 } 1174 1175 for my $w (sort keys %{$state->{wantlib}}) { 1176 OpenBSD::PackingElement::Wantlib->add($plist, $w); 1177 } 1178 1179 if (defined $state->opt('A')) { 1180 OpenBSD::PackingElement::Arch->add($plist, $state->opt('A')); 1181 } 1182 1183 if (defined $state->opt('L')) { 1184 OpenBSD::PackingElement::LocalBase->add($plist, $state->opt('L')); 1185 } 1186 $self->add_extra_info($plist, $state); 1187} 1188 1189sub cant_read_fragment 1190{ 1191 my ($self, $state, $frag) = @_; 1192 $state->fatal("can't read packing-list #1", $frag); 1193} 1194 1195sub read_all_fragments 1196{ 1197 my ($self, $state, $plist) = @_; 1198 1199 if (defined $state->{prefix}) { 1200 OpenBSD::PackingElement::Cwd->add($plist, $state->{prefix}); 1201 } else { 1202 $state->usage("Prefix required"); 1203 } 1204 for my $contentsfile (@{$state->{contents}}) { 1205 $self->read_fragments($state, $plist, $contentsfile) or 1206 $self->cant_read_fragment($state, $contentsfile); 1207 } 1208 1209 $plist->register_forbidden($state); 1210 if (defined $state->{forbidden}) { 1211 for my $e (@{$state->{forbidden}}) { 1212 $state->errsay("Error: #1 can't be set explicitly", "\@".$e->keyword." ".$e->stringize); 1213 } 1214 $state->fatal("Can't continue"); 1215 } 1216} 1217 1218sub create_plist 1219{ 1220 my ($self, $state, $pkgname) = @_; 1221 1222 my $plist = OpenBSD::PackingList->new; 1223 1224 if ($pkgname =~ m|([^/]+)$|o) { 1225 $pkgname = $1; 1226 $pkgname =~ s/\.tgz$//o; 1227 } 1228 $state->say("Creating package #1", $pkgname) 1229 if !(defined $state->opt('q')) && $state->opt('v'); 1230 if (!$state->opt('q')) { 1231 $plist->set_infodir(OpenBSD::Temp->dir); 1232 } 1233 1234 unless (defined $state->opt('q') && defined $state->opt('n')) { 1235 $state->set_status("reading plist"); 1236 } 1237 $self->read_all_fragments($state, $plist); 1238 $plist->set_pkgname($pkgname); 1239 1240 $self->add_elements($plist, $state); 1241 return $plist; 1242} 1243 1244sub make_plist_with_sum 1245{ 1246 my ($self, $state, $plist) = @_; 1247 my $p2 = OpenBSD::PackingList->new; 1248 $state->progress->visit_with_count($plist, 'makesum_plist', $p2); 1249 $p2->set_infodir($plist->infodir); 1250 return $p2; 1251} 1252 1253sub read_existing_plist 1254{ 1255 my ($self, $state, $contents) = @_; 1256 1257 my $plist = OpenBSD::PackingList->new; 1258 if (-d $contents && -f $contents.'/'.CONTENTS) { 1259 $plist->set_infodir($contents); 1260 $contents .= '/'.CONTENTS; 1261 } else { 1262 $plist->set_infodir(dirname($contents)); 1263 } 1264 $plist->fromfile($contents) or 1265 $state->fatal("can't read packing-list #1", $contents); 1266 return $plist; 1267} 1268 1269sub create_package 1270{ 1271 my ($self, $state, $plist, $ordered, $wname) = @_; 1272 1273 $state->say("Creating gzip'd tar ball in '#1'", $wname) 1274 if $state->opt('v'); 1275 my $h = sub { 1276 unlink $wname; 1277 my $caught = shift; 1278 $SIG{$caught} = 'DEFAULT'; 1279 kill $caught, $$; 1280 }; 1281 1282 local $SIG{'INT'} = $h; 1283 local $SIG{'QUIT'} = $h; 1284 local $SIG{'HUP'} = $h; 1285 local $SIG{'KILL'} = $h; 1286 local $SIG{'TERM'} = $h; 1287 $state->{archive} = $state->create_archive($wname, $plist->infodir); 1288 $state->set_status("archiving"); 1289 my $p = $state->progress->new_sizer($plist, $state); 1290 for my $e (@$ordered) { 1291 $e->create_package($state); 1292 $p->advance($e); 1293 } 1294 $state->end_status; 1295 $state->{archive}->close; 1296 if ($state->{bad}) { 1297 unlink($wname); 1298 exit(1); 1299 } 1300} 1301 1302sub show_bad_symlinks 1303{ 1304 my ($self, $state) = @_; 1305 for my $dest (sort keys %{$state->{bad_symlinks}}) { 1306 $state->errsay("Warning: symlink(s) point to non-existent #1", 1307 $dest); 1308 for my $link (@{$state->{bad_symlinks}{$dest}}) { 1309 $state->errsay("\t#1", $link); 1310 } 1311 } 1312} 1313 1314sub check_dependencies 1315{ 1316 my ($self, $plist, $state) = @_; 1317 1318 my $solver = OpenBSD::Dependencies::CreateSolver->new($plist); 1319 1320 # look for libraries in the "real" tree 1321 $state->{destdir} = '/'; 1322 1323 $solver->solve_all_depends($state); 1324 if (!$solver->solve_wantlibs($state, 1)) { 1325 $state->{bad}++; 1326 } 1327} 1328 1329sub finish_manpages 1330{ 1331 my ($self, $state, $plist) = @_; 1332 $plist->grab_manpages($state); 1333 if (defined $state->{manpages}) { 1334 $state->run_makewhatis(['-t'], $state->{manpages}); 1335 } 1336 1337 if (defined $state->{mandir}) { 1338 require File::Path; 1339 File::Path::remove_tree($state->{mandir}); 1340 } 1341} 1342 1343sub save_history 1344{ 1345 my ($self, $plist, $dir) = @_; 1346 1347 # grab the old stuff: 1348 # - order 1349 # - and presence 1350 my (%known, %found); 1351 my $fname; 1352 if (defined $dir) { 1353 unless (-d $dir) { 1354 require File::Path; 1355 1356 File::Path::make_path($dir); 1357 } 1358 1359 my $name = $plist->fullpkgpath; 1360 $name =~ s,/,.,g; 1361 my $fname = "$dir/$name"; 1362 my $n = 0; 1363 1364 if (open(my $f, '<', $fname)) { 1365 while (<$f>) { 1366 chomp; 1367 $known{$_} //= $n++; 1368 } 1369 close($f); 1370 } 1371 } 1372 my @new; 1373 my $entries = {}; 1374 my $list = []; 1375 my $tail = []; 1376 $plist->record_digest(\@new, $entries, $list, $tail); 1377 1378 my $f; 1379 if (defined $fname) { 1380 open($f, ">", "$fname.new"); 1381 } 1382 1383 # split list 1384 # - first, unknown stuff 1385 for my $h (@new) { 1386 if ($known{$h}) { 1387 $found{$h} = $known{$h}; 1388 } else { 1389 print $f "$h\n" if defined $f; 1390 push(@$list, (shift @{$entries->{$h}})); 1391 } 1392 } 1393 # - then known stuff, preserve the order 1394 for my $h (sort {$found{$a} <=> $found{$b}} keys %found) { 1395 print $f "$h\n" if defined $f; 1396 push(@$list, @{$entries->{$h}}); 1397 } 1398 if (defined $f) { 1399 close($f); 1400 rename("$fname.new", $fname); 1401 } 1402 # create a new list with check points. 1403 my $l = [@$tail]; 1404 my $i = 0; 1405 my $end_marker = OpenBSD::PackingElement::StreamMarker->new; 1406 while (@$list > 0) { 1407 my $e = pop @$list; 1408 if ($e->really_archived && $i++ % 16 == 0) { 1409 unshift @$l, $end_marker; 1410 } 1411 unshift @$l, $e; 1412 } 1413 # remove extraneous marker if @$tail is empty. 1414 if ($l->[-1] eq $end_marker) { 1415 pop @$l; 1416 } 1417 return $l; 1418} 1419 1420sub parse_and_run 1421{ 1422 my ($self, $cmd) = @_; 1423 1424 my $regen_package = 0; 1425 my $sign_only = 0; 1426 1427 my $state = OpenBSD::PkgCreate::State->new($cmd); 1428 $state->handle_options; 1429 1430 if (@ARGV == 0) { 1431 $regen_package = 1; 1432 } elsif (@ARGV != 1) { 1433 if (defined $state->{contents} || 1434 !defined $state->{signature_params}) { 1435 $state->usage("Exactly one single package name is required: #1", join(' ', @ARGV)); 1436 } 1437 } 1438 1439 try { 1440 if (defined $state->opt('Q')) { 1441 $state->{opt}{q} = 1; 1442 } 1443 1444 if (!defined $state->{contents}) { 1445 $state->usage("Packing-list required"); 1446 } 1447 1448 my $plist; 1449 if ($regen_package) { 1450 if (!defined $state->{contents} || @{$state->{contents}} > 1) { 1451 $state->usage("Exactly one single packing-list is required"); 1452 } 1453 $plist = $self->read_existing_plist($state, 1454 $state->{contents}[0]); 1455 } else { 1456 $plist = $self->create_plist($state, $ARGV[0]); 1457 } 1458 1459 1460 $plist->discover_directories($state); 1461 my $ordered; 1462 unless (defined $state->opt('q') && defined $state->opt('n')) { 1463 $state->set_status("checking dependencies"); 1464 $self->check_dependencies($plist, $state); 1465 $state->set_status("checksumming"); 1466 if ($regen_package) { 1467 $state->progress->visit_with_count($plist, 'verify_checksum'); 1468 } else { 1469 $plist = $self->make_plist_with_sum($state, $plist); 1470 } 1471 $ordered = $self->save_history($plist, 1472 $state->defines('HISTORY_DIR')); 1473 $self->show_bad_symlinks($state); 1474 $state->end_status; 1475 } 1476 1477 if (!defined $plist->pkgname) { 1478 $state->fatal("can't write unnamed packing-list"); 1479 } 1480 1481 if (defined $state->opt('q')) { 1482 if (defined $state->opt('Q')) { 1483 $plist->print_file; 1484 } else { 1485 $plist->write(\*STDOUT); 1486 } 1487 return 0 if defined $state->opt('n'); 1488 } 1489 1490 if ($plist->{deprecated}) { 1491 $state->fatal("found obsolete constructs"); 1492 } 1493 1494 $plist->avert_duplicates_and_other_checks($state); 1495 if ($state->{has_no_default_conflict} && !$state->{has_conflict}) { 1496 $state->errsay("Warning: \@option no-default-conflict without \@conflict"); 1497 } 1498 $state->{stash} = {}; 1499 1500 if ($state->{bad} && !$state->defines('REGRESSION_TESTING')) { 1501 $state->fatal("can't continue"); 1502 } 1503 $state->{bad} = 0; 1504 1505 if (defined $state->{signer}) { 1506 $state->add_signature($plist); 1507 $plist->save if $regen_package; 1508 } 1509 1510 my $wname; 1511 if ($regen_package) { 1512 $wname = $plist->pkgname.".tgz"; 1513 } else { 1514 $plist->save or $state->fatal("can't write packing-list"); 1515 $wname = $ARGV[0]; 1516 } 1517 1518 if ($state->opt('n')) { 1519 $state->{archive} = OpenBSD::Ustar->new(undef, $state, 1520 $plist->infodir); 1521 $plist->pretend_to_archive($state); 1522 } else { 1523 $self->create_package($state, $plist, $ordered, $wname); 1524 } 1525 $self->finish_manpages($state, $plist); 1526 }catch { 1527 print STDERR "$0: $_\n"; 1528 return 1; 1529 }; 1530 return 0; 1531} 1532 15331; 1534