#!/usr/local/bin/perl -w

use strict;

my @heredocs = ("");

sub copy_file {
    my $destination = shift;
    my $source = shift;

    if (1) {
	open(OUTFILE, ">$destination") || die "Unable to open >$destination";
    } else {
	print "FAKE: Copying to $destination\n";
	open(OUTFILE, ">/dev/null") || die "Unable to open >/dev/null";
    }
    if ($source =~ /\${HEREDOC:(\d+)}/) {
	# Copy from a here-document
	print OUTFILE $heredocs[$1];
    } else {
	# Copy from a real file
	open(INFILE, "<$source") || die "Unable to open <$source";
	while(<INFILE>) {
	    print OUTFILE $_;
	}
	close(INFILE) || die "Read error in $source";
    }
    close(OUTFILE) || die "Write error in $destination";
}



#require "find.pl";
#
## Traverse desired filesystems
#
#&find('.');
#
#exit;

use File::Find ();
#    &File::Find::find(\&wanted, @_);
#&File::Find::find(
#   sub {
#       (($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) &&
#	   --d _ &&
#	       ! /^CVS$/;
#   }, "directory-to-search-in", "more-dirs-to-search");

MAIN: {
    if ($#ARGV != 1) {
	print STDERR "Usage: $0 <destination disk> <configfile>\n";
	print STDERR "WARNING: This program will ERASE the destination disk\n";
	exit 1;
    }
    my $ICDEV = shift;
    my $CONFIGNAME = $ARGV[0];

    my $cwd = `pwd`;
    chomp $cwd;
    my $OBJDIR = `cd ../.. && pwd`;
    chomp $OBJDIR;
    $OBJDIR = "/usr/obj/aout" . $OBJDIR;
    my $TOOLROOT = "${OBJDIR}/tmp";
    my $oldpath = $ENV{PATH};
    my $DESTDIR = "$cwd/boxbuild";
    my $i;
    my $config = {};

    $ENV{PATH} = "${TOOLROOT}/sbin:${TOOLROOT}/bin:${TOOLROOT}/usr/sbin:" .
	"${TOOLROOT}/usr/bin:${TOOLROOT}/usr/games:$oldpath";
    $ENV{OBJFORMAT_PATH} = "${TOOLROOT}/usr/libexec";
    # COMPILER_ENV
    $ENV{BISON_SIMPLE} =    "${TOOLROOT}/usr/share/misc/bison.simple";
    $ENV{COMPILER_PATH} =   "${TOOLROOT}/usr/libexec:${TOOLROOT}/usr/bin";
    $ENV{GCC_EXEC_PREFIX} = "${TOOLROOT}/usr/lib/";
    $ENV{LD_LIBRARY_PATH} = "${TOOLROOT}/usr/lib";
    $ENV{LIBRARY_PATH}    = "${TOOLROOT}/usr/lib";

    if (!-e "/dev/r${ICDEV}") {
	print STDERR "No such device: /dev/r${ICDEV}\n";
	print STDERR "WARNING: Will FULLY OVERRIDE the destination device\n";
	exit 1;
    }


    #
    # Read config file
    #
  LINE:
    while (<>) {
	# Skip comments
	next if /^\s*\#/;
	# Skip empty lines
	next if /^\n/;
	# Get type
	if (!s/^([A-Za-z_-]+):\s*//) {
	    print STDERR "Syntax error in line $. of $CONFIGNAME\n";
	    exit 1;
	}
	my $type = lc($1);
	$config->{$type} = [[""]] if !exists $config->{$type};
	chomp $_;
	my $configtype = $config->{$type};
	$configtype->[$#$configtype+1] = [];
	my $arguments = $configtype->[$#$configtype];
	while ($_ ne "") {
	    if (/^\"/) {
		# Quoted string
		if (s/^\"([^\"]+)\"\s*//) {
		    $arguments->[$#$arguments+1] = $1;
		} else {
		    print STDERR "Missing quote at line $. in $ARGV\n";
		    exit 1;
		}
	    } elsif (s/^<<\s*([^\s]+)\s*//) {
		my $delimiter;
		my $temp;
		$delimiter = $1;
		
		while (defined($temp = <>) && $temp ne "$delimiter\n") {
		    $heredocs[$#heredocs] .= $temp;
		}
		$arguments->[$#$arguments+1] = "\${HEREDOC:$#heredocs}";
		$heredocs[$#heredocs+1] = "";
		$temp = <>;
		last LINE if !defined($temp);
		$_ .= $temp;
		while (chomp) {}
	    } elsif (/^\#/) {
		# Zap comments
		$_ = "";
	    } else {
		# Normal string
		s/(\S+)\s*//;
		$arguments->[$#$arguments+1] = $1;
	    }
	}
    }

    # Bring in default values if the user has not inserted anything
    # him/her self.
    $config->{boot1} = [["/usr/mdec/boot1"]] if !exists $config->{boot1};
    $config->{boot2} = [["/usr/mdec/boot2"]] if !exists $config->{boot2};
    $config->{labeller} = [["$cwd/labeledit.pl"]]	if !exists $config->{labeller};
    $config->{'source-dir'} = [["${cwd}/../.."]] if !exists $config->{'source-dir'};
    $config->{'override-file'} = [] if !exists $config->{'override-file'};
    $config->{'config-name'} = [[$CONFIGNAME]]
	if !exists $config->{'config-name'};
    $config->{'config-exe'} = [["$OBJDIR/usr.sbin/config/config"]]
	if !exists $config->{'config-exe'};
    $config->{'config-options'} = [[""]]
	if !exists $config->{'config-options'};

    # Add fstab entry if the user hasn't done so herself
    my $has_fstab = 0;
    my $file_overrides = $config->{'override-file'};
    for ($i=1; $i <= $#$file_overrides; $i++) {
	if ($config->{'override-file'}->[$i]->[0] eq "/etc/fstab") {
	    $has_fstab = 1;
	    last;
	}
    }
    if (!$has_fstab) {
	$heredocs[$#heredocs+1] =
"# Device                Mountpoint      FStype  Options         Dump    Pass#
/dev/wd0b               none            swap    sw              0       0
/dev/wd0a               /               ufs     rw              1       1
proc                    /proc           procfs  rw              0       0
";
	$config->{'override-file'}->[$#$file_overrides+1] =
	    ["/etc/fstab", "\${HEREDOC:$#heredocs}"];
    }
    
    mkdir $DESTDIR,755;

    #
    # Build kernel if necessary
    #
    if (exists($config->{kernel})) {
	my $kernels = $config->{kernel};
	# Create a directory for building the kernel in.
	print "===> Building kernel directory ${CONFIGNAME}-kerndir\n";
	mkdir "${CONFIGNAME}-kerndir", 755;
	mkdir "${CONFIGNAME}-kerndir/sys", 755;
	my $basedir = "$config->{'source-dir'}->[0]->[0]/sys";

	
	mkdir "${CONFIGNAME}-kerndir/sys/compile", 755;
	for ($i = 1; $i <= $#$kernels; $i++) {
	    my $kernel = $kernels->[$i];
    	    my $arch = $kernel->[1]; # Architecture
	    mkdir "${CONFIGNAME}-kerndir/sys/${arch}", 755;
	    mkdir "$cwd/${CONFIGNAME}-kerndir/sys/${arch}/conf", 755;
	    symlink             "${basedir}/${arch}/conf/files.${arch}",
	    "$cwd/${CONFIGNAME}-kerndir/sys/${arch}/conf/files.${arch}";
	    symlink             "${basedir}/${arch}/conf/options.${arch}",
	    "$cwd/${CONFIGNAME}-kerndir/sys/${arch}/conf/options.${arch}";
	    symlink             "${basedir}/${arch}/conf/devices.${arch}",
	    "$cwd/${CONFIGNAME}-kerndir/sys/${arch}/conf/devices.${arch}";
	    symlink             "${basedir}/${arch}/conf/Makefile.${arch}",
	    "$cwd/${CONFIGNAME}-kerndir/sys/${arch}/conf/Makefile.${arch}";
	}
	my $finder = 
	    sub {
		my ($dev,$ino,$mode,$nlink,$uid,$gid);
		my $temp;
		if ((($dev,$ino,$mode,$nlink,$uid,$gid) = lstat($_)) &&
		    -d _ &&
		    ! /^CVS$/ &&
		    $File::Find::name !~ /^$basedir\/compile/ &&
		    $File::Find::name ne $basedir
		    ) {
		    my $subdirname = $File::Find::name;
		    $subdirname =~ s|$basedir/||;
		    if (!-e "${CONFIGNAME}-kerndir/sys/$subdirname") {
			symlink "$File::Find::name",
		    	"$cwd/${CONFIGNAME}-kerndir/sys/$subdirname";
		    }
		}
	    };
# Comment out when running code - to "fool" emacs into behaving.
#    {
	&File::Find::find($finder, $basedir);
        symlink "$config->{'source-dir'}->[0]->[0]/include",
		"$cwd/${CONFIGNAME}-kerndir/include";
        mkdir   "$cwd/${CONFIGNAME}-kerndir/sys/compile", 755;
	for ($i = 1; $i <= $#$kernels; $i++) {
	    my $kernel = $kernels->[$i];
    	    my $arch = $kernel->[1]; # Architecture
	    my $configname = $kernel->[2];
	    print "===> Building kernel $configname\n";
	    # Copy/create the config file
	    copy_file("$cwd/${CONFIGNAME}-kerndir/sys/$arch/conf/$configname",
		      $kernel->[3]);
	    # Do the actual configuration and compilation
	    system("cd $cwd/${CONFIGNAME}-kerndir/sys/${arch}/conf && ".
		   "$config->{'config-exe'}->[0]->[0] ".
                       "$config->{'config-options'}->[0]->[0] $configname && ".
		   "cd $cwd/${CONFIGNAME}-kerndir/sys/compile/$configname && ".
		   (exists($config->{'do-not-do-depend'}) &&
		    -e "$cwd/${CONFIGNAME}-kerndir/sys/compile/".
				"$configname/.depend"
		    ? "" : "make depend && ").
		   "make");
	    if ($?) {print STDERR "Error during kernel compile\n" && exit $?;}
	}
    }

    if (exists($config->{'do-not-do-install'})) {
	print "===> Skipping disk creation, distribution, and installworld\n";
    } else {
	#
	# Create disk
	#
	print "===> Labelling\n";
	print "=====> Clearing junk\n";
	`dd if=/dev/zero of=/dev/r${ICDEV} count=10`;
	if ($?) {print STDERR "Error during labelling\n" && exit $?;}
	print "=====> Auto-labelling\n";
	`disklabel -rwB -b $config->{boot1}->[0]->[0] -s $config->{boot2}->[0]->[0] ${ICDEV} auto`;
	if ($?) {print STDERR "Error during labelling\n" && exit $?;}
	print "=====> Setup labelling\n";
	`export EDITOR=$config->{labeller}->[0]->[0]; \
                disklabel -e ${ICDEV}`;
	if ($?) {print STDERR "Error during labelling\n" && exit $?;}
	print "Disk labelled.\n";
	print "===> Creating new filesystem on disk\n";
	system "newfs /dev/r${ICDEV}a";
	if ($?) {print STDERR "Error during newfs\n" && exit $?}
	print "===> Mounting new filesystem\n";
	`mount -o async /dev/${ICDEV}a ${DESTDIR}`;
	if ($?) {print STDERR "Error during mount\n" && exit $?}
	# We have a ready but empty filesystem in $DESTDIR

	#
	# Fill in filesystem with the usual defaults
	#
	print "===> Doing \"make distrib-dirs\"\n";
	`cd $config->{'source-dir'}->[0]->[0]/etc && make DESTDIR=${DESTDIR} distrib-dirs`;
	if ($?) {print STDERR "Error during distrib-dirs\n" && exit $?}

	print "===> Doing \"make distribution\"\n";
	`cd $config->{'source-dir'}->[0]->[0]/etc && make DESTDIR=${DESTDIR} distribution`;
	if ($?) {print STDERR "Error during distribution\n" && exit $?}

	print "===> Installing the world\n";
	`cd $config->{'source-dir'}->[0]->[0] && make DESTDIR=${DESTDIR} installworld`;
	if ($?) {print STDERR "Error during installworld\n" && exit $?}
    }

    #
    # Install any kernels that has been built
    #
    if (exists($config->{kernel})) {
	my $kernels = $config->{kernel};
	print "===> Installing kernels\n";
	for ($i = 1; $i <= $#$kernels; $i++) {
	    my $kernel = $kernels->[$i];
	    my $configname = $kernel->[2];
	    print "====> Installing \"$kernel->[0]\"\n";
	    system("install -c -m 555 -o root -g wheel -fschg " .
		   "$cwd/${CONFIGNAME}-kerndir/sys/compile/$configname/kernel " .
		   "${DESTDIR}$kernel->[0]");
	    if ($?) {print STDERR "Error during kernel install\n" && exit $?}
	}
    }

    #
    # Override single files
    #
    $file_overrides = $config->{'override-file'};
    if ($#$file_overrides == 1) {
	print "===> Handling one file override or extra installed file\n";
    } else {
	print "===> Handling $#$file_overrides file overrides or extra installed files\n";
    }
    for ($i=1; $i <= $#$file_overrides; $i++) {
    	my $destination = $file_overrides->[$i]->[0];
	if ($file_overrides->[$i]->[1] =~ /\${HEREDOC:\d+}/) {
	    print "====> Overriding \"$destination\" with here-document\n";
	} else {
	    print "====> Overriding \"$destination\" with \"$file_overrides->[$i]->[1]\"\n";
	}
	copy_file("${DESTDIR}$destination",
		  $file_overrides->[$i]->[1]);
    }

    if (exists($config->{'do-not-do-install'})) {
	print "===> Skipping unmounting of disk\n";
    } else {
	print "===> Unmounting disk\n";
	`umount $DESTDIR`;
	if ($?) {print STDERR "Error during unmount\n" && exit $?}
    }
}
