#! @PERL@
# -*- Mode: Perl -*-
# @configure_input@
#
#  $Id: identify_bootdisk.pl,v 1.1 1998/10/12 15:54:26 fawcett Exp fawcett $
#  IDENTIFY_BOOTDISK: Given a bootdisk image or floppy device, analyze
#  it and present as much information as possible.
#
#  Usage:
#     identify_bootdisk disk_or_file
#
#
##############################################################################
use strict qw(subs refs);
use FileHandle;
use Cwd;
use English;
use File::Find;

##############################################################################
# Constants from /usr/src/linux/arch/i386/kernel/setup.c:
$::RAMDISK_IMAGE_START_MASK   =	0x07FF;
$::RAMDISK_PROMPT_FLAG        =	0x8000;
$::RAMDISK_LOAD_FLAG          = 0x4000;

##############################################################################
STDOUT->autoflush(1);

#require "find.pl";
#sub wanted { &$wanted }

#  Filename for root filesystem listing
my($root_listing) = "rootfs.ls_alR";

%VGA_modestr
    = (-3  => "Prompt",
       -2  => "Extended VGA",
       -1  => "Normal VGA",
        0  => "as if 0 was pressed at the prompt",
        1  => "as if 1 was pressed at the prompt",
        2  => "as if 2 was pressed at the prompt"
      );


if ($#ARGV == -1) {
    print "\nUsage:\n";
    print "$PROGRAM_NAME disk_or_floppy_drive\n";
    exit;
}

my($boot_image) = @ARGV;

#  First, figure out what we're looking at
chomp($file_output = `file $boot_image`);

if ($file_output =~ /block special/) {
   ##  It's a device
   print "$boot_image is a device -- transferring contents\n";
   system("@DD@ if=$boot_image of=/tmp/boot_image");
   $boot_image = "/tmp/boot_image";
   chop($file_output = `file $boot_image`);

}   

print "File says:  $file_output\n";

my($kernel);
if ($file_output =~ /filesystem/) {
    mkdir("/tmp/mnt", 0777) unless -d "/tmp/mnt";
    system("@MOUNT@ -o loop $boot_image /tmp/mnt");
    sub wanted {
       print "Seeing $File::Find::name\n";
       $kernel = $File::Find::name 
	   if $File::Find::name =~ m!/(vmlinuz|zImage)$!;
    };
    find(\&wanted, "/tmp/mnt");

    die "Couldn't find a kernel!\n" unless defined($kernel);
    print "Found kernel file $kernel\n";
    undef &wanted;
#    #  We're done with the kernel filesystem
#    system("@UMOUNT@ /tmp/mnt");

} elsif ($file_output =~ /x86 boot sector/) {
   ## It's an image file
    $kernel = $boot_image;
 
} elsif ($file_output =~ /data/) {
    print "Boot image has no filesystem\n";
    $kernel = $boot_image;

} else {
    die "Can't identify boot image $boot_image\n";
}


open(KERNEL, "<$kernel")   or die "open $kernel: $!";
read(KERNEL, $header, 512) or die "couldn't read kernel header: $!";
close(KERNEL);

my($str)	       = "";
my($version_start) = "";

open(DATA, $boot_image) or die "can't open $boot_image.\n";
# check signature of kernel image
seek(DATA, 514, 0);
read(DATA, $str, 4);
die "Kernel image file ($boot_image) does not have Linux kernel signature\n"
    unless $str =~ "HdrS";
# setup header version should be 0x201
read(DATA, $str, 2);
$str = unpack("S",$str);
print "Kernel setup header version is 0x" .
    sprintf("%04x",$str)." (expected 0x0201).\n" unless $str == 0x201;
# get offset of version string (indirect) and read version string
seek(DATA, 526, 0);
read(DATA, $version_start, 2) or die "can't read from $boot_image.\n";
$version_start = unpack("S",$version_start) + 512;
seek(DATA, $version_start, 0);
read(DATA, $str, 10) or
    die "can't read from offset $version_start of $boot_image.\n";
close(DATA);
# extract the version number
my($version) = $str =~ /^(\d+\.\d+\.\d+)\s/;
print "Kernel version is $version\n";

#        In a bootable image for the Linux kernel, there  are  sev-
#        eral  pairs  of  bytes  which specify the root device, the
#        video mode, the size of the RAM disk, and the swap device.
#        These  pairs  of  bytes,  by  default, begin at offset 504
#        (decimal) in the kernel image:
#
#                498 Root flags
#               (500 and 502 Reserved)
#                504 RAM Disk Size
#                506 VGA Mode
#                508 Root Device
#               (510 Boot Signature)

my($root_flags, $x, $x, $ramdisk_word, $VGA_mode, $rdev_low, $rdev_high,
   $boot_signature) = unpack("x498 SSSSsCCS", $header);

if ($boot_signature == 0xAA55) {
    print "(Found boot signature on image)\n";
} else {
    die "Didn't find boot signature (0xAA55) on image!\n$boot_signature\n";
}


my($devname) = device_name($rdev_high, $rdev_low);
print "Root filesystem: ", $root_flags == 0 ? "Read/Write" : "Read-only", "\n";
print "Root device: major $rdev_high, minor $rdev_low" .
    ($devname ? " (= $devname)" : "") . "\n";
print "VGA mode: ", ($VGA_modestr{$VGA_mode} or $VGA_mode), "\n";

#  Now interpret the ramdisk word
print "Raw RAMDISK word: ", sprintf("0x%X", $ramdisk_word), "\n";
my($ramdisk_offset) = $ramdisk_word & $RAMDISK_IMAGE_START_MASK;
my($ramdisk_load)   = $ramdisk_word & $RAMDISK_LOAD_FLAG   ? 1 : 0;
my($ramdisk_prompt) = $ramdisk_word & $RAMDISK_PROMPT_FLAG ? 1 : 0;
print "\tRamdisk load: $ramdisk_load\n";
if ($ramdisk_load > 0) {
   printf("\tRamdisk offset: %d (0x%X) blocks\n",
	  $ramdisk_offset, $ramdisk_offset);
   print "\tRamdisk prompt: $ramdisk_prompt\n";
}

if ($ramdisk_prompt != 0) {
    exit;
} else {
    system("@DD@ if=$boot_image skip=$ramdisk_start of=/tmp/ramdisk.gz bs=1k");
    system("@GUNZIP@ --force /tmp/ramdisk.gz");
    mkdir("/tmp/mnt2", 0777) unless -d "/tmp/mnt2";
    system("@MOUNT@ -o loop /tmp/ramdisk /tmp/mnt2");
    my($here) = cwd();
    system("cd /tmp/mnt2 ; ls -alR . > $here/$root_listing");
    system("@UMOUNT@ /tmp/mnt2");
    unlink("/tmp/ramdisk") or die "unlink ramdisk: $!";
}

print "Done, listing of root filesystem is on $root_listing\n";
exit(0);


#  DEVICE_NAME(major,minor)
#
#  Given a pair of numbers (major, minor), print the device name.
#  Does this somewhat crudely by scanning your /dev directory and
#  seeing which device matches.
#  Tries to fail noiselessly.
sub device_name {
   my($major, $minor) = @_;

   opendir(DEVS, "/dev") or return("");
   my($file);
   while (defined($file = readdir(DEVS))) {
      my($device) = (stat("/dev/$file"))[6];
      if (-b _ and ($device >> 8) == $major  and ($device & 0xFF) == $minor) {
	 closedir(DEVS);
	 return("/dev/$file");
      }
   }
   closedir(DEVS);
   ""
}
