#!/usr/bin/perl
#
# imagetool - for extracting and creating Cobalt ROM images
#
# Duncan Laurie <duncan@iceblink.org>
# Copyright (c) 2003 Sun Microsystems, Inc.
# 

use Getopt::Std;
use strict;
use vars qw($opt_h $opt_c $opt_e $opt_s $opt_i $opt_o $opt_d);

my $image = "cobalt.rom";
my $spec_file = "spec/cobalt-1M.spec";
my $co_prefix = "co_romfs";

my $dir = ".";
my $bs = "rom.bs";
my $fs = "rom.fs";

my $old = 0;
my $image_size = 0;
my $spec;


sub usage {
    print <<EOL

usage: $0 [options] <mode>
options:
  -h                 : get this help
  -o                 : use older (2.3.x) romfs tools
  -s <spec>          : rom layout spec file
  -i <image>         : rom image to extract/create
  -d <dir>           : work directory
modes:
  -c                 : create filesystem and join with bootsector to create image
  -e                 : extract image bootsector and extract filesystem

EOL
;
    exit 0;
}

# analyze rom image and guess spec
sub analyze_image {
    $image = $opt_i if ($opt_i);

    die "cannot find image $image!" unless (-f $image);
    print qx(md5sum $image);

    $image_size = (stat $image)[7];
    my $spec_guess = "spec/cobalt-" . $image_size / (1024 * 1024) . "M";

    my $type;
    open(ROM, $image) || die "$!";
    read(ROM, $type, 4);
    close(ROM);

    $spec_guess .= "-flat" if ($type eq "CRfs");
    $spec_guess .= ".spec";

    if (-f $spec_guess && !$opt_s) {
	print "reading $spec_guess\n";
	$spec = require $spec_guess;
    } elsif (-f $spec_file) {
	print "reading $spec_file\n";
	$spec = require $spec_file;
    }

    die "image size != spec size\n" unless ($image_size == $spec->{SIZE});
}

# extract the bootsector and the filesystem from image
sub extract_image {
    my($len,$tlen,$data);

    print "extracting ROM $image ($image_size bytes)...\n";

    open(ROM, $image) || die "$!";
    seek(ROM, $spec->{BOOT_START}, 0);

    $tlen = 0;
    open(BS, "> $bs") || die "$!";
    while ($len = read(ROM, $data, 1024)) {
	last if ($tlen >= $spec->{BOOT_SIZE});
	$tlen += $len;
	print BS $data;
    }
    close(BS);
    print "bootsector saved in $bs ($tlen bytes)\n";

    seek(ROM, $spec->{FS_START}, 0);

    $tlen = 0;
    open(FS, "> $fs") || die "$!";
    while ($len = read(ROM, $data, 1024)) {
	last if ($tlen >= $spec->{FS_SIZE});
	$tlen += $len;
	print FS $data;
    }
    close(FS);
    print "filesystem saved in $fs ($tlen bytes)\n";

    close(ROM);
}

# create rom image from bootsector and filesystem
sub create_image {
    my $chunksize = 32768;
    my $chunk = chr(0xff) x $chunksize;
    my $len = 0;
    my $tlen;
    my $data;

    print "creating ROM $image (", $spec->{SIZE}, " bytes)...\n";

    unlink $image if (-f $image);
    open(ROM, "> $image") || die "$!";
    seek(ROM, 0, 0);

    while ($len < $spec->{SIZE}) {
	print ROM $chunk;
	$len += $chunksize;
    }

    seek(ROM, $spec->{BOOT_START}, 0);

    $tlen = 0;
    open(BS, $bs) || die "$!";
    while ($len = read(BS, $data, 1024)) {
	last if ($tlen > $spec->{BOOT_SIZE});
	$tlen += $len;
	print ROM $data;
    }
    close(BS);

    print "bootsector $bs added to $image ($tlen bytes)\n";

    seek(ROM, $spec->{FS_START}, 0);

    $tlen = 0;
    open(FS, $fs) || die "$!";
    while ($len = read(FS, $data, 1024)) {
	last if ($tlen > $spec->{FS_SIZE});
	$tlen += $len;
	print ROM $data;
    }
    close(FS);

    print "filesystem $fs added to $image ($tlen bytes)\n";

    close(ROM);
}

# extract files from romfs filesystem
sub extract_filesystem {
    my @files;

    print "extracting filesystem $fs...\n";

    open(CO_LS, "$co_prefix/co_ls $fs / |");
    while (<CO_LS>) {
	chomp;
	if ($opt_o) {
	    next if (/^\.+$/);
	    push @files, $_;
	} else {
	    if (/\s*(\d+)\s+(\S+)/) {
		next unless $1;
		push @files, $2;
	    }
	}
    }
    close(CO_LS);

    foreach my $f (@files) {
	my $file = "$dir/$f";
	qx($co_prefix/co_cpfrom $fs $f $file);
	if (-f $file) {
	    printf " %s (%d bytes)\n", $file, (stat $file)[7];
	}
    }
}

# create romfs filesystem from files listed in spec
sub create_filesystem {
    my $files = $spec->{FS_FILES};
    my $sizel = $spec->{FS_SIZE};

    print "creating filesystem $fs (", $spec->{FS_SIZE}, " bytes)...\n";
    unlink $fs if (-e $fs);
    qx($co_prefix/co_mkfs $fs $sizel);

    foreach my $f (@$files) {
	my $file = $dir.'/'.$$f[0];
	if (-f $file) {
	    my $sz = (stat $file)[7];
	    $sizel -= $sz;
	    printf " %s (%d bytes, %d free)\n", $file, $sz, $sizel;
	    qx($co_prefix/co_cpto $fs $file $$f[0]);
	}
	else {
	    print "could not find file $file\n";
	}
    }
}

#
# main
#

getopts('hoced:s:i:');

usage() if ($opt_h);
usage() unless ($opt_c || $opt_e);

if ($opt_d) {
    $dir = $opt_d;
    mkdir($dir) unless (-d $dir);
    $bs = "$dir/$bs";
    $fs = "$dir/$fs";
}

if ($opt_o) {
    $co_prefix .= ".old";
    $spec_file = "spec/cobalt-1M-old.spec";
    print "Using old romfs tools in $co_prefix\n";
}

if ($opt_s) {
    $spec_file = $opt_s;
}

if ($opt_c) {
    die "cannot find spec $spec_file!" unless (-f $spec_file);
    $spec = require $spec_file;
    $image = $opt_i || $spec->{NAME};
    create_filesystem();
    create_image();
    print qx(md5sum $image);
}

if ($opt_e) {
    analyze_image();
    extract_image();
    extract_filesystem();
}

exit 0;
