#! /usr/bin/perl # ex:ts=8 sw=4: # $OpenBSD: pkg_create,v 1.94 2007/05/05 11:18:18 espie Exp $ # # Copyright (c) 2003-2007 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; use OpenBSD::PackingList; use OpenBSD::PackageInfo; use OpenBSD::Getopt; use OpenBSD::md5; use OpenBSD::Temp; use OpenBSD::Error; use OpenBSD::Ustar; use OpenBSD::ArcCheck; use File::Basename; # Extra stuff needed to archive files package OpenBSD::PackingElement; sub create_package { my ($self, $arc, $base, $verbose) = @_; $self->archive($arc, $base); if ($verbose) { $self->comment_create_package; } } sub pretend_to_archive { my ($self, $arc, $base) = @_; $self->comment_create_package; } sub archive {} sub comment_create_package {} sub print_file {} sub avert_duplicates_and_other_checks { my ($self, $allfiles) = @_; return unless $self->NoDuplicateNames; my $n = $self->fullname; if (defined $allfiles->{$n}) { print STDERR "Error in packing-list: duplicate item $n\n"; $main::errors++; } $allfiles->{$n} = 1; } sub makesum_plist { my ($self, $plist, $base, $stash, $infodir) = @_; $self->add_object($plist); } sub verify_checksum { } use OpenBSD::md5; sub compute_checksum { my ($self, $result, $base, $name, $stash) = @_; my $fname = "$base/$name"; if (-l $fname) { my $value = readlink $fname; $result->make_symlink($value); } elsif (-f _) { my ($dev, $ino, $size) = (stat _)[0,1,7]; if (defined $stash->{"$dev/$ino"}) { $result->make_hardlink($stash->{"$dev/$ino"}); } else { $stash->{"$dev/$ino"} = $name; $result->add_md5(OpenBSD::md5::fromfile($fname)); $result->add_size($size); } } else { print STDERR "Error in package: $fname does not exist\n"; $main::errors++; } } sub makesum_plist_filename { my ($self, $plist, $base, $name, $stash) = @_; $self->compute_checksum($self, $base, $name, $stash); $self->add_object($plist); } sub verify_checksum_filename { my ($self, $base, $name, $stash) = @_; my $check = ref($self)->new($self->{name}); $self->compute_checksum($check, $base, $name, $stash); for my $field (qw(symlink link md5 size)) { if ((defined $check->{$field} && defined $self->{$field} && $check->{$field} ne $self->{$field}) || (defined $check->{$field} xor defined $self->{$field})) { print STDERR "Error: $field inconsistency for $name\n"; $main::errors++; } } } use POSIX; sub prepare_for_archival { my ($self, $arc) = @_; my $o = $arc->prepare_long($self); if (!defined $self->{owner} && !$o->isSymLink) { if ($o->{uname} ne 'root' && $o->{uname} ne 'bin') { print STDERR "Error: no \@owner for ", $self->fullname, " (", $o->{uname}, ")\n"; $main::errors++; } } if (!defined $self->{group} && !$o->isSymLink) { if ($o->{gname} ne 'bin' && $o->{gname} ne 'wheel') { print STDERR "Warning: no \@group for ", $self->fullname, " (", $o->{gname}, ")\n"; # $main::errors++; } } if (!defined $self->{mode} && $o->isFile) { if (($o->{mode} & (S_ISUID | S_ISGID | S_IWOTH)) != 0) { print STDERR "Error: weird mode for ", $self->fullname, ": ", sprintf("%4o", $o->{mode} & (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID)), "\n"; $main::errors++; } } return $o; } 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, $base, $stash, $infodir) = @_; $self->makesum_plist_filename($plist, $infodir ,$self->{name}, $stash); } sub verify_checksum { my ($self, $base, $stash, $infodir) = @_; $self->verify_checksum_filename($infodir, $self->{name}, $stash); } # override for CONTENTS: we cannot checksum this. package OpenBSD::PackingElement::FCONTENTS; sub makesum_plist { my ($self, $plist, $base, $stash, $infodir) = @_; $self->add_object($plist); } sub verify_checksum { } package OpenBSD::PackingElement::Cwd; use OpenBSD::Temp; sub archive { my ($self, $arc, $base) = @_; $arc->destdir($base."/".$self->{name}); } sub pretend_to_archive { my ($self, $arc, $base) = @_; $arc->destdir($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, $arc, $base) = @_; my $o = $self->prepare_for_archival($arc); $o->write unless $main::errors; } sub pretend_to_archive { my ($self, $arc, $base) = @_; $self->prepare_for_archival($arc); $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, $base, $stash, $infodir) = @_; $self->makesum_plist_filename($plist, $base, $self->fullname, $stash); } sub verify_checksum { my ($self, $base, $stash, $infodir) = @_; $self->verify_checksum_filename($base, $self->fullname, $stash); } package OpenBSD::PackingElement::InfoFile; sub makesum_plist { my ($self, $plist, $base, $stash, $infodir) = @_; $self->SUPER::makesum_plist($plist, $base, $stash); my $fname = $self->fullname; for (my $i = 1; ; $i++) { if (-e "$base/$fname-$i") { my $e = OpenBSD::PackingElement::File->add($plist, $self->{name}."-".$i); $e->compute_checksum($e, $base, "$fname-$i", $stash); } else { last; } } } # 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 main; my %defines; sub dosubst { local $_ = shift; return $_ unless m/\$/; while (my ($k, $v) = each %defines) { s/\$\{\Q$k\E\}/$v/g; } s/\$\\/\$/g; return $_; } sub copy_subst_fh { my ($srcname, $dest) = @_; open my $src, '<', $srcname or die "can't open $srcname"; local $_; while (<$src>) { print $dest dosubst($_); } } sub copy_subst { my ($srcname, $destname) = @_; open my $dest, '>', $destname or die "can't open $destname"; copy_subst_fh($srcname, $dest); } our ($opt_p, $opt_f, $opt_c, $opt_d, $opt_v, $opt_i, $opt_k, $opt_r, $opt_S, $opt_h, $opt_s, $opt_O, $opt_A, $opt_L, $opt_m, $opt_M, $opt_U, $opt_P, $opt_W, $opt_n, $opt_B, $opt_q, $opt_Q); sub deduce_name { my ($o, $frag, $not) = @_; my $noto = $o; my $nofrag = "no-$frag"; $o =~ s/PFRAG\./PFRAG.$frag-/ or $o =~ s/PLIST/PFRAG.$frag/; $noto =~ s/PFRAG\./PFRAG.no-$frag-/ or $noto =~ s/PLIST/PFRAG.no-$frag/; 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 $opt_q; return $noto if -e $noto; } else { print "Switching to $o\n" if !defined $opt_q; return $o if -e $o; } return; } sub read_fragments { my ($plist, $filename) = @_; my $stack = []; push(@$stack, MyFile->new($filename)); return $plist->read($stack, sub { my ($stack, $cont) = @_; local $_; while(my $file = pop @$stack) { GETLINE: while ($_ = $file->readline) { if (m/^(\!)?\%\%(.*)\%\%$/) { my ($not, $frag) = ($1, $2); my $def = $frag; if ($frag eq 'SHARED') { $def = 'SHARED_LIBS'; $frag = 'shared'; } if (!defined $defines{$def}) { die "Error: unknown fragment $frag"; } elsif ($defines{$def} == 1) { next GETLINE if defined $not; } elsif ($defines{$def} == 0) { next GETLINE unless defined $not; } else { die "Incorrect define for $frag"; } my $newname = deduce_name($file->name, $frag, $not); if (defined $newname) { push(@$stack, $file); $file = MyFile->new($newname); } next GETLINE; } if (m/^(\@comment\s+\$(?:Open)BSD\$)$/) { $_ = '@comment $'.'OpenBSD: '.basename($file->name).',v$'; } if (m,^\@lib\s+.*/lib[^/]+\.so\.\d+\.\d+$,) { Warn "Shared library without SHARED_LIBS: $_"; $main::errors++; } &$cont(dosubst($_)); } } } ); } sub add_special_file { my ($plist, $name, $infodir, $opt) = @_; if (defined $opt) { OpenBSD::PackingElement::File->add($plist, $name); copy_subst($opt, $infodir.$name) if defined $infodir; } } sub add_description { my ($plist, $name, $infodir, $opt_c, $opt_d) = @_; OpenBSD::PackingElement::File->add($plist, $name); my $comment = $defines{COMMENT}; if (defined $comment) { $comment =~ s/^\"(.*)\"$/$1/ or $comment =~ s/^\'(.*)\'$/$1/; 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; } } elsif (!defined $opt_c) { Usage "Comment required"; } if (!defined $opt_d) { Usage "Description required"; } if (defined $infodir) { open(my $fh, '>', $infodir.$name) or die "Can't write to DESC: $!"; if (defined $comment) { print $fh dosubst($comment), "\n"; } else { if ($opt_c =~ /^\-/) { print $fh $', "\n"; } else { copy_subst_fh($opt_c, $fh); } } if ($opt_d =~ /^\-/) { print $fh $', "\n"; } else { copy_subst_fh($opt_d, $fh); } if (defined $comment) { print $fh "\n", dosubst('Maintainer: ${MAINTAINER}'), "\n\n"; if (defined $defines{HOMEPAGE}) { print $fh dosubst('WWW: ${HOMEPAGE}'), "\n"; } } close($fh); } } our $errors = 0; my (@contents, %dependencies, %wantlib); my $regen_package = 0; set_usage( 'pkg_create [-hnQqv] [-A arches] [-B pkg-destdir] [-D name=value]', '[-i iscript] [-k dscript] [-L localbase] [-M module] [-M displayfile]', '[-P pkg-dependency] [-p prefix] [-r rscript] [-S pkg-destdir]', '[-U undisplayfile] [-W wantedlib] -c desc -d desc -f packinglist pkg-name'); my $plist = new OpenBSD::PackingList; OpenBSD::PackingElement::File->add($plist, CONTENTS); try { getopts('hp:f:c:d:vi:k:r:m:M:U:S:hs:OA:L:B:D:P:W:nqQ', {'D' => sub { local $_ = shift; if (m/\=/) { $defines{$`} = $'; } else { $defines{$_} = 1; } }, 'f' => sub { push(@contents, shift); }, 'h' => sub { Usage(); }, 'P' => sub { my $d = shift; $dependencies{$d} = 1; }, 'W' => sub { my $w = shift; $wantlib{$w} = 1; } }); } catchall { Usage($_); }; if (@ARGV == 0) { $regen_package = 1; } elsif (@ARGV != 1) { Usage "Exactly one single package name is required: ", join(' ', @ARGV); } try { my $infodir; if (defined $opt_s) { Usage "Option s is no longer supported"; } if (defined $opt_O) { Usage "Option O is no longer supported"; } if (defined $opt_Q) { $opt_q = 1; } if (!@contents) { Usage "Packing list required"; } if ($regen_package) { if (@contents != 1) { Usage "Exactly one single packing list is required"; } if (-d $contents[0] && -f $contents[0].'/'.CONTENTS) { $infodir = $contents[0]; $contents[0] .= '/'.CONTENTS; } else { $infodir = dirname($contents[0]); } $plist->fromfile($contents[0]) or Fatal "Can't read packing list $contents[0]"; } else { print "Creating package $ARGV[0]\n" if !(defined $opt_q) && $opt_v; $infodir = OpenBSD::Temp::dir() unless $opt_q; add_description($plist, DESC, $infodir, $opt_c, $opt_d); add_special_file($plist, INSTALL, $infodir, $opt_i); add_special_file($plist, DEINSTALL, $infodir, $opt_k); add_special_file($plist, REQUIRE, $infodir, $opt_r); add_special_file($plist, DISPLAY, $infodir, $opt_M); add_special_file($plist, MODULE, $infodir, $opt_m); add_special_file($plist, UNDISPLAY, $infodir, $opt_U); if (defined $opt_p) { OpenBSD::PackingElement::Cwd->add($plist, $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 $opt_A) { OpenBSD::PackingElement::Arch->add($plist, $opt_A); } if (defined $opt_L) { OpenBSD::PackingElement::LocalBase->add($plist, $opt_L); } if ($ARGV[0] =~ m|([^/]+)$|) { my $pkgname = $1; $pkgname =~ s/\.tgz$//; OpenBSD::PackingElement::Name->add($plist, $pkgname); } my $fullpkgpath = $defines{'FULLPKGPATH'}; my $cdrom = $defines{'PERMIT_PACKAGE_CDROM'}; my $ftp = $defines{'PERMIT_PACKAGE_FTP'}; if (defined $fullpkgpath && defined $cdrom && defined $ftp) { $cdrom = 'yes' if $cdrom =~ m/^yes$/i; $ftp = 'yes' if $ftp =~ m/^yes$/i; OpenBSD::PackingElement::ExtraInfo->add($plist, $fullpkgpath, $cdrom, $ftp); } for my $contentsfile (@contents) { read_fragments($plist, $contentsfile) or Fatal "Can't read packing list $contentsfile"; } } my $base = '/'; if (defined $opt_B) { $base = $opt_B; } elsif (defined $opt_S) { $base = $opt_S; } elsif (defined $ENV{'PKG_PREFIX'}) { $base = $ENV{'PKG_PREFIX'}; } unless (defined $opt_q && defined $opt_n) { if ($regen_package) { $plist->verify_checksum($base, {}, $infodir); } else { my $p2 = OpenBSD::PackingList->new; $plist->makesum_plist($p2, $base, {}, $infodir); $plist = $p2; } } if (!defined $plist->{name}) { print STDERR "Can't write unnamed packing list\n"; exit 1; } if (defined $opt_q) { if (defined $opt_Q) { $plist->print_file; } else { $plist->write(\*STDOUT); } exit 0 if defined $opt_n; } if ($plist->{deprecated}) { print STDERR "Error: found obsolete constructs\n"; exit 1; } $plist->avert_duplicates_and_other_checks({}); if ($errors) { exit 1; } my $wname; if ($regen_package) { $wname = $plist->pkgname.".tgz"; } else { $plist->tofile($infodir.CONTENTS) or Fatal "Can't write packing list"; $wname = $ARGV[0]; } if ($opt_n) { my $dummy = OpenBSD::Ustar->new(undef, $infodir); $plist->pretend_to_archive($dummy, $base); } else { print "Creating gzip'd tar ball in '$wname'\n" if $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, "|gzip >$wname"); my $wrarc = OpenBSD::Ustar->new($fh, $infodir); $plist->create_package($wrarc, $base, $opt_v); $wrarc->close; if ($errors) { unlink($wname); exit(1); } } } catch { print STDERR "$0: $_\n"; exit(1); };