diff --git a/.github/workflows/zfs-tests-functional.yml b/.github/workflows/zfs-tests-functional.yml index 176180612aa1..5f5c28d2193e 100644 --- a/.github/workflows/zfs-tests-functional.yml +++ b/.github/workflows/zfs-tests-functional.yml @@ -23,9 +23,10 @@ jobs: sudo apt-get upgrade sudo xargs --arg-file=${{ github.workspace }}/.github/workflows/build-dependencies.txt apt-get install -qq sudo apt-get clean - echo "RPM2CPIO: $(which rpm2cpio)" - echo "CPIO: $(which cpio)" - echo "PKG: $(sudo apt list --installed | grep -E 'rpm|cpio')" + apt list --installed | grep -E 'cpio|alien|rpm' + echo "BASE ------" + sudo lzip -c /usr/share/perl5/Alien/Package/Rpm.pm | base64 + sudo cp ${{ github.workspace }}/Rpm.pm /usr/share/perl5/Alien/Package/Rpm.pm - name: Autogen.sh run: | @@ -35,7 +36,10 @@ jobs: ./configure --enable-debug --enable-debuginfo - name: Make run: | - make --no-print-directory -s pkg-utils pkg-kmod + make --no-print-directory -s pkg-utils pkg-kmod 2>&1 || true + echo "current files:" + ls -l + - name: Install run: | sudo dpkg -i *.deb diff --git a/Rpm.pm b/Rpm.pm new file mode 100644 index 000000000000..629fb1da9f22 --- /dev/null +++ b/Rpm.pm @@ -0,0 +1,800 @@ +#!/usr/bin/perl -w +use Cwd qw(); + +=head1 NAME + +Alien::Package::Rpm - an object that represents a rpm package + +=cut + +package Alien::Package::Rpm; +use strict; +use base qw(Alien::Package); + +=head1 DESCRIPTION + +This is an object class that represents a rpm package. It is derived from +Alien::Package. + +=head1 FIELDS + +=over 4 + +=item prefixes + +Relocatable rpm packages have a prefixes field. + +=back + +=head1 METHODS + +=over 4 + +=item checkfile + +Detect rpm files by their extension. + +=cut + +sub checkfile { + my $this=shift; + my $file=shift; + + return $file =~ m/.*\.rpm$/; +} + +=item install + +Install a rpm. If RPMINSTALLOPT is set in the environment, the options in +it are passed to rpm on its command line. + +=cut + +sub install { + my $this=shift; + my $rpm=shift; + + my $v=$Alien::Package::verbose; + $Alien::Package::verbose=2; + $this->do("rpm -ivh ".(exists $ENV{RPMINSTALLOPT} ? $ENV{RPMINSTALLOPT} : '').$rpm) + or die "Unable to install"; + $Alien::Package::verbose=$v; +} + +=item scan + +Implement the scan method to read a rpm file. + +=cut + +sub scan { + my $this=shift; + $this->SUPER::scan(@_); + my $file=$this->filename; + + my %fieldtrans=( + PREIN => 'preinst', + POSTIN => 'postinst', + PREUN => 'prerm', + POSTUN => 'postrm', + LICENSE => 'copyright', + ); + + # Use --queryformat to pull out all the fields we need. + foreach my $field (qw{NAME VERSION RELEASE ARCH CHANGELOGTEXT + SUMMARY DESCRIPTION PREFIXES}, + keys(%fieldtrans)) { + my $value=$this->runpipe(0, "LANG=C rpm -qp --queryformat \%{$field} '$file'"); + next if $? || $value eq '(none)'; + my $key; + if (exists $fieldtrans{$field}) { + $key=$fieldtrans{$field}; + } + else { + $key=lc($field); + } + $this->$key($value); + } + + # Get the conffiles list. + $this->conffiles([map { chomp; $_ } $this->runpipe(0, "LANG=C rpm -qcp '$file'")]); + if (defined $this->conffiles->[0] && + $this->conffiles->[0] eq '(contains no files)') { + $this->conffiles([]); + } + + $this->binary_info(scalar $this->runpipe(0, "rpm -qpi '$file'")); + + # Get the filelist. + $this->filelist([map { chomp; $_ } $this->runpipe(0, "LANG=C rpm -qpl '$file'")]); + if (defined $this->filelist->[0] && + $this->filelist->[0] eq '(contains no files)') { + $this->filelist([]); + } + + # Sanity check and sanitize fields. + unless (defined $this->summary) { + # Older rpms will have no summary, but will have a + # description. We'll take the 1st line out of the + # description, and use it for the summary. + $this->summary($this->description."\n")=~m/(.*?)\n/m; + + # Fallback. + if (! $this->summary) { + $this->summary('Converted RPM package'); + } + } + unless (defined $this->description) { + $this->description($this->summary); + } + unless (defined $this->copyright) { + # Older rpms have no licence tag, but have a copyright. + $this->copyright($this->runpipe(0, "LANG=C rpm -qp --queryformat \%{COPYRIGHT} '$file'")); + + # Fallback. + if (! $this->copyright) { + $this->copyright('unknown'); + } + } + if (! defined $this->release || ! defined $this->version || + ! defined $this->name) { + die "Error querying rpm file"; + } + + $this->distribution("Red Hat"); + $this->origformat("rpm"); + + return 1; +} + +=item unpack + +Implement the unpack method to unpack a rpm file. This is a little nasty +because it has to handle relocatable rpms and has to do a bit of +permissions fixing as well. + +=cut + +sub unpack { + my $this=shift; + $this->SUPER::unpack(@_); + my $workdir=$this->unpacked_tree; + my $path = Cwd::cwd(); + print "START: $path / $workdir\n"; + + # Check if we need to use lzma to uncompress the cpio archive + my $decomp=''; + if ($this->do("rpm2cpio '".$this->filename."' | lzma -t -q > /dev/null 2>&1")) { + $decomp = 'lzma -d -q |'; + } + print("DO: rpm2cpio '".$this->filename."' | (cd $workdir; $decomp cpio --extract --make-directories --no-absolute-filenames --preserve-modification-time) 2>&1 ::".Cwd::abs_path(). "\n"); + system("ls -l $workdir\n"); + + $this->do("pwd"); + $this->do("ls -l"); + print("WORKDIR files:"); + $this->do("ls -l $workdir"); + $this->do("rpm2cpio '".$this->filename."' | (cd $workdir; $decomp cpio --extract --make-directories --no-absolute-filenames --preserve-modification-time | tee /tmp/out) 2>&1 | tee /tmp/out2 ") + or die "Unpacking of '".$this->filename."' failed: ".$!; + + # cpio does not necessarily store all parent directories in an + # archive, and so some directories, if it has to make them and has + # no permission info, will come out with some random permissions. + # Find those directories and make them mode 755, which is more + # reasonable. + my %seenfiles; + open (RPMLIST, "rpm2cpio '".$this->filename."' | $decomp cpio -it --quiet |") + or die "File list of '".$this->filename."' failed"; + while () { + chomp; + $seenfiles{$_}=1; + } + close RPMLIST; + foreach my $file (`cd $workdir; find ./`) { + chomp $file; + if (! $seenfiles{$file} && -d "$workdir/$file" && ! -l "$workdir/$file") { + $this->do("chmod 755 '$workdir/$file'"); + } + } + + # If the package is relocatable. We'd like to move it to be under + # the $this->prefixes directory. However, it's possible that that + # directory is in the package - it seems some rpm's are marked as + # relocatable and unpack already in the directory they can relocate + # to, while some are marked relocatable and the directory they can + # relocate to is removed from all filenames in the package. I + # suppose this is due to some change between versions of rpm, but + # none of this is adequatly documented, so we'll just muddle + # through. + # + # Test to see if the package contains the prefix directory already. + if (defined $this->prefixes && ! -e "$workdir/".$this->prefixes) { + my $relocate=1; + + # Get the files to move. + my @filelist=glob("$workdir/*"); + + # Now, make the destination directory. + my $collect=$workdir; + foreach (split m:/:, $this->prefixes) { + if ($_ ne '') { # this keeps us from using anything but relative paths. + $collect.="/$_"; + if (-d $collect) { + # The package contains a parent + # directory of the relocation + # directory. Since it's impossible + # to move a parent directory into + # its child, bail out and do + # nothing. + $relocate=0; + last; + } + $this->do("mkdir", $collect) || die "unable to mkdir $collect: $!"; + } + } + + if ($relocate) { + # Now move all files in the package to the directory we made. + if (@filelist) { + $this->do("mv", @filelist, "$workdir/".$this->prefixes) + or die "error moving unpacked files into the default prefix directory: $!"; + } + + # Deal with relocating conffiles. + my @cf; + foreach my $cf (@{$this->conffiles}) { + $cf=$this->prefixes.$cf; + push @cf, $cf; + } + $this->conffiles([@cf]); + } + } + + # rpm files have two sets of permissions; the set in the cpio + # archive, and the set in the control data; which override them. + # The set in the control data are more correct, so let's use those. + # Some permissions setting may have to be postponed until the + # postinst. + my %owninfo = (); + my %modeinfo = (); + open (GETPERMS, 'rpm --queryformat \'[%{FILEMODES} %{FILEUSERNAME} %{FILEGROUPNAME} %{FILENAMES}\n]\' -qp \''.$this->filename.'\' |'); + while () { + chomp; + my ($mode, $owner, $group, $file) = split(/ /, $_, 4); + + next if -l "$workdir/$file"; + + $mode = $mode & 07777; # remove filetype + my $uid = getpwnam($owner); + if (! defined $uid || $uid != 0) { + $owninfo{$file}=$owner; + $uid=0; + } + my $gid = getgrnam($group); + if (! defined $gid || $gid != 0) { + if (exists $owninfo{$file}) { + $owninfo{$file}.=":$group"; + } + else { + $owninfo{$file}=":$group"; + } + $gid=0; + } + if (defined($owninfo{$file}) && (($mode & 07000) > 0)) { + $modeinfo{$file} = sprintf "%lo", $mode; + } + # Note that ghost files exist in the metadata but not + # in the cpio archive, so check that the file exists + # before trying to access it + if (-e "$workdir/$file") { + if ($> == 0) { + $this->do("chown", "$uid:$gid", "$workdir/$file") + || die "failed chowning $file to $uid\:$gid\: $!"; + } + $this->do("chmod", sprintf("%lo", $mode), "$workdir/$file") + || die "failed changing mode of $file to $mode\: $!"; + } + } + $this->owninfo(\%owninfo); + $this->modeinfo(\%modeinfo); + + return 1; +} + +=item prep + +Prepare for package building by generating the spec file. + +=cut + +sub prep { + my $this=shift; + my $dir=$this->unpacked_tree || die "The package must be unpacked first!"; + + # Exclude standard system directories + # This list is adapted from the rpmlint source code + my @standard_dirs = ( + "/", + "/bin/", + "/boot/", + "/etc/", + "/etc/X11/", + "/etc/opt/", + "/etc/profile.d/", + "/etc/skel/", + "/etc/xinetd.d/", + "/home/", + "/lib/", + "/lib/modules/", + "/lib64/", + "/media/", + "/mnt/", + "/mnt/cdrom/", + "/mnt/disk/", + "/mnt/floppy/", + "/opt/", + "/proc/", + "/root/", + "/run/", + "/sbin/", + "/selinux/", + "/srv/", + "/sys/", + "/tmp/", + "/usr/", + "/usr/X11R6/", + "/usr/X11R6/bin/", + "/usr/X11R6/doc/", + "/usr/X11R6/include/", + "/usr/X11R6/lib/", + "/usr/X11R6/lib64/", + "/usr/X11R6/man/", + "/usr/X11R6/man/man1/", + "/usr/X11R6/man/man2/", + "/usr/X11R6/man/man3/", + "/usr/X11R6/man/man4/", + "/usr/X11R6/man/man5/", + "/usr/X11R6/man/man6/", + "/usr/X11R6/man/man7/", + "/usr/X11R6/man/man8/", + "/usr/X11R6/man/man9/", + "/usr/X11R6/man/mann/", + "/usr/bin/", + "/usr/bin/X11/", + "/usr/etc/", + "/usr/games/", + "/usr/include/", + "/usr/lib/", + "/usr/lib/X11/", + "/usr/lib/games/", + "/usr/lib/gcc-lib/", + "/usr/lib/menu/", + "/usr/lib64/", + "/usr/lib64/gcc-lib/", + "/usr/local/", + "/usr/local/bin/", + "/usr/local/doc/", + "/usr/local/etc/", + "/usr/local/games/", + "/usr/local/info/", + "/usr/local/lib/", + "/usr/local/lib64/", + "/usr/local/man/", + "/usr/local/man/man1/", + "/usr/local/man/man2/", + "/usr/local/man/man3/", + "/usr/local/man/man4/", + "/usr/local/man/man5/", + "/usr/local/man/man6/", + "/usr/local/man/man7/", + "/usr/local/man/man8/", + "/usr/local/man/man9/", + "/usr/local/man/mann/", + "/usr/local/sbin/", + "/usr/local/share/", + "/usr/local/share/man/", + "/usr/local/share/man/man1/", + "/usr/local/share/man/man2/", + "/usr/local/share/man/man3/", + "/usr/local/share/man/man4/", + "/usr/local/share/man/man5/", + "/usr/local/share/man/man6/", + "/usr/local/share/man/man7/", + "/usr/local/share/man/man8/", + "/usr/local/share/man/man9/", + "/usr/local/share/man/mann/", + "/usr/local/src/", + "/usr/sbin/", + "/usr/share/", + "/usr/share/dict/", + "/usr/share/doc/", + "/usr/share/icons/", + "/usr/share/info/", + "/usr/share/man/", + "/usr/share/man/man1/", + "/usr/share/man/man2/", + "/usr/share/man/man3/", + "/usr/share/man/man4/", + "/usr/share/man/man5/", + "/usr/share/man/man6/", + "/usr/share/man/man7/", + "/usr/share/man/man8/", + "/usr/share/man/man9/", + "/usr/share/man/mann/", + "/usr/share/misc/", + "/usr/src/", + "/usr/tmp/", + "/var/", + "/var/cache/", + "/var/db/", + "/var/lib/", + "/var/lib/games/", + "/var/lib/misc/", + "/var/lib/rpm/", + "/var/local/", + "/var/log/", + "/var/mail/", + "/var/nis/", + "/var/opt/", + "/var/preserve/", + "/var/spool/", + "/var/spool/mail/", + "/var/tmp/" + ); + + # Place %config in front of files that are conffiles. + my @conffiles = @{$this->conffiles}; + my $filelist; + foreach my $fn (@{$this->filelist}) { + if (! grep(m:^\Q$fn\E$:,@standard_dirs)) { + # Unquote any escaped characters in filenames - needed for + # non ascii characters. (eg. iso_8859-1 latin set) + if ($fn =~ /\\/) { + $fn=eval qq{"$fn"}; + } + + # Note all filenames are quoted in case they contain + # spaces. + if ($fn =~ m:/$:) { + $filelist.=qq{%dir "$fn"\n}; + } + elsif (grep(m:^\Q$fn\E$:,@conffiles)) { # it's a conffile + $filelist.=qq{%config "$fn"\n}; + } + else { # normal file + $filelist.=qq{"$fn"\n}; + } + } + } + + # Write out the spec file. + my $spec="$dir/".$this->name."-".$this->version."-".$this->release.".spec"; + open (OUT, ">$spec") || die "$spec: $!"; + my $pwd=`pwd`; + chomp $pwd; + print OUT "Buildroot: $pwd/$dir\n"; # must be absolute dirname + print OUT "Name: ".$this->name."\n"; + print OUT "Version: ".$this->version."\n"; + print OUT "Release: ".$this->release."\n"; + print OUT "Requires: ".$this->depends."\n" + if defined $this->depends && length $this->depends; + print OUT "Summary: ".$this->summary."\n"; + print OUT "License: ".$this->copyright."\n"; + print OUT "Distribution: ".$this->distribution."\n"; + print OUT "Group: Converted/".$this->group."\n"; + print OUT "\n"; + print OUT "\%define _rpmdir ../\n"; # write rpm to current directory + print OUT "\%define _rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.%%{ARCH}.rpm\n"; + print OUT "\%define _unpackaged_files_terminate_build 0\n"; # work on SuSE + print OUT "\n"; + if ($this->usescripts) { + if ($this->preinst) { + print OUT "\%pre\n"; + print OUT $this->preinst."\n"; + print OUT "\n"; + } + if ($this->postinst) { + print OUT "\%post\n"; + print OUT $this->postinst."\n"; + print OUT "\n"; + } + if ($this->prerm) { + print OUT "\%preun\n"; + print OUT $this->prerm."\n"; + print OUT "\n"; + } + if ($this->postrm) { + print OUT "\%postun\n"; + print OUT $this->postrm."\n"; + print OUT "\n"; + } + } + print OUT "\%description\n"; + print OUT $this->description."\n"; + print OUT "\n"; + print OUT "(Converted from a ".$this->origformat." package by alien version $Alien::Version.)\n"; + print OUT "\n"; + print OUT "%files\n"; + print OUT $filelist if defined $filelist; + close OUT; +} + +=item cleantree + +Delete the spec file. + +=cut + +sub cleantree { + my $this=shift; + my $dir=$this->unpacked_tree || die "The package must be unpacked first!"; + + unlink "$dir/".$this->name."-".$this->version."-".$this->release.".spec"; +} + +=item build + +Build a rpm. If RPMBUILDOPT is set in the environment, the options in +it are passed to rpm on its command line. + +An optional parameter, if passed, can be used to specify the program to use +to build the rpm. It defaults to rpmbuild. + +=cut + +sub build { + my $this=shift; + my $buildcmd=shift || 'rpmbuild'; + my $dir=$this->unpacked_tree || die "The package must be unpacked first!"; + + # Ask rpm how it's set up. We want to know where it will place rpms. + my $rpmdir; + foreach ($this->runpipe(1, "rpm --showrc")) { + chomp; + if (/^rpmdir\s+:\s(.*)$/) { + $rpmdir=$1; + } + } + + my $rpm=$this->name."-".$this->version."-".$this->release.".".$this->arch.".rpm"; + my $opts=''; + if ($rpmdir) { + # Old versions of rpm toss it off in the middle of nowhere. + $rpm="$rpmdir/".$this->arch."/$rpm"; + + # This is the old command line argument to set the arch. + $opts="--buildarch ".$this->arch; + } + else { + # Presumably we're dealing with rpm 3.0 or above, which + # doesn't output rpmdir in any format I'd care to try to + # parse. Instead, rpm is now of a late enough version to + # notice the %define's in the spec file, that will make the + # file end up in the directory we started in. + # Anyway, let's assume this is version 3 or above. + + # This is the new command line arcgument to set the arch + # rpms. It appeared in rpm version 3. + $opts="--target ".$this->arch; + } + + $opts.=" $ENV{RPMBUILDOPT}" if exists $ENV{RPMBUILDOPT}; + my $pwd=`pwd`; + chomp $pwd; + my $command="cd $dir; $buildcmd --buildroot='$pwd/$dir' -bb $opts '".$this->name."-".$this->version."-".$this->release.".spec'"; + my $log=$this->runpipe(1, "$command 2>&1"); + if ($?) { + die "Package build failed. Here's the log of the command ($command):\n", $log; + } + + return $rpm; +} + +=item version + +Set/get version. + +When retreiving the version, remove any dashes in it. + +=cut + +sub version { + my $this=shift; + + # set + $this->{version} = shift if @_; + + # get + return unless defined wantarray; # optimization + $_=$this->{version}; + tr/-/_/; + return $_; +} + +=item postinst + +=item postrm + +=item preinst + +=item prerm + +Set/get script fields. + +When retrieving a value, we have to do some truly sick mangling. Since +debian/slackware scripts can be anything -- perl programs or binary files +-- and rpm is limited to only shell scripts, we need to encode the files +and add a scrap of shell script to make it unextract and run on the fly. + +When setting a value, we do some mangling too. Rpm maintainer scripts +are typically shell scripts, but often lack the leading shebang line. +This can confuse dpkg, so add the shebang if it looks like there +is no shebang magic already in place. + +Additionally, it's not uncommon for rpm maintainer scripts to contain +bashisms, which can be triggered when they are ran on systems where /bin/sh +is not bash. To work around this, the shebang line of the scripts is +changed to use bash. + +Also, if the rpm is relocatable, the script could refer to +RPM_INSTALL_PREFIX, which is set by rpm at run time. Deal with this by +adding code to the script to set RPM_INSTALL_PREFIX. + +=cut + +# This helper function deals with all the scripts. +sub _script_helper { + my $this=shift; + my $script=shift; + + # set + if (@_) { + my $prefixcode=""; + if (defined $this->prefixes) { + $prefixcode="RPM_INSTALL_PREFIX=".$this->prefixes."\n"; + $prefixcode.="export RPM_INSTALL_PREFIX\n"; + } + + my $value=shift; + if (length $value and $value !~ m/^#!\s*\//) { + $value="#!/bin/bash\n$prefixcode$value"; + } + else { + $value=~s@^#!\s*/bin/sh(\s)@#!/bin/bash$1@; + $value=~s/\n/\n$prefixcode/s; + } + $this->{$script} = $value; + } + $this->{$script} = shift if @_; + + # get + return unless defined wantarray; # optimization + $_=$this->{$script}; + return '' unless defined $_; + return $_ if m/^\s*$/; + return $_ if m/^#!\s*\/bin\/sh/; # looks like a shell script already + my $f = pack("u",$_); + $f =~ s/%/%%/g; # Rpm expands %S, so escape such things. + return "#!/bin/sh\n". + "set -e\n". + "mkdir /tmp/alien.\$\$\n". + qq{perl -pe '\$_=unpack("u",\$_)' << '__EOF__' > /tmp/alien.\$\$/script\n}. + $f."__EOF__\n". + "chmod 755 /tmp/alien.\$\$/script\n". + "/tmp/alien.\$\$/script \"\$@\"\n". + "rm -f /tmp/alien.\$\$/script\n". + "rmdir /tmp/alien.\$\$"; +} +sub postinst { + my $this=shift; + $this->_script_helper('postinst', @_); +} +sub postrm { + my $this=shift; + $this->_script_helper('postrm', @_); +} +sub preinst { + my $this=shift; + $this->_script_helper('preinst', @_); +} +sub prerm { + my $this=shift; + $this->_script_helper('prerm', @_); +} + +=item arch + +Set/get arch field. When the arch field is set, some sanitizing is done +first to convert it to the debian format used internally. When it's +retrieved it's converted back to rpm form from the internal form. + +=cut + +sub arch { + my $this=shift; + + my $arch; + if (@_) { + $arch=shift; + + if ($arch eq 1) { + $arch='i386'; + } + elsif ($arch eq 2) { + $arch='alpha'; + } + elsif ($arch eq 3) { + $arch='sparc'; + } + elsif ($arch eq 6) { + $arch='m68k'; + } + elsif ($arch eq 'noarch') { + $arch='all'; + } + elsif ($arch eq 'ppc') { + $arch='powerpc'; + } + elsif ($arch eq 'x86_64') { + $arch='amd64'; + } + elsif ($arch eq 'em64t') { + $arch='amd64'; + } + elsif ($arch =~ m/i\d86/i || $arch =~ m/pentium/i) { + # Treat 486, 586, etc, as 386. + $arch='i386'; + } + elsif ($arch eq 'armv4l') { + # Treat armv4l as arm. + $arch='arm'; + } + elsif ($arch eq 'armv7l') { + # Treat armv7l as armel. + $arch='armel'; + } + elsif ($arch eq 'aarch64') { + # Treat aarch64 as arm64. + $arch='arm64'; + } + elsif ($arch eq 'parisc') { + $arch='hppa'; + } + elsif ($arch eq 'ppc64le') { + $arch='ppc64el'; + } + + $this->{arch}=$arch; + } + + $arch=$this->{arch}; + if ($arch eq 'amd64') { + $arch='x86_64'; + } + elsif ($arch eq 'powerpc') { + # XXX is this the canonical name for powerpc on rpm + # systems? + $arch='ppc'; + } + elsif ($arch eq 'hppa') { + $arch='parisc'; + } + elsif ($arch eq 'all') { + $arch='noarch'; + } + elsif ($arch eq 'ppc64el') { + $arch='ppc64le'; + } + + return $arch +} + +=back + +=head1 AUTHOR + +Joey Hess + +=cut + +1