diff options
Diffstat (limited to 'usr.sbin/pkg_add/OpenBSD/PkgCreate.pm')
-rw-r--r-- | usr.sbin/pkg_add/OpenBSD/PkgCreate.pm | 831 |
1 files changed, 831 insertions, 0 deletions
diff --git a/usr.sbin/pkg_add/OpenBSD/PkgCreate.pm b/usr.sbin/pkg_add/OpenBSD/PkgCreate.pm new file mode 100644 index 00000000000..6200916674d --- /dev/null +++ b/usr.sbin/pkg_add/OpenBSD/PkgCreate.pm @@ -0,0 +1,831 @@ +#! /usr/bin/perl +# ex:ts=8 sw=4: +# $OpenBSD: PkgCreate.pm,v 1.1 2010/06/04 13:19:39 espie Exp $ +# +# Copyright (c) 2003-2010 Marc Espie <espie@openbsd.org> +# +# 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; + +use OpenBSD::AddCreateDelete; + +package OpenBSD::PkgCreate::State; +our @ISA = qw(OpenBSD::AddCreateDelete::State); + +sub init +{ + my $self = shift; + + $self->{stash} = {}; + $self->SUPER::init(@_); +} + +sub stash +{ + my ($self, $key) = @_; + return $self->{stash}{$key}; +} + +sub error +{ + my $self = shift; + $self->{bad}++; + $self->errsay(@_); +} + +package OpenBSD::PkgCreate; + +use OpenBSD::PackingList; +use OpenBSD::PackageInfo; +use OpenBSD::Getopt; +use OpenBSD::Temp; +use OpenBSD::Error; +use OpenBSD::Ustar; +use OpenBSD::ArcCheck; +use OpenBSD::Paths; +use OpenBSD::Subst; +use OpenBSD::ProgressMeter; +use File::Basename; + +# Extra stuff needed to archive files +package OpenBSD::PackingElement; +sub create_package +{ + my ($self, $state) = @_; + + $self->archive($state); + if ($state->verbose) { + $self->comment_create_package; + } +} + +sub pretend_to_archive +{ + my ($self, $state) = @_; + $self->comment_create_package; +} + +sub archive {} +sub comment_create_package {} + +sub print_file {} + +sub avert_duplicates_and_other_checks +{ + my ($self, $state) = @_; + return unless $self->NoDuplicateNames; + my $n = $self->fullname; + if (defined $state->stash($n)) { + $state->error("Error in packing-list: duplicate item $n"); + } + $state->{stash}{$n} = 1; +} + +sub makesum_plist +{ + my ($self, $plist, $state) = @_; + $self->add_object($plist); +} + +sub verify_checksum +{ +} + +sub compute_checksum +{ + my ($self, $result, $state, $base) = @_; + my $name = $self->fullname; + my $fname = $name; + if (defined $base) { + $fname = $base.$fname; + } + + if (-l $fname) { + my $value = readlink $fname; + $result->make_symlink($value); + } elsif (-f _) { + my ($dev, $ino, $size) = (stat _)[0,1,7]; + if (defined $state->stash("$dev/$ino")) { + $result->make_hardlink($state->stash("$dev/$ino")); + } else { + $state->{stash}{"$dev/$ino"} = $name; + $result->add_digest($self->compute_digest($fname)); + $result->add_size($size); + } + } else { + $state->error("Error in package: $fname does not exist"); + } +} + +sub makesum_plist_with_base +{ + my ($self, $plist, $state, $base) = @_; + $self->compute_checksum($self, $state, $base); + $self->add_object($plist); +} + +sub verify_checksum_with_base +{ + my ($self, $state, $base) = @_; + my $check = ref($self)->new($self->name); + $self->compute_checksum($check, $state, $base); + + for my $field (qw(symlink link size)) { # md5 + if ((defined $check->{$field} && defined $self->{$field} && + $check->{$field} ne $self->{$field}) || + (defined $check->{$field} xor defined $self->{$field})) { + $state->error("Error: $field inconsistency for ", + $self->fullname); + } + } + if ((defined $check->{d} && defined $self->{d} && + !$check->{d}->equals($self->{d})) || + (defined $check->{d} xor defined $self->{d})) { + $state->error("Error: checksum inconsistency for ", + $self->fullname); + } +} + + +sub prepare_for_archival +{ + my ($self, $state) = @_; + + my $o = $state->{archive}->prepare_long($self); + if (!$o->verify_modes($self)) { + $state->error("Modes don't match"); + } + return $o; +} + +sub copy_over +{ +} +package OpenBSD::PackingElement::SpecialFile; +sub archive +{ + &OpenBSD::PackingElement::FileBase::archive; +} + +sub pretend_to_archive +{ + &OpenBSD::PackingElement::FileBase::pretend_to_archive; +} + +sub comment_create_package +{ + my ($self) = @_; + print "Adding ", $self->name, "\n"; +} + +sub makesum_plist +{ + my ($self, $plist, $state) = @_; + $self->makesum_plist_with_base($plist, $state, undef); +} + +sub verify_checksum +{ + my ($self, $state) = @_; + $self->verify_checksum_with_base($state, undef); +} + +sub prepare_for_archival +{ + my ($self, $state) = @_; + + my $o = $state->{archive}->prepare_long($self); + $o->{uname} = 'root'; + $o->{gname} = 'wheel'; + $o->{uid} = 0; + $o->{gid} = 0; + $o->{mode} &= 0555; # zap all write and suid modes + return $o; +} + +sub copy_over +{ + my ($self, $wrarc, $rdarc) = @_; + $wrarc->destdir($rdarc->info); + my $e = $wrarc->prepare($self->{name}); + $e->write; +} + +# override for CONTENTS: we cannot checksum this. +package OpenBSD::PackingElement::FCONTENTS; +sub makesum_plist +{ +} + +sub verify_checksum +{ +} + + +package OpenBSD::PackingElement::Cwd; +sub archive +{ + my ($self, $state) = @_; + $state->{archive}->destdir($state->{base}."/".$self->name); +} + +sub pretend_to_archive +{ + my ($self, $state) = @_; + $state->{archive}->destdir($state->{base}."/".$self->name); + $self->comment_create_package; +} + +sub comment_create_package +{ + my ($self) = @_; + print "Cwd: ", $self->name, "\n"; +} + +package OpenBSD::PackingElement::FileBase; + +sub archive +{ + my ($self, $state) = @_; + + my $o = $self->prepare_for_archival($state); + + $o->write unless $state->{bad}; +} + +sub pretend_to_archive +{ + my ($self, $state) = @_; + + $self->prepare_for_archival($state); + $self->comment_create_package; +} + +sub comment_create_package +{ + my ($self) = @_; + print "Adding ", $self->name, "\n"; +} + +sub print_file +{ + my ($item) = @_; + print '@', $item->keyword, " ", $item->fullname, "\n"; +} + +sub makesum_plist +{ + my ($self, $plist, $state) = @_; + $self->makesum_plist_with_base($plist, $state, $state->{base}); +} + +sub verify_checksum +{ + my ($self, $state) = @_; + $self->verify_checksum_with_base($state, $state->{base}); +} + +sub copy_over +{ + my ($self, $wrarc, $rdarc) = @_; + my $e = $rdarc->next; + if (!$e->check_name($self)) { + die "Names don't match: ", $e->{name}, " ", $self->{name}; + } + $e->copy_long($wrarc); +} + +package OpenBSD::PackingElement::InfoFile; +sub makesum_plist +{ + my ($self, $plist, $state) = @_; + $self->SUPER::makesum_plist($plist, $state); + my $fname = $self->fullname; + for (my $i = 1; ; $i++) { + if (-e "$state->{base}/$fname-$i") { + my $e = OpenBSD::PackingElement::File->add($plist, $self->name."-".$i); + $e->compute_checksum($e, $state, $state->{base}); + } else { + last; + } + } +} + +package OpenBSD::PackingElement::Manpage; +sub makesum_plist +{ + my ($self, $plist, $state) = @_; + if ($state->{subst}->empty("USE_GROFF") || !$self->is_source) { + return $self->SUPER::makesum_plist($plist, $state); + } + my $dest = $self->source_to_dest; + $self->format($state->{base}, $self->cwd."/".$dest); + my $e = OpenBSD::PackingElement::Manpage->add($plist, $dest); + $e->compute_checksum($e, $state, $state->{base}); +} + +package OpenBSD::PackingElement::Depend; +sub avert_duplicates_and_other_checks +{ + my ($self, $state) = @_; + if (!$self->spec->is_valid) { + $state->error("Error in packing-list: invalid \@", + $self->keyword, " ", $self->stringize); + } + $self->SUPER::avert_duplicates_and_other_checks($state); +} + +package OpenBSD::PackingElement::AskUpdate; +sub avert_duplicates_and_other_checks +{ + &OpenBSD::PackingElement::Depend::avert_duplicates_and_other_checks; +} + +package OpenBSD::PackingElement::Dependency; +sub avert_duplicates_and_other_checks +{ + my ($self, $state) = @_; + + my @issues = OpenBSD::PackageName->from_string($self->{def})->has_issues; + if (@issues > 0) { + $state->error("Error in packing-list: invalid \@", + $self->keyword, " ", $self->stringize, "\n", + "$self->{def}: , ", join(' ', @issues)); + } + + $self->SUPER::avert_duplicates_and_other_checks($state); +} + +package OpenBSD::PackingElement::Name; +sub avert_duplicates_and_other_checks +{ + my ($self, $state) = @_; + + my @issues = OpenBSD::PackageName->from_string($self->name)->has_issues; + if (@issues > 0) { + $state->error("Bad packagename ", $self->name, ":", + join(' ', @issues)); + } + $self->SUPER::avert_duplicates_and_other_checks($state); +} + +# put together file and filename, in order to handle fragments simply +package MyFile; +sub new +{ + my ($class, $filename) = @_; + + open(my $fh, '<', $filename) or die "Missing file $filename"; + + bless { fh => $fh, name => $filename }, $class; +} + +sub readline +{ + my $self = shift; + return readline $self->{fh}; +} + +sub name +{ + my $self = shift; + return $self->{name}; +} + +sub close +{ + my $self = shift; + close($self->{fh}); +} + +package OpenBSD::PkgCreate; +our @ISA = qw(OpenBSD::AddCreateDelete); + +sub deduce_name +{ + my ($state, $o, $frag, $not) = @_; + + my $noto = $o; + my $nofrag = "no-$frag"; + + $o =~ s/PFRAG\./PFRAG.$frag-/o or + $o =~ s/PLIST/PFRAG.$frag/o; + + $noto =~ s/PFRAG\./PFRAG.no-$frag-/o or + $noto =~ s/PLIST/PFRAG.no-$frag/o; + unless (-e $o or -e $noto) { + die "Missing fragments for $frag: $o and $noto don't exist"; + } + if ($not) { + print "Switching to $noto\n" if !defined $state->opt('q'); + return $noto if -e $noto; + } else { + print "Switching to $o\n" if !defined $state->opt('q'); + return $o if -e $o; + } + return; +} + +sub read_fragments +{ + my ($state, $plist, $filename) = @_; + + my $stack = []; + my $subst = $state->{subst}; + push(@$stack, MyFile->new($filename)); + + return $plist->read($stack, + sub { + my ($stack, $cont) = @_; + local $_; + while(my $file = pop @$stack) { + GETLINE: + while ($_ = $file->readline) { + if (my ($not, $frag) = m/^(\!)?\%\%(.*)\%\%$/) { + my $def = $frag; + if ($frag eq 'SHARED') { + $def = 'SHARED_LIBS'; + $frag = 'shared'; + } + if ($subst->has_fragment($def, $frag)) { + next GETLINE if defined $not; + } else { + next GETLINE unless defined $not; + } + my $newname = deduce_name($state, $file->name, $frag, $not); + if (defined $newname) { + push(@$stack, $file); + $file = MyFile->new($newname); + } + next GETLINE; + } + if (m/^(\@comment\s+\$(?:Open)BSD\$)$/o) { + $_ = '@comment $'.'OpenBSD: '.basename($file->name).',v$'; + } + if (m/^\@lib\s+(.*)$/o && + OpenBSD::PackingElement::Lib->parse($1)) { + $state->error("Shared library without SHARED_LIBS: $_"); + } + &$cont($subst->do($_)); + } + } + } + ); +} + +sub add_special_file +{ + my ($subst, $plist, $name, $opt) = @_; + if (defined $opt) { + my $o = OpenBSD::PackingElement::File->add($plist, $name); + $subst->copy($opt, $o->fullname) if defined $o->fullname; + } +} + +sub add_description +{ + my ($subst, $plist, $name, $opt_d) = @_; + my $o = OpenBSD::PackingElement::FDESC->add($plist, $name); + my $comment = $subst->value('COMMENT'); + if (defined $comment) { + if (length $comment > 60) { + print STDERR "Error: comment is too long\n"; + print STDERR $comment, "\n"; + print STDERR ' 'x60, "^"x (length($comment)-60), "\n"; + exit 1; + } + } else { + Usage "Comment required"; + } + if (!defined $opt_d) { + Usage "Description required"; + } + if (defined $o->fullname) { + open(my $fh, '>', $o->fullname) or die "Can't write to DESC: $!"; + if (defined $comment) { + print $fh $subst->do($comment), "\n"; + } + if ($opt_d =~ /^\-(.*)$/o) { + print $fh $1, "\n"; + } else { + $subst->copy_fh($opt_d, $fh); + } + if (defined $comment) { + if ($subst->empty('MAINTAINER')) { + Warn "no MAINTAINER\n"; + } else { + print $fh "\n", $subst->do('Maintainer: ${MAINTAINER}'), "\n"; + } + if (!$subst->empty('HOMEPAGE')) { + print $fh "\n", $subst->do('WWW: ${HOMEPAGE}'), "\n"; + } + } + close($fh); + } +} + +my (@contents, %dependencies, %wantlib, @signature_params); + +my $regen_package = 0; +my $sign_only = 0; +my ($cert, $privkey); + +sub parse_and_run +{ + my $self = shift; + + my $state = OpenBSD::PkgCreate::State->new; + my $plist = new OpenBSD::PackingList; + + $state->{opt} = { + 'f' => + sub { + push(@contents, shift); + }, + 'P' => sub { + my $d = shift; + $dependencies{$d} = 1; + }, + 'W' => sub { + my $w = shift; + $wantlib{$w} = 1; + }, + 's' => sub { + push(@signature_params, shift); + } + }; + $self->handle_options('p:f:d:M:U:s:A:L:B:P:W:qQ', $state, + 'pkg_create [-nQqvx] [-A arches] [-B pkg-destdir] [-D name[=value]]', + '[-L localbase] [-M displayfile] [-P pkg-dependency]', + '[-s x509 -s cert -s priv] [-U undisplayfile] [-W wantedlib]', + '-d desc -D COMMENT=value -f packinglist -p prefix pkg-name'); + + my $subst = $state->{subst}; + + if (@ARGV == 0) { + $regen_package = 1; + } elsif (@ARGV != 1) { + if (@contents || @signature_params == 0) { + Usage "Exactly one single package name is required: ", + join(' ', @ARGV); + } + } + + try { + + if (@signature_params > 0) { + if (@signature_params != 3 || $signature_params[0] ne 'x509' || + !-f $signature_params[1] || !-f $signature_params[2]) { + Usage "Signature only works as -s x509 -s cert -s privkey"; + } + $cert = $signature_params[1]; + $privkey = $signature_params[2]; + } + + if (defined $state->opt('Q')) { + $state->{opt}{q} = 1; + } + + if (!@contents) { + if (@signature_params > 0) { + $sign_only = 1; + } else { + Usage "Packing list required"; + } + } + + my $cont = 0; + if ($regen_package) { + if (@contents != 1) { + Usage "Exactly one single packing list is required"; + } + if (-d $contents[0] && -f $contents[0].'/'.CONTENTS) { + $plist->set_infodir($contents[0]); + $contents[0] .= '/'.CONTENTS; + } else { + $plist->set_infodir(dirname($contents[0])); + } + $plist->fromfile($contents[0]) or + Fatal "Can't read packing list $contents[0]"; + } elsif ($sign_only) { + if ($state->not) { + Fatal "Can't pretend to sign existing packages"; + } + for my $pkgname (@ARGV) { + require OpenBSD::PackageLocator; + require OpenBSD::x509; + + my $true_package = OpenBSD::PackageLocator->find($pkgname); + die "No such package $pkgname" unless $true_package; + my $dir = $true_package->info; + my $plist = OpenBSD::PackingList->fromfile($dir.CONTENTS); + $plist->set_infodir($dir); + my $sig = OpenBSD::PackingElement::DigitalSignature->new_x509; + $sig->add_object($plist); + $sig->{b64sig} = OpenBSD::x509::compute_signature($plist, + $cert, $privkey); + $plist->save; + my $tmp = OpenBSD::Temp::permanent_file(".", "pkg"); + open( my $outfh, "|-", OpenBSD::Paths->gzip, "-o", $tmp); + + my $wrarc = OpenBSD::Ustar->new($outfh, "."); + $plist->copy_over($wrarc, $true_package); + $wrarc->close; + $true_package->wipe_info; + unlink($plist->pkgname.".tgz"); + rename($tmp, $plist->pkgname.".tgz") or + die "Can't create final signed package $!"; + } + exit(0); + } else { + print "Creating package $ARGV[0]\n" if !(defined $state->opt('q')) && $state->opt('v'); + if (!$state->opt('q')) { + $plist->set_infodir(OpenBSD::Temp->dir); + } + add_description($subst, $plist, DESC, $state->opt('d')); + add_special_file($subst, $plist, DISPLAY, $state->opt('M')); + add_special_file($subst, $plist, UNDISPLAY, $state->opt('U')); + if (defined $state->opt('p')) { + OpenBSD::PackingElement::Cwd->add($plist, $state->opt('p')); + } else { + Usage "Prefix required"; + } + for my $d (sort keys %dependencies) { + OpenBSD::PackingElement::Dependency->add($plist, $d); + } + + for my $w (sort keys %wantlib) { + OpenBSD::PackingElement::Wantlib->add($plist, $w); + } + + if (defined $state->opt('A')) { + OpenBSD::PackingElement::Arch->add($plist, $state->opt('A')); + } + + if (defined $state->opt('L')) { + OpenBSD::PackingElement::LocalBase->add($plist, $state->opt('L')); + } + if ($ARGV[0] =~ m|([^/]+)$|o) { + my $pkgname = $1; + $pkgname =~ s/\.tgz$//o; + $plist->set_pkgname($pkgname); + } + my $fullpkgpath = $subst->value('FULLPKGPATH'); + my $cdrom = $subst->value('PERMIT_PACKAGE_CDROM') || + $subst->value('CDROM');; + my $ftp = $subst->value('PERMIT_PACKAGE_FTP') || + $subst->value('FTP'); + if (defined $fullpkgpath || defined $cdrom || defined $ftp) { + $fullpkgpath //= ''; + $cdrom //= 'no'; + $ftp //= 'no'; + $cdrom = 'yes' if $cdrom =~ m/^yes$/io; + $ftp = 'yes' if $ftp =~ m/^yes$/io; + + OpenBSD::PackingElement::ExtraInfo->add($plist, + $fullpkgpath, $cdrom, $ftp); + } else { + Warn "Package without FULLPKGPATH\n"; + } + unless (defined $state->opt('q') && defined $state->opt('n')) { + if ($state->progress->set_header("reading plist")) { + $state->progress->message; + } else { + $| = 1; + print "Reading plist..."; + $cont = 1; + } + } + for my $contentsfile (@contents) { + read_fragments($state, $plist, $contentsfile) or + Fatal "Can't read packing list $contentsfile"; + } + } + + + my $base = '/'; + if (defined $state->opt('B')) { + $base = $state->opt('B'); + } elsif (defined $ENV{'PKG_PREFIX'}) { + $base = $ENV{'PKG_PREFIX'}; + } + + $state->{base} = $base; + + unless (defined $state->opt('q') && defined $state->opt('n')) { + if ($cont) { + print "\nChecksumming..."; + } else { + $state->progress->set_header("checksumming"); + } + if ($regen_package) { + $state->progress->visit_with_count($plist, 'verify_checksum', $state); + } else { + my $p2 = OpenBSD::PackingList->new; + $state->progress->visit_with_count($plist, 'makesum_plist', $p2, $state); + $p2->set_infodir($plist->infodir); + $plist = $p2; + } + if ($cont) { + print "\n"; + } + } + + if (!defined $plist->{name}) { + $state->error("Can't write unnamed packing list"); + exit 1; + } + + if (defined $state->opt('q')) { + if (defined $state->opt('Q')) { + $plist->print_file; + } else { + $plist->write(\*STDOUT); + } + exit 0 if defined $state->opt('n'); + } + + if ($plist->{deprecated}) { + $state->error("Error: found obsolete constructs"); + exit 1; + } + + $plist->avert_duplicates_and_other_checks($state); + $state->{stash} = {}; + + if ($state->{bad} && $subst->empty('REGRESSION_TESTING')) { + exit 1; + } + $state->{bad} = 0; + + if (defined $cert) { + my $sig = OpenBSD::PackingElement::DigitalSignature->new_x509; + $sig->add_object($plist); + require OpenBSD::x509; + $sig->{b64sig} = OpenBSD::x509::compute_signature($plist, $cert, $privkey); + $plist->save if $regen_package; + } + + my $wname; + if ($regen_package) { + $wname = $plist->pkgname.".tgz"; + } else { + $plist->save or Fatal "Can't write packing list"; + $wname = $ARGV[0]; + } + + if ($state->opt('n')) { + $state->{archive} = OpenBSD::Ustar->new(undef, $plist->infodir); + $plist->pretend_to_archive($state); + } else { + print "Creating gzip'd tar ball in '$wname'\n" if $state->opt('v'); + my $h = sub { + unlink $wname; + my $caught = shift; + $SIG{$caught} = 'DEFAULT'; + kill $caught, $$; + }; + + local $SIG{'INT'} = $h; + local $SIG{'QUIT'} = $h; + local $SIG{'HUP'} = $h; + local $SIG{'KILL'} = $h; + local $SIG{'TERM'} = $h; + open(my $fh, "|-", OpenBSD::Paths->gzip, "-f", "-o", $wname); + $state->{archive} = OpenBSD::Ustar->new($fh, $plist->infodir); + + if ($cont) { + print "Archiving..."; + } else { + $state->progress->set_header("archiving"); + } + $state->progress->visit_with_size($plist, 'create_package', $state); + if ($cont) { + print "\n"; + } + $state->progress->clear; + $state->{archive}->close; + if ($state->{bad}) { + unlink($wname); + exit(1); + } + } + } catch { + print STDERR "$0: $_\n"; + exit(1); + }; +} + +1; |