# RPM Package Management System -*-perl-*-
# Copyright (C) 1995 Red Hat, Inc
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

# binary and source package building routines

# This needs to match the pack() call in gen_header EXACTLY
$package_header_length = 104;

$date = 0;
$host = "";

sub gen_header {
    local ( *spec, $type, $x, $speci_len, $size ) = @_;
    local ( $result, $typeb, $cpu, $name, $archive_offset );
    local ( $speci_offset, $os, $group_len, $icon_len );
    local ( @s, $ifn );

    if ($type ne "bin") {
	$typeb = 1;
	$cpu = 0;
	$os = 0;
    } else {
	$typeb = 0;
	$cpu = `uname -m`;
	if ($cpu =~ /^i.86/) {
	    $cpu = 1;
	} else {
	    &error("unknown architecture: $arch");
	}
	$os = `uname`;
	if ($os =~ /linux/i) {
	    $os = 1;
	} else {
	    &error("unknown operating system: $os");
	}
    }

    $group_len = length($spec{"subpackage:$x:group"});

    $icon_len = 0;
    if ($spec{"subpackage:$x:icon"}) {
	$ifn = $rpm{"sourcedir"} . "/" . $spec{"subpackage:$x:icon"};
	@s = stat($ifn);
	if (@s) {
	    $icon_len = $s[7];
	}
    }

    $name = $spec{"name"};
    if ($spec{"subpackage:$x:name"}) {
	$name .= "-" . $spec{"subpackage:$x:name"};
    }
    $name .= "-" . $spec{"version"} . "-" . $spec{"release"};

    $name = substr($name, 0, 64);

    # This stuff gets stuck in later by the packaging scripts
    $archive_offset = $package_header_length;
    $speci_len = 0x00000000;

    $speci_offset = $package_header_length + $group_len + $icon_len;

    $result = pack("CCCC CC CC CC a66 N N N N N N N", 
		   0xed, 0xab, 0xee, 0xdb,
		   0x01, 0x01,
		   0x00, $typeb,
		   0x00, $cpu,
		   $name,
		   $speci_offset, $speci_len, $archive_offset, 
		   $size, $os, $group_len, $icon_len);

    # Add the group
    $result .= $spec{"subpackage:$x:group"};

    # Add the icon
    if ($icon_len > 0) {
	open(FD, $ifn);
	while (<FD>) {
	    $result .= $_;
	}
	close(FD);
    }

    return $result;
}

sub write_pack_bin_script {
    local ($script, *spec, *pspec) = @_;
    local ( $i, $j );
    local ( $name, $subname, $version, $release, $path, $info );
    local ( $pre, $post, $preun, $postun, $pf, $isconf, $isdoc );
    local ( $cpio_stderr_fn, $cpio_opts );
    local ( @files, %filesdoc, %filesconf );
    local ( %md5sum_array );
    local ( $lastpath );
    local ( $cpu );
    local ( $filelist_fn, $speci_fn, $cpio_stderr_fn, $preamble );
    local ( $header, $header_fn, $pad, $size, @infoarray, $speci_filelist );
    local ( $l_header, $cputoken );

    &debug("ENTERING: write_pack_bin_script()");

    $name = $spec{"name"};
    $version = $spec{"version"};
    $release = $spec{"release"};

    open(OF, ">$script");
    print OF "#!/bin/sh -e\n";
    print OF "
# Binary package script generated by rpm $rpm{'version'} on $date
# package name: $name
#      version: $version
#      release: $release
#  subpackageC: $spec{'subpackageC'}
";
    if (&isverbose) {
    print OF "
set -x

";
}
    
    $date = time;
    $host = `hostname -fqdn`;
    $cpu = `uname -m`;
    chop($host);
    chop($cpu);
    $cputoken = $cpu;
    $cputoken =~ s/i.86/i386/;
    foreach $i (0 .. $spec{"subpackageC"} - 1) {
	$filelist_fn = &tempname();
	$speci_fn = &tempname();
	$cpio_stderr_fn = &tempname();
	$header_fn = &tempname();
	$subname = $spec{"subpackage:$i:name"};
	$preamble = $spec{"subpackage:$i:preamble"};

	# Add stuff from the main preamble that we want in all preambles
	if ($i) {
	    foreach (split(/\n/, $spec{"subpackage:0:preamble"})) {
		if (/^source([0-9]*):/i ||
		    /^patch([0-9]*):/i ||
		    /^copyright:/i ||
		    /^vendor:/i ||
		    /^distribution:/i) {
		    $preamble .= "$_\n";
		}
	    }
	}

	$preamble .= "CpuType: $cpu\n";
	$preamble .= "BuildDate: $date\n";
	$preamble .= "BuildHost: $host\n";
	$preamble .= "PreambleVersion: 1\n";

	print OF "
#
# subpackage = $subname
# filelist = $filelist_fn
#    speci = $speci_fn
#   header = $header_fn
#

cat <<\"..EOF..\" > $speci_fn
%package $subname
name: $name
version: $version
release: $release
$preamble";

	@files = ();
	%filesdoc = ();
	%filesconf = ();

	# Now we generate an internal file list, expanding
	# directories, etc.  This list is used to generate
	# the speci and filelist.

	# The internal file list will *not* have $rpm{'root'}
	# prepended to it!

	&expand_filelist(*spec, *pspec, $i, *files, *filesconf, *filesdoc);

	# generate the %speci (the file list) section of speci file
	@files = sort @files;
	# md5sum_array() should follow $rpm{'root'}
	&md5sum_array(*files, *md5sum_array, 0);
	# The speci list is reverse sorted, for easy removal
	$lastpath = "";
	$size = 0;
	$speci_filelist = "";
	foreach $path (reverse @files) {
	    if ($path eq $lastpath) {
		&error("file listed twice: $path");
	    }
	    $lastpath = $path;
	    # stat_info_array() should follow $rpm{'root'}
	    $info = &stat_info_array($path, $filesconf{$path}, $filesdoc{$path}, *md5sum_array);
	    @infoarray = split(/[ \t\n]+/, $info);
	    $size += $infoarray[0];
	    if ($info eq "missing") {
		&error("file missing: $path");
	    }
	    &time_check($path, $info);
	    $speci_filelist .= "$path $info\n";
	}

	# Now that we have the size we finish off the speci
	print OF "size: $size\n";

	# pre script
	if ($pspec{"pre:$i"}) {
	    $pre = $pspec{"pre:$i"};
	    print OF "%pre $subname\n$pre";
	}
	# post script
	if ($pspec{"post:$i"}) {
	    $post = $pspec{"post:$i"};
	    print OF "%post $subname\n$post";
	}
	# preun script
	if ($pspec{"preun:$i"}) {
	    $preun = $pspec{"preun:$i"};
	    print OF "%preun $subname\n$preun";
	}
	# postun script
	if ($pspec{"postun:$i"}) {
	    $postun = $pspec{"postun:$i"};
	    print OF "%postun $subname\n$postun";
	}

	# and finally the speci filelist
	print OF "%speci $subname\n";
	print OF $speci_filelist;
	print OF "..EOF..\n";

	# At this point the speci file is made
	# Now we just need to create the file list
	print OF "\ncat <<\"..EOF..\" > $filelist_fn\n";
	# The speci is now in the header
	# print OF "$speci_fn\n";
	# The file list is plain sorted, for best cpio --extract
	foreach $path (@files) {
	    # strip leading "/" for chroots
	    $path =~ s/^\///;
	    print OF "$path\n";
	}
	print OF "..EOF..\n";

	# Now just assemble the header

	$header = &gen_header(*spec, "bin", $i, 0, $size);
	open(PF, ">$header_fn");
	print PF $header;
	close(PF);
	$l_header = length($header);

	# Get the speci size, pad it to 4 bytes and insert in header
	print OF "SPECSIZE=`ls -l $speci_fn | awk '{print \$5}'`\n";
	print OF "perl -e 'print \"\\0\" x ((4 - (\$ARGV[0] % 4)) % 4);' \$SPECSIZE >> $speci_fn\n";
	print OF "perl -e 'print pack(\"NN\", \$ARGV[0], \$ARGV[0] + ((4 - (\$ARGV[0] % 4)) % 4) + $l_header);' \$SPECSIZE | dd conv=notrunc bs=1 count=8 seek=80 of=$header_fn\n";
	
	# Now we just need to build the package
	if ($subname) {
	    $pf = "$name-$subname-$version-$release.$cputoken$rpm{'binext'}";
	} else {
	    $pf = "$name-$version-$release.$cputoken$rpm{'binext'}";
	}

	# If we want to build from some chroot(), do it here
	print OF "cd $rpm{'root'}/\n";
	if (! &isquiet) {
	    print OF "echo -n \"$pf: \"\n";
	}

	# Finish off the output filename
	if ($rpm{'arch_sensitive'}) {
	    $pf = $rpm{'rpmdir'} . "/" . $cputoken . "/" . $pf;
	} else {
	    $pf = $rpm{'rpmdir'} . "/" . $pf;
	}

	if (&isverbose) {
	    $cpio_opts = "-ovH";
	} else {
	    $cpio_opts = "-oH";
	}
	print OF "cpio $cpio_opts crc < $filelist_fn 2> $cpio_stderr_fn | gzip -c9f | cat $header_fn $speci_fn - > $pf\n";
	print OF "if [ \$? -ne 0 ]; then\n";
	print OF "  exit $?\n";
	print OF "fi\n";

	if (&isverbose) {
	    print OF "cat $cpio_stderr_fn\n";
	} elsif (! &isquiet) {
	    # Normal verbosity
	    print OF "grep '[0-9][0-9]* blocks' $cpio_stderr_fn\n";
	}

	# This is where we check for errors since cpio is
	# bone headed and doesn't return a non zero exit code
	# if it can't find a file.  Who wrote cpio anyway?
	print OF "if grep 'No such file' $cpio_stderr_fn ; then\n";
	print OF "  exit 1;\n";
	print OF "fi\n";

	if (! $rpm{'keeptemps'}) {
	    print OF "rm -f $header_fn\n";
	    print OF "rm -f $speci_fn\n";
	    print OF "rm -f $filelist_fn\n";
	    print OF "rm -f $cpio_stderr_fn\n";
	}
    }
    print OF "exit 0\n";
    close OF;
    chmod 0755, $script;

    &debug("EXITING: write_pack_bin_script()");
}

sub write_pack_src_script {
    local ($script, $specfile, *spec, *spec_source, *spec_patch) = @_;
    local ( $tmpdir, $tmpscript, $tmp, $preamble, $size, @stat, $i );
    local ( $filelist, $cpio_stderr_fn, $header_fn, $speci_fn, $cpio_opts );

    &debug("ENTERING: write_pack_src_script()");

    $tmpdir = &tempname();
    $tmpscript = &tempname();
    $filelist = &tempname();
    $header_fn = &tempname();
    $speci_fn = &tempname();
    $cpio_stderr_fn = &tempname();

    $size = 0;

    open(OF, ">$script");
    print OF "#!/bin/sh -e\n";
    print OF "# Source package script generated by rpm $rpm{'version'} on $date\n";
    print OF "# package name: $spec{'name'}\n";
    print OF "#      version: $spec{'version'}\n";
    print OF "#      release: $spec{'release'}\n";

    if (&isverbose) {
	print OF "\nset -x\n\n";
    }

    print OF "mkdir -p $tmpdir\n";
    print OF "cd $tmpdir\n";
    print OF "touch $filelist\n";
    
    # Add spec file
    $specfile =~ s/.*\/(.*)/$1/;
    print OF "ln -s $rpm{'specdir'}/$specfile $tmpdir/$specfile\n";
    print OF "echo '$specfile' >> $filelist\n";
    @stat = stat("$rpm{'specdir'}/$specfile");
    $size += $stat[7];

    # Add icon file(s)
    foreach $i (0 .. $spec{"subpackageC"} - 1) {
        if ($spec{"subpackage:$i:icon"}) {
	    $tmp = $spec{"subpackage:$i:icon"};
	    if (! &member($tmp, @the_icons)) {
		print OF "ln -s $rpm{'sourcedir'}/$tmp $tmpdir/$tmp\n";
		print OF "echo '$tmp' >> $filelist\n";
		@stat = stat("$rpm{'sourcedir'}/$tmp");
		$size += $stat[7];
	    }
	}
    }

    # Add all source and patch files
    foreach $tmp (values %spec_source, values %spec_patch) {
	$tmp =~ s/.*\/(.*)/$1/;
	print OF "echo '$tmp' >> $filelist\n";
	print OF "ln -s $rpm{'sourcedir'}/$tmp $tmpdir/$tmp\n";
	@stat = stat("$rpm{'sourcedir'}/$tmp");
	$size += $stat[7];
    }

    # Generate the header
    $header = &gen_header(*spec, "src", 0, 0, $size);
    open(PF, ">$header_fn");
    print PF $header;
    close(PF);
    $l_header = length($header);

    # Generate a minimal speci
    # $date and $host are already set from the binary packaging
    $preamble = "Name: $spec{'name'}\n";
    $preamble .= "Version: $spec{'version'}\n";
    $preamble .= "Release: $spec{'release'}\n";
    $preamble .= $spec{"subpackage:0:preamble"};
    # Add stuff from the main preamble
    foreach (split(/\n/, $spec{"subpackage:0:preamble"})) {
	if (/^source([0-9]*):/i ||
	    /^patch([0-9]*):/i ||
	    /^copyright:/i ||
	    /^vendor:/i ||
	    /^distribution:/i) {
	    $preamble .= "$_\n";
	}
    }
    $preamble .= "CpuType: $cpu\n";
    $preamble .= "BuildDate: $date\n";
    $preamble .= "BuildHost: $host\n";
    $preamble .= "Size: $size\n";
    $preamble .= "PreambleVersion: 1\n";
    print OF "cat <<\"..EOF..\" > $speci_fn\n";
    print OF "$preamble";
    print OF "..EOF..\n";

    # Get the speci size, pad it to 4 bytes and insert in header
    print OF "SPECSIZE=`ls -l $speci_fn | awk '{print \$5}'`\n";
    print OF "perl -e 'print \"\\0\" x ((4 - (\$ARGV[0] % 4)) % 4);' \$SPECSIZE >> $speci_fn\n";
    print OF "perl -e 'print pack(\"NN\", \$ARGV[0], \$ARGV[0] + ((4 - (\$ARGV[0] % 4)) % 4) + $l_header);' \$SPECSIZE | dd conv=notrunc bs=1 count=8 seek=80 of=$header_fn\n";

    # Package it all up
    $tmp = "$spec{'name'}-$spec{'version'}-$spec{'release'}$rpm{'srcext'}";
    if (! &isquiet) {
	print OF "echo -n \"$tmp: \"";
    }
    if (&isverbose) {
	$cpio_opts = "-ovLH";
    } else {
	$cpio_opts = "-oLH";
    }
    print OF "\ncpio $cpio_opts crc < $filelist 2> $cpio_stderr_fn | gzip -c9f | cat $header_fn $speci_fn - > $rpm{'srcrpmdir'}/$tmp\n";
    print OF "if [ \$? -ne 0 ]; then\n";
    print OF "  exit $?\n";
    print OF "fi\n";

    if (&isverbose) {
	print OF "cat $cpio_stderr_fn\n";
    } elsif (! &isquiet) {
	# Normal verbosity
	print OF "grep '[0-9][0-9]* blocks' $cpio_stderr_fn\n";
    }

    # This is where we check for errors since cpio is
    # bone headed and doesn't return a non zero exit code
    # if it can't find a file.  Who wrote cpio anyway?
    print OF "if grep 'No such file' $cpio_stderr_fn ; then\n";
    print OF "  exit 1;\n";
    print OF "fi\n";

    print OF "\ncd ..\n";
    print OF "rm -rf $tmpdir\n";
    if (! $rpm{'keeptemps'}) {
	print OF "rm -f $header_fn\n";
	print OF "rm -f $filelist\n";
	print OF "rm -f $cpio_stderr_fn\n";
	print OF "rm -f $speci_fn\n";
    }
    print OF "\nexit 0\n";
    close OF;
    chmod 0755, $script;

    &debug("EXITING: write_pack_src_script()");
}

sub expand_filelist {
    local ( *spec, *pspec, $i, *files, *filesconf, *filesdoc ) = @_;
    local ( $name, $version, $release, $isconf, $isdoc, $j, $path );

    @files = ();
    %filesdoc = ();
    %filesconf = ();

    $name = $spec{"name"};
    $version = $spec{"version"};
    $release = $spec{"release"};

    # Now we generate an internal file list, expanding
    # directories, etc.  This list is used to generate
    # the speci and filelist.

    # The file list will *not* have $rpm{'root'}
    # prepended to it!

    foreach $j (0 .. $spec{"subpackage:$i:fileC"} - 1) {
	$path = $spec{"subpackage:$i:file:$j:path"};
	if ($rpm{'root'}) {
	    $path = $rpm{'root'} . $path;
	}
	if ((-d $path) && (! $pspec{"subpackage:$i:file:$j:isdir"})) {
	    &debug("execing: find $path -print");
	    open(FIND, "find $path -print |");
	    while (<FIND>) {
		chop;
		if ($rpm{'root'}) {
		    s/^$rpm{'root'}//;
		}
		push(@files, $_);
		$filesdoc{$_} = &is_doc($_) || $pspec{"subpackage:$i:file:$j:isdoc"};
		$filesconf{$_} = $pspec{"subpackage:$i:file:$j:isconf"};
	    }
	    close FIND;
	    &debug("done: find $path -print");
	} else {
	    foreach $_ (&rpmglob($path)) {
		if ($rpm{'root'}) {
		    s/^$rpm{'root'}//;
		}
		push(@files, $_);
		$filesdoc{$_} = &is_doc($_) || $pspec{"subpackage:$i:file:$j:isdoc"};
		$filesconf{$_} = $pspec{"subpackage:$i:file:$j:isconf"};
	    }
	}
    }	    
    # Now add any %doc macro files to the internal file list
    if ($pspec{"doc:$i"}) {
	# Add the doc directory first
	push(@files, "$rpm{'docdir'}/$name-$version-$release");
    }
    foreach $j (split(/[ \n\t]+/, $pspec{"doc:$i"})) {
	$j =~ s/.*\/(.*)/$1/;
	next if (! $j);
	$path = "$rpm{'docdir'}/$name-$version-$release/$j";
	if ($rpm{'root'}) {
	    $path = $rpm{'root'} . $path;
	}
	&debug("doc: $path");
	foreach $path (&rpmglob($path)) {
	    if (-d $path) {
		&debug("execing: find $path -print");
		open(FIND, "find $path -print |");
		while (<FIND>) {
		    chop;
		    if ($rpm{'root'}) {
			s/^$rpm{'root'}//;
		    }
		    push(@files, $_);
		    if (! (-d $_)) {
			$filesdoc{$_} = 1;
		    }
		}
		close FIND;
	    } else {
		if ($rpm{'root'}) {
		    $path =~ s/^$rpm{'root'}//;
		}
		push(@files, $path);
		$filesdoc{$path} = 1;
	    }
	}
    }

    # The list is fully expanded
}

sub time_check {
    local ( $path, $info ) = @_;

    return if ($rpm{'timecheck'} == 0);

    @foo = split(/[ \t\n]+/, $info);
    $time = time - $foo[1];
    if ($rpm{'timecheck'} < $time) {
	&warning("TIMECHECK: $path is over $rpm{'timecheck'} seconds old");
    }
}

sub list_check {
    local (*spec, *pspec) = @_;
    local ( $i, $j );
    local ( $name, $subname, $version, $release );
    local ( $failed, $info, $path, $lastpath );
    local ( @files, %filesdoc, %filesconf, %md5sum_array );

    $name = $spec{"name"};
    $version = $spec{"version"};
    $release = $spec{"release"};

    $failed = 0;

    foreach $i (0 .. $spec{"subpackageC"} - 1) {
	$subname = $spec{"subpackage:$i:name"};
	if ($subname) {
	    &normal("List check: $name-$subname-$version-$release");
	} else {
	    &normal("List check: $name-$version-$release");
	}

	&verbose("file size date md5sum mode uid gid isconf isdoc dev symlink");

	# Expand the file list
	&expand_filelist(*spec, *pspec, $i, *files, *filesconf, *filesdoc);

	@files = sort @files;

	# md5sum_array() should follow $rpm{'root'}
	&md5sum_array(*files, *md5sum_array, 0);
	$lastpath = "";
	foreach $path (@files) {
	    if ($path eq $lastpath) {
		$failed |= 1;
		&normal("file listed twice: $path");
	    }
	    $lastpath = $path;
	    # stat_info_array() should follow $rpm{'root'}
	    $info = &stat_info_array($path, $filesconf{$path}, $filesdoc{$path}, *md5sum_array);
	    if ($info eq "missing") {
		$failed |= 1;
		&normal("file missing: $path");
	    } else {
		&time_check($path, $info);
	    }
	    &verbose("$path $info");
	}
    }

    return $failed;
}

1;
