#! /usr/bin/perl

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# Create SUSE Linux boot disks.
#
# Try 'mkbootdisk --help' for a usage summary.
#
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

use strict 'vars';
use integer;

%::ConfigData = ( product_name => "product_X" );


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# Basic FAT manipulation functions.
#
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{
  package FAT;

  use strict 'vars';
  use integer;


  sub new
  {
    my $self = {};

    bless $self;

    $self->{offset} = 0;
    $self->{image} = "\x00" x 0x200;

    return $self
  }

  sub image
  {
    my $self = shift;

    return $self->{image};
  }


  sub offset
  {
    my $self = shift;

    $self->{offset} = shift if @_;

    return $self->{offset};
  }


  sub write_image
  {
    my $self = shift;

    if(@_) {
      my $file = shift;
      open W1, ">$file";
      print W1 $self->image;
      close W1;
    }
  }


  sub read_image
  {
    my $self = shift;

    if(@_) {
      my $file = shift;
      my $image;
      open F1, $file;
      read F1, $image, -s($file);
      close F1;
      $self->{image} = $image;
    }
  }


  sub resize_image
  {
    my $self = shift;
    my $new_size = shift;

    my $len = $new_size + $self->{offset} - length($self->image);
    $self->{image} .= "\x00" x $len if $len > 0;
  }


  sub _string
  {
    my $self = shift;
    my $ofs = $self->{offset} + shift;
    my $len = 0 + shift;

    substr($self->{image}, $ofs, $len) = pack("a$len", shift) if @_[0];
    return substr($self->{image}, $ofs, $len);
  }


  sub _byte
  {
    my $self = shift;
    my $ofs = $self->{offset} + shift;

    substr($self->{image}, $ofs, 1) = pack("C", shift) if @_[0];
    return unpack("C", substr($self->{image}, $ofs, 1));
  }


  sub _word
  {
    my $self = shift;
    my $ofs = $self->{offset} + shift;

    substr($self->{image}, $ofs, 2) = pack("v", shift) if @_[0];
    return unpack("v", substr($self->{image}, $ofs, 2));
  }


  sub _dword
  {
    my $self = shift;
    my $ofs = $self->{offset} + shift;

    substr($self->{image}, $ofs, 4) = pack("V", shift) if @_[0];
    return unpack("V", substr($self->{image}, $ofs, 4));
  }


  sub sector
  {
    my $self = shift;
    my $sec = shift;
    my $len = $self->sector_size;
    my $ofs = $sec * $len + $self->{offset};

    if(@_) {
      my $buf = shift;
      my $xlen = $len - length($buf);
      $buf .= "\x00" x $xlen if $xlen > 0;
      substr($self->{image}, $ofs, $len) = $buf;
    }

    return substr($self->{image}, $ofs, $len);
  }


  sub cluster
  {
    my $self = shift;
    my $cl_nr = shift;
    my $len = $self->sector_size * $self->cluster_size;

    return undef if $cl_nr < 2;

    my $ofs = ($cl_nr - 2) * $len + $self->{offset};

    $ofs += ($self->res_sectors + $self->fats * $self->fat_size + $self->_root_sectors) * $self->sector_size;

    if(@_) {
      my $buf = shift;
      my $xlen = $len - length($buf);
      $buf .= "\x00" x $xlen if $xlen > 0;
      substr($self->{image}, $ofs, $len) = $buf;
    }

    return substr($self->{image}, $ofs, $len);
  }


  #
  # dir_entry(cluster, entry_index [, buffer])
  #
  sub dir_entry
  {
    my $self = shift;
    my $cl_nr = shift;
    my $entry = shift;
    my $len = 32;
    my $ofs;

    return undef if $cl_nr < 2 && $cl_nr != 0;

    $ofs = $self->res_sectors + $self->fats * $self->fat_size;
    if($cl_nr >= 2) {
      $ofs += $self->_root_sectors + ($cl_nr - 2) * $self->cluster_size;
    }

    $ofs = $ofs * $self->sector_size + $self->{offset} + ($entry << 5);

    if(@_) {
      my $buf = shift;
      my $xlen = $len - length($buf);
      $buf .= "\x00" x $xlen if $xlen > 0;
      substr($self->{image}, $ofs, $len) = $buf;
    }

    return substr($self->{image}, $ofs, $len);
  }


  # dos_date(day, month, year)
  # or
  # dos_date(unix_time)

  sub dos_date
  {
    my (@u);

    @u = @_;
    if(@u == 1) {
      @u = (localtime shift)[3..5];
      $u[1]++;
    }

    return pack("v", $u[0] + ($u[1] << 5) + (($u[2] < 80 ? 0 : $u[2] - 80) << 9));
  }


  # dos_time(second, minute, hour)
  # or
  # dos_time(unix_time)

  sub dos_time
  {
    my (@u);

    @u = @_;
    if(@u == 1) {
      @u = (localtime shift)[0..2];
    }

    return pack("v", ($u[0] >> 1) + ($u[1] << 5) + ($u[2] << 11));
  }


  sub fs_date
  {
    my $self = shift;

    $self->{fs_date} = dos_date(@_) if @_;

    return $self->{fs_date};
  }


  sub fs_time
  {
    my $self = shift;

    $self->{fs_time} = dos_time(@_) if @_;

    return $self->{fs_time};
  }


  #
  # dir_entry(name, attribute, time, date, start, size);
  #
  sub new_dir_entry
  {
    my ($name, $attribute, $time, $date, $start, $size) = @_;

    return pack("A11CZ10a2a2vV", $name, $attribute, "", $time, $date, $start, $size);
  }


  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  sub boot_code
  {
    my $self = shift;

    $self->_word(0, 0xfeeb);
    $self->_byte(2, 0x90);

    if(@_) {
      my $code = shift;
      $self->_byte(1, 0x3c);
      $self->_string(0x3e, length($code), $code);
    }
  }


  sub manuf_id
  {
    my $self = shift;

    if(@_) {
      return unpack("A8", $self->_string(0x03, 8, pack("A8", shift)));
    }
    else {
      return unpack("A8", $self->_string(0x03, 8));
    }
  }


  sub sector_size
  {
    my $self = shift;

    return $self->_word(0x0b, shift);
  }


  sub cluster_size
  {
    my $self = shift;

    return $self->_byte(0x0d, shift);
  }


  sub res_sectors
  {
    my $self = shift;

    return $self->_word(0x0e, shift);
  }


  sub fats
  {
    my $self = shift;

    return $self->_byte(0x10, shift);
  }


  sub root_entries
  {
    my $self = shift;

    if(@_) {
      my $entries = shift;
      my $sec_size = $self->sector_size;
      if($sec_size) {
        $entries = (((($entries << 5) + $sec_size - 1) / $sec_size) * $sec_size) >> 5;
      }
      return $self->_word(0x11, $entries);
    }
    else {
      return $self->_word(0x11, shift);
    }
  }


  sub sectors
  {
    my $self = shift;
    my $secs;

    if(@_) {
      $secs = shift;
      if($secs >> 16) {
        $self->_dword(0x20, $secs);
      }
      else {
        $self->_word(0x13, $secs);
      }
    }

    $secs = $self->_word(0x13);
    $secs = $self->_dword(0x20) unless $secs;

    return $secs;
  }


  sub media_id
  {
    my $self = shift;

    return $self->_byte(0x15, shift);
  }


  sub fat_size
  {
    my $self = shift;

    return $self->_word(0x16, shift);
  }


  sub track_size
  {
    my $self = shift;

    return $self->_word(0x18, shift);
  }


  sub heads
  {
    my $self = shift;

    return $self->_word(0x1a, shift);
  }


  sub hidden_sectors
  {
    my $self = shift;

    return $self->_dword(0x1c, shift);
  }


  sub drive_id
  {
    my $self = shift;

    return $self->_byte(0x24, shift);
  }


  sub extended_bpb
  {
    my $self = shift;

    return $self->_byte(0x26, shift);
  }


  sub serial
  {
    my $self = shift;

    return $self->_dword(0x27, shift);
  }


  sub volume_id
  {
    my $self = shift;

    if(@_) {
      return unpack("A11", $self->_string(0x2b, 11, pack("A11", shift)));
    }
    else {
      return unpack("A11", $self->_string(0x2b, 11));
    }
  }


  sub fat_bits
  {
    my $self = shift;

    if(@_) {
      my $bits = shift;
      $bits = 16 unless $bits == 12 || $bits == 32;
      $self->_string(0x36, 8, sprintf("FAT%-5u", $bits));
    }

    my $id = $self->_string(0x36, 8);
    if($id =~ /FAT(\d+)/) {
      $id = $1 + 0;
    }
    else {
      $id = undef;
    }

    return $id;
  }


  sub _root_sectors
  {
    my $self = shift;

    return (($self->root_entries << 5) + $self->sector_size - 1) / $self->sector_size;
  }


  sub _data_sectors
  {
    my $self = shift;

    return $self->sectors - $self->res_sectors - $self->fats * $self->fat_size - $self->_root_sectors;
  }


  sub clusters
  {
    my $self = shift;

    return $self->_data_sectors / $self->cluster_size;
  }


  sub cluster_to_sector
  {
    my $self = shift;
    my $cl_nr = shift;

    return undef if $cl_nr < 2;

    return $self->res_sectors + $self->fats * $self->fat_size + $self->_root_sectors +
      ($cl_nr - 2) * $self->cluster_size;
  }


  sub sector_to_cluster
  {
    my $self = shift;
    my $sec_nr = shift;

    $sec_nr -= $self->res_sectors + $self->fats * $self->fat_size + $self->_root_sectors;

    return undef if $sec_nr < 0;

    return $sec_nr / $self->cluster_size + 2;
  }


  sub wasted_sectors
  {
    my $self = shift;

    return $self->_data_sectors - $self->clusters * $self->cluster_size;
  }


  sub fat_entry
  {
    my $self = shift;
    my $cl_nr = shift;
    my $bits = $self->fat_bits;
    my $fats = $self->fats;
    my ($cl, $i, $ofs);

    return undef unless $bits;

    if(@_) {
      for($i = 0; $i < $fats; $i++) {
        if($bits == 12) {
          $ofs = ($self->res_sectors + $self->fat_size * $i) * $self->sector_size + $cl_nr + ($cl_nr >> 1);
          $cl = $self->_word($ofs);
          if($cl_nr & 1) {
            $cl = ($cl & ~0xfff0) + (($_[0] << 4) & 0xfff0);
          }
          else {
            $cl = ($cl & ~0xfff) + ($_[0] & 0xfff);
          }
          $self->_word($ofs, $cl);
        }
        elsif($bits == 16) {
          $self->_word(($self->res_sectors + $self->fat_size * $i) * $self->sector_size + ($cl_nr << 1), $_[0]);
        }
      }
    }

    if($bits == 12) {
      $cl = $self->_word($self->res_sectors * $self->sector_size + $cl_nr + ($cl_nr >> 1));
      if($cl_nr & 1) {
        $cl >>= 4;
      }
      else {
        $cl &= 0xfff;
      }
    }
    elsif($bits == 16) {
      $cl = $self->_word($self->res_sectors * $self->sector_size + ($cl_nr << 1));
    }

    return $cl;
  }


  sub free_cluster
  {
    my $self = shift;
    my $clusters = $self->clusters + 2;
    my $cl_nr;

    for($cl_nr = 2; $cl_nr < $clusters; $cl_nr ++) {
      return $cl_nr unless $self->fat_entry($cl_nr);
    }

    return undef;
  }


  sub add_file
  {
    my $self = shift;
    my ($cl_nr, $idx, $name, $attr, $buf) = @_;
    my $cl_len = $self->cluster_size * $self->sector_size;
    my $len = length($buf);
    my ($i, $cl, $start, $next);

    if($len) {
      $start = $self->free_cluster;
      return undef unless $start;
    }

    $self->dir_entry($cl_nr, $idx, new_dir_entry(
      $name, $attr, $self->fs_time, $self->fs_date, $start, $len
    ));

    return 1 unless $len;

    for($i = 0; $i < $len; $i += $cl_len) {
      $self->cluster($start, substr($buf, $i, $cl_len));
      $self->fat_entry($start, 0xffff);
      if($i + $cl_len < $len) {
        $next = $self->free_cluster;
        return undef unless $next;
        $self->fat_entry($start, $next);
      }
      $start = $next;
    }

    return 1;
  }


  sub add_dir
  {
    my $self = shift;
    my ($cl_nr, $idx, $name, $attr) = @_;
    my $start = $self->free_cluster;

    return undef unless $start;

    $self->dir_entry($cl_nr, $idx, new_dir_entry(
      $name, 0x10 | $attr, $self->fs_time, $self->fs_date, $start, 0
    ));
    $self->fat_entry($start, 0xffff);

    $self->dir_entry($start, 0, new_dir_entry(
      ".", 0x10 | $attr, $self->fs_time, $self->fs_date, $start, 0
    ));
    $self->dir_entry($start, 1, new_dir_entry(
      "..", 0x10 | $attr, $self->fs_time, $self->fs_date, $cl_nr, 0
    ));

    return $start;
  }


  sub init_fs
  {
    my $self = shift;
    my ($clusters, $i, $ofs, $buf);

    $self->_word($self->sector_size - 2, 0xaa55);
    $self->resize_image($self->sector_size * $self->sectors);

    $clusters = $self->clusters;

    if(!$self->fat_bits && !$self->fat_size) {
      for(my $i = 0; $i < 3; $i++) {
        # might not converge; use odd number of iterations to get lower limit
        $self->fat_bits($clusters <= 0xff5 ? 12 : 16);
        $self->fat_size((($self->fat_bits * ($clusters + 2) + 7) / 8 + $self->sector_size - 1) / $self->sector_size);
        $clusters = $self->clusters;
      }
    }

    for($i = $self->res_sectors; $i < $self->sectors; $i++) {
      # clear all sectors
      $self->sector($i, undef);
    }

    for($i = 0; $i < $self->fats; $i++) {
      $ofs = ($self->res_sectors + $i * $self->fat_size) * $self->sector_size;
      $self->_byte($ofs, $self->media_id);
      $self->_word($ofs + 1, 0xffff);
      $self->_byte($ofs + 3, 0xff) if $self->fat_bits >= 16;
      $self->_dword($ofs + 4, 0xffffffff) if $self->fat_bits == 32;
    }

    $self->add_file(0, 0, $self->volume_id, 8, undef);
  }
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# Create special FAT image.
#
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{
  package MakeFAT;

  use strict 'vars';
  use integer;

  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  my $boot_msg = "\r
  I'm $::ConfigData{product_name} Boot Disk <disk>. I cannot boot. :-(\r
  \r
  Please try Boot Disk 1.\r\n";


  # Not more than 1024 chars (1 cluster)! --> Or adjust cluster size!
  my $readme =
  "This is $::ConfigData{product_name} Boot Disk <disk>.

  <x_readme>
  To access Boot Disk data, you have to join the individual disk images first:

    cat bootdisk?? >/tmp/bootdisk

  Then mount it as usual:

    mount -oloop /tmp/bootdisk /mnt

  When you're done, unmount it:

    umount /mnt

  If you have changed Boot Disk data and want to get separate Boot Disk images
  of floppy size back, split it:

    split -a 1 -b 1440k /tmp/bootdisk /tmp/bootdsk

  The new Boot Disks are /tmp/bootdsk[a-<last_disk_letter>].\n";


  my $x_readme =
  "\n***  There is nothing for you to change on this disk.  ***\n";


  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  # cluster size, extra root dir sectors
  my @format = (
    [ 4, 7 ],	# 1 (at least 96 root entries, default is 128 (16 + 16 * _7_))
    undef,	# 1
    [ 4, 5 ],	# 2
    [ 4, 7 ],	# 3
    [ 4, 5 ],	# 4
    [ 4, 7 ],	# 5
    [ 4, 5 ],	# 6
    [ 4, 6 ],	# 7
    [ 4, 7 ],	# 8
    [ 4, 8 ],	# 9
  );

  my $opt_disks = 2;
  my ($serial, $fat);


  sub set_boot_msg
  {
    my $msg = shift;
    my $code =
      "\xfa\x31\xc9\x8e\xd1\x89\xcc\x8e\xd9\x8e\xc1\xfb\xfc\xe8\x00\x00" .
      "\x5e\x81\xc6\x13\x00\xac\x08\xc0\x74\xfe\xbb\x07\x00\xb4\x0e\xcd" .
      "\x10\xeb\xf2";

    $fat->boot_code($code . $msg . "\x00");
  }


  # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  sub create_image
  {
    my ($cl_size, $x_root, $i, $ldsk);

    $cl_size = $format[$opt_disks] ? $format[$opt_disks][0] : $format[0][0];
    $x_root = $format[$opt_disks] ? $format[$opt_disks][1] : $format[0][1];

    $fat = FAT::new;

    $fat->resize_image(1440 * 1024 * $opt_disks);

    $fat->sector_size(0x200);
    $fat->res_sectors(1);
    $fat->extended_bpb(0x29);

    $fat->sectors(1440 * 2 * $opt_disks);
    $fat->track_size(18);
    $fat->heads(2);
    $fat->cluster_size($cl_size);
    $fat->fats(1);
    $fat->root_entries((16 * $x_root) + 1);

    $fat->media_id(0xf0);
    $fat->drive_id(0x00);

    $fat->serial($serial + 0);
    $fat->volume_id("BOOTDISK01");
    $fat->manuf_id("SUSE");

    $fat->fs_date(time);
    $fat->fs_time(0, 10, 9);

    $fat->init_fs;

    $i = $readme;
    $i =~ s/<x_readme>//g;
    $i =~ s/<disk>/1/g;
    $ldsk = chr($opt_disks - 1 + ord('a'));
    $i =~ s/<last_disk_letter>/$ldsk/g;
    $fat->add_file(0, 1, "README  TXT", 0, $i);
  }


  sub create_small_image
  {
    my $disk = shift;
    my ($max_cl, $i, $dsk, $ldsk);

    $fat->offset($disk * 1440 * 1024);

    $fat->sector_size(0x200);
    $fat->res_sectors(1);
    $fat->extended_bpb(0x29);

    $fat->sectors(1440 * 2);
    $fat->track_size(18);
    $fat->heads(2);
    $fat->cluster_size(2);
    $fat->fats(1);
    $fat->root_entries((16 * 1) + 1);

    $fat->media_id(0xf0);
    $fat->drive_id(0x00);

    $fat->serial($serial + $disk);

    $dsk = $disk + 1;

    $fat->volume_id(sprintf "BOOTDISK%02u", $dsk);
    $fat->manuf_id("SUSE");

    $i = $boot_msg;
    $i =~ s/<disk>/$dsk/g;
    set_boot_msg($i);

    $fat->init_fs;

    $i = $readme;
    $i =~ s/<x_readme>/$x_readme/g;
    $i =~ s/<disk>/$dsk/g;
    $ldsk = chr($opt_disks - 1 + ord('a'));
    $i =~ s/<last_disk_letter>/$ldsk/g;
    $fat->add_file(0, 1, "README  TXT", 0, $i);

    $max_cl = $fat->clusters + 2;

    for($i = 3; $i < $max_cl; $i++) {
      $fat->fat_entry($i, 0xfff7);
    }

    # printf "res = %u\n", $fat->cluster_to_sector(2);

    if($i = $fat->wasted_sectors) {
      warn "small image: $i sectors wasted\n"
    }
  }


  sub Image
  {
    my ($i, $res_sectors, $start_sec, $sec, $cl, $clusters, $free_clusters);
    my ($fat_entry, $file, $verbose);

    ($file, $opt_disks, $verbose) = @_;

    $serial = int(rand(0x10000000)) << 4;

    create_image;

    for($i = 1; $i < $opt_disks; $i++) {
      create_small_image $i;
    }

    $res_sectors = $fat->cluster_to_sector(2 + 1);	# 1 cluster for 'README'

    # print "res_sectors = $res_sectors\n";

    $fat->offset(0);

    for($i = 1; $i < $opt_disks; $i++) {
      $start_sec = 1440 * 2 * $i;
      for($sec = $start_sec; $sec < $start_sec + $res_sectors; $sec ++) {
        $cl = $fat->sector_to_cluster($sec);
        $fat->fat_entry($cl, 0xfff7);
        # print "sec = $sec, $cl\n";
      }
    }

    $clusters = $fat->clusters + 2;
    $free_clusters = 0;

    for($i = 2; $i < $clusters; $i++) {
      $fat_entry = $fat->fat_entry($i);
      $free_clusters++ if defined($fat_entry) && $fat_entry == 0;
    }

    if($verbose) {
      printf "      image size = %u\n", $fat->sectors * $fat->sector_size;
      printf "        manuf id = \"%s\"\n", $fat->manuf_id;
      printf "     sector size = 0x%x\n", $fat->sector_size;
      printf " sectors/cluster = %u\n", $fat->cluster_size;
      printf "reserved sectors = %u\n", $fat->res_sectors;
      printf "            fats = %u\n", $fat->fats;
      printf "root dir entries = %u\n", $fat->root_entries;
      printf "         sectors = %u\n", $fat->sectors;
      printf "        media id = 0x%02x\n", $fat->media_id;
      printf "     sectors/fat = %u\n", $fat->fat_size;
      printf "   sectors/track = %u\n", $fat->track_size;
      printf "           heads = %u\n", $fat->heads;
      printf "  hidden sectors = %u\n", $fat->hidden_sectors;
      printf "        drive id = 0x%02x\n", $fat->drive_id;
      printf " extended bpb id = 0x%02x\n", $fat->extended_bpb;
      printf "          serial = 0x%08x\n", $fat->serial;
      printf "       volume id = \"%s\"\n", $fat->volume_id;
      printf "        fat bits = %u\n", $fat->fat_bits;
      printf "        clusters = %u\n", $fat->clusters;
      printf "   free clusters = %u (%uk)\n", $free_clusters, ($free_clusters * $fat->cluster_size * $fat->sector_size) >> 10;
      printf "  wasted sectors = %u\n", $fat->wasted_sectors if $fat->wasted_sectors;
    }

    $fat->write_image($file) if $file;

    return ( $free_clusters, $fat->cluster_size * $fat->sector_size );
  }
}



# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# Parse command line and do something.
#
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

use Getopt::Long;

sub cleanup;
sub dir_sort_func;
sub dir_sort;
sub help;
sub dir_size;
sub get_disk_number;
sub unpack_bootlogo;
sub set_boot_flag;

my $opt_file = "./bootdisk";
my $opt_verbose = 0;
my $opt_src = undef;
my $opt_64 = 0;
my $opt_96 = 0;
my $opt_keep = undef;
my $opt_syslinux = -x "/usr/bin/mcopy" ? "/usr/bin/syslinux" : "/usr/bin/syslinux-nomtools";
my $opt_disk = undef;
my $opt_backup_mbr = undef;

my ($boot_disks, $tmp_dir, $buf, $i);

END { cleanup }
$SIG{INT} = \&cleanup;
$SIG{TERM} = \&cleanup;

$ENV{PATH} = "/bin:/usr/bin:/sbin:/usr/sbin";


chomp (my $arch = `uname -m`);
$opt_64 = 1 if $arch eq 'x86_64';

GetOptions(
  'help|h'       => \&help,
  'verbose|v'    => \$opt_verbose,
  'out|o=s'      => \$opt_file,
  '96'           => sub { $opt_64 = 0; $opt_96 = 1 },
  '64'           => sub { $opt_64 = 1; $opt_96 = 0 },
  '32'           => sub { $opt_64 = 0; $opt_96 = 0 },
  'keep'         => \$opt_keep,
  'syslinux=s'   => \$opt_syslinux,
  'partition=s'  => \$opt_disk,
  'backup-mbr=s' => \$opt_backup_mbr,
);

$opt_src = shift;

help unless $opt_src && -d($opt_src);

die "error: must be root to run this script\n" if $< && $opt_disk;

die "error: $opt_syslinux not found\nPlease install package \"syslinux\" first.\n" unless -f($opt_syslinux) && -x($opt_syslinux);

chomp ($tmp_dir = `mktemp -d /tmp/mkbootdisk.XXXXXXXXXX`);
die "error: mktemp failed\n" if $?;

$arch = $opt_64 ? 'x86_64' : 'i386';

my $src = "$opt_src/boot/$arch/loader";
$opt_64 = $opt_96 = 0 if -f "$src/isolinux.cfg";
$src = "$opt_src/boot/loader" unless -f "$src/isolinux.cfg";
$src = "$opt_src/loader" unless -f "$src/isolinux.cfg";
$src = $opt_src unless -f "$src/isolinux.cfg";

die "$opt_src: no $arch installation source\n" unless -f "$src/isolinux.cfg";

mkdir "$tmp_dir/src", 0755;
mkdir "$tmp_dir/mp", 0755;
system "cp -a '$src'/* $tmp_dir/src" and die "error: failed to copy boot loader files\n";

$src = "$tmp_dir/src";

# delete unnecessary files
system "rm -f $tmp_dir/src/{*live*,directory.yast}";
# system "rm -f $tmp_dir/src/{06400480,16001200}.spl";
if(!$opt_96) {
  for my $f (<$src/*64>) {
    if($opt_64) {
      (my $s = $f) =~ s/64$//;
      rename $f, $s;
    }
    else {
      unlink $f;
    }
  }
}

# prepare syslinux config file

open F, "$src/isolinux.cfg"; my @cfg = <F>; close F;
push @cfg, "disksize\t2880\n" unless defined $opt_disk;
open F, ">$src/syslinux.cfg"; print F @cfg; close F,
unlink "$src/isolinux.cfg";
unlink "$src/isolinux.bin";

system "cp $opt_syslinux $src/ldlinux.sys" and die "error: no syslinux?\n";

$boot_disks = get_disk_number $src;
$boot_disks = 2 if $boot_disks < 2;

unlink "$src/ldlinux.sys";

my $mp;

if($opt_disk) {
  # make (usb) disk bootable

  # mbr taken from makebootfat package
  my $new_mbr =
    "\xeb\x58\x90\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" .
    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" .
    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" .
    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" .
    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" .
    "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfa\x31\xc0\x8e\xd8\x8e" .
    "\xc0\x8e\xd0\xbc\x00\x7c\xfb\xfc\x89\xe6\xbf\x00\x06\xb9\x00\x01" .
    "\xf3\xa5\xea\x77\x06\x00\x00\x88\x16\x00\x08\xbe\x9b\x07\xf6\xc2" .
    "\x80\x74\x03\xbe\x9f\x07\xe8\xc7\x00\xb4\x08\xcd\x13\x31\xc0\x88" .
    "\xf0\x40\xa3\x74\x07\x80\xe1\x3f\x88\x0e\x76\x07\xbe\xbe\x07\x31" .
    "\xc0\xb9\x04\x00\xf6\x04\x80\x74\x03\x40\x89\xf7\x83\xc6\x10\xe2" .
    "\xf3\x83\xf8\x01\x74\x03\xe9\x88\x00\x8a\x16\x00\x08\xb8\x00\x41" .
    "\xbb\xaa\x55\x31\xc9\x30\xf6\xf9\xcd\x13\x72\x2e\x81\xfb\x55\xaa" .
    "\x75\x28\xf6\xc1\x01\x74\x23\xbe\xa3\x07\xe8\x73\x00\x57\xbe\x64" .
    "\x07\x8b\x5d\x08\x89\x5c\x08\x8b\x5d\x0a\x89\x5c\x0a\x8a\x16\x00" .
    "\x08\x8c\xd8\x8e\xc0\xb8\x00\x42\xeb\x34\xbe\xa9\x07\xe8\x50\x00" .
    "\x57\x8b\x45\x08\x8b\x55\x0a\xf7\x36\x76\x07\x42\x89\xd1\x31\xd2" .
    "\xf7\x36\x74\x07\x88\xc5\xd1\xe8\xd1\xe8\x24\xc0\x08\xc1\x88\xd6" .
    "\x8a\x16\x00\x08\x8c\xd8\x8e\xc0\xbb\x00\x7c\xb8\x01\x02\xcd\x13" .
    "\x72\x16\x5e\x81\x3e\xfe\x7d\x55\xaa\x75\x08\xfa\xea\x00\x7c\x00" .
    "\x00\x77\x05\xbe\x78\x07\xeb\x03\xbe\x8e\x07\xe8\x02\x00\xeb\xfe" .
    "\xac\x20\xc0\x74\x0c\xb4\x0e\x8a\x3e\x62\x04\xb3\x07\xcd\x10\xeb" .
    "\xef\xc3\x00\x00\x10\x00\x01\x00\x00\x7c\x00\x00\x00\x00\x00\x00" .
    "\x00\x00\x00\x00\x00\x00\x00\x00\x4e\x6f\x20\x6f\x70\x65\x72\x61" .
    "\x74\x69\x6e\x67\x20\x73\x79\x73\x74\x65\x6d\x0d\x0a\x00\x44\x69" .
    "\x73\x6b\x20\x65\x72\x72\x6f\x72\x0d\x0a\x00\x46\x44\x44\x00\x48" .
    "\x44\x44\x00\x20\x45\x42\x49\x4f\x53\x0d\x0a\x00";

  my $part = $opt_disk;

  $opt_disk =~ s/(\d+)$//;
  my $pn = $1;

  die "not a partition: $opt_disk\n" unless $pn ne "";

  $opt_disk =~ s/(?<=\d)p$//;

  print "disk $opt_disk, partition $part\n";

  die "sorry, must be a primary partition (number 1 - 4)\n" if $pn < 1 || $pn > 4;

  my ($bpc, $fatsize);

  for (`fsck.vfat -vn $part 2>/dev/null`) {
    if(/(\d+)\s+bytes\s+per\s+cluster/) {
      $bpc = $1;
      next;
    }
    if(/FATs,\s+(\d+)\s+bit\s+entries/) {
      $fatsize = $1;
      next;
    }
  }

  die "not a FAT file system\n" unless $bpc >= 512 && $fatsize >= 12;

  die "must be 16 bit FAT\n" unless $fatsize == 16;

  die "cluster too large (max. 32k)\n" if $bpc > 0x8000;

  system "$opt_syslinux $part" and die "error: syslinux failed\n";

  die "error: activate failed\n" if set_boot_flag $opt_disk, $pn;

  system "mount -tmsdos $part $tmp_dir/mp" and die "error: mount failed\n";
  $mp = "$tmp_dir/mp";

  for (dir_sort $src) {
    if($_ ne 'bootlogo') {
      system "cp -r $src/$_ $mp" and die "error: copy failed\n";
    }
    else {
      for my $i (unpack_bootlogo $src) {
        system "cp -r $src/$i $mp" and die "error: copy failed\n";
      }
    }
  }

  system "umount $mp" and die "error: umount failed\n";
  undef $mp;

  if($opt_backup_mbr) {
    my $backup;
    open F, "$opt_disk";
    sysread F, $backup, 0x200;
    close F;

    die "mbr backup failed\n" unless length($backup) == 0x200;

    open W, ">$opt_backup_mbr" or die "$opt_backup_mbr: $!\n";
    die "mbr backup failed\n" unless syswrite(W, $backup) == 0x200;
    close W;
  }

  open W, ">$opt_disk" or die "$opt_disk: $!\n";
  die "writing mbr failed\n" unless syswrite(W, $new_mbr) == length($new_mbr);
  close W;

}
else {
  # make boot disk images

  MakeFAT::Image("$tmp_dir/img", $boot_disks, $opt_verbose);

  system "$opt_syslinux $tmp_dir/img" and die "error: syslinux failed\n";

  for (dir_sort $src) {
    system "mcopy -D o -s -i $tmp_dir/img $src/$_ ::" and die "error: copy failed\n";
  }

  my $size = 0;
  for (`mdir -i $tmp_dir/img ::`) {      
    if(/^\s*((\d|\s)+)\s+bytes free\s*$/) {  
      ($size = $1) =~ s/\s+//g;
      $size >>= 10;
      last;
    }
  }    

  $i = "32 bit";
  $i = "64 bit" if $opt_64;
  $i = "32+64 bit" if $opt_96;

  print "Writing $boot_disks boot disks ($i, ${size}k free).\n";

  open F, "$tmp_dir/img";
  for ($i = 1; $i <= $boot_disks; $i++) {
    unlink sprintf("%s%02d", $opt_file, $i);
    open W, sprintf(">%s%02d", $opt_file, $i);
    sysread F, $buf, 1440*1024;
    syswrite W, $buf;
    close W;
  }
  close F;

}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# Unmount image and remove temorary files.
#
sub cleanup
{
  system "umount $mp" if $mp;
  undef $mp;
  system "rm -r $tmp_dir" if ! $opt_keep && -d "$tmp_dir";
  undef $tmp_dir;
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# Sorting function to ensure files are written in the correct order.
#
sub dir_sort_func
{
  my ($wa, $wb, $i, $p, $r);

  $p = 2;
  for $i ( qw ( memtest biostest initrd64 initrd *.tlk .*\.spl linux64 linux bootlogo message .*\.cfg ) ) {
    if($i eq 'bootlogo') {
      $r = $p;
      $p <<= 1;
    }
    $wa = $p if $a =~ /^$i$/;
    $wb = $p if $b =~ /^$i$/;
    $p <<= 1;
  }

  $wa = $r unless $wa;
  $wb = $r unless $wb;

  return $wb - $wa + ($a cmp $b);
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#
# Sort directory.
#
sub dir_sort
{
  my ($i, $size, @dir);

  opendir D, shift;
  @dir = grep { !/^\./ } readdir D;
  closedir D;

  return ( sort dir_sort_func @dir );
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub help
{
  (my $p = $0) =~ s:.*/::;

  print STDERR
  "Usage: $p [options] cd_mount_point\n" .
  "Create boot disk images from SUSE Linux DVD or CD1 or make (USB) disk bootable.\n" .
  "Options:\n" .
  "  --out file\t\twrite disks as fileN (default: bootdisk)\n" .
  "  --32\t\t\tcreate boot disks for 32 bit arch\n" .
  "  --64\t\t\tcreate boot disks for 64 bit arch\n" .
  "  --partition device\tmake this disk with this partition bootable\n" .
  "  --backup-mbr file\tsave mbr to file\n" .
  "Examples:\n" .
  "  $p /media/cdrom\n" .
  "  - write boot disks as bootdisk1 ... bootdiskN (N is approx. 8)\n" .
  "  $p --64 --out foo /media/cdrom\n" .
  "  - write 64 bit boot disks as foo1 ... fooN\n" .
  "  $p --partition /dev/sdb1 --backup-mbr mbr_old /media/cdrom\n" .
  "  - copy install files to /dev/sdb1 and write new mbr to /dev/sdb\n";

  exit 0;
}

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub dir_size
{
  my ($i, $size);
  my ($dir, $block_size) = @_;

  for $i (<$dir/*>) {
    next if -l $i;
    next if $i =~ m|/\.\.?$|;
    $size += dir_size($i, $block_size) if -d $i;
    $size += ((-s $i) + $block_size - 1) / $block_size if -f $i;
  }

  return $size;
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub get_disk_number
{
  my ( $est, $i, $blocks, $block_size, $dir_size, $disks );

  my $src = shift;

  # minimum estimate
  $est = dir_size($src, 2048) / 720 + 1;

  for($i = $est; $i < $est + 10; $i++) {        # max 10 tries
    ($blocks, $block_size) = MakeFAT::Image(undef, $i);
    # print "$i: $est, $blocks, $block_size\n";

    $dir_size = dir_size($src, $block_size);
    # print "$blocks, $dir_size\n";
    if($blocks >= $dir_size) {
      $disks = $i;
      last;
    }
    undef $blocks;
  }

  return $disks;
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub unpack_bootlogo
{
  my ($dir, $tmp, $files, @files, @ext);
  local $_;

  $dir = shift;
  $tmp = "$dir/bootlogo.unpacked";

  mkdir "$tmp", 0755;

  @files = `cpio --quiet -t <$dir/bootlogo`;

  system "cd $tmp; cpio --quiet -i <../bootlogo";

  for (@files) {
    chomp;
    if(-k("$tmp/$_") && ! -l("$tmp/$_")) {
      push @ext, $_;
      undef $_;
    }
  }

  open P, "| cd $tmp; cpio --quiet -o >../bootlogo";
  print P "$_\n" for grep $_, @files;
  close P;

  system "mv $tmp/$_ $dir" for @ext;

  return ( 'bootlogo', @ext );
}


# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
sub set_boot_flag
{
  my $disk = shift;
  my $part = shift;
  my $err = 1;

  if(-x('/sbin/activate')) {
    $err = 0 unless system "/sbin/activate $disk $part";
  }

  if($err && -x('/usr/sbin/parted')) {
    $err = 0 unless system "/usr/sbin/parted $disk set $part boot on";
  }

  return $err;
}

