#!/usr/bin/perl

#Copyright (C) 1999-2005 by  Sbastien Chaumat <schaumat@debian.org>
#                        and Loc Prylli <lprylli@lhpca.univ-lyon1.fr>

#    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.

#    A copy of the GNU General Public License is available as
#    `/usr/share/common-licenses/GPL' in the Debian GNU/Linux distribution
#    or on the World Wide Web at http://www.gnu.org/copyleft/gpl.html.  You
#    can also obtain it by writing to the Free Software Foundation, Inc.,
#    59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

# 09-26-2004
#   *change : fixed double definition of &error
#
# 08-29-2004
#   *change : &loadconf called here and no more in repli-common
#
# 08-19-2004
#   *change : default entry for grub menu is now dhcp wit ha timeout of 5 seconds

use File::Path;
use File::Copy;
use FileHandle;

################
#Init variables#
################
$rootdir='/usr/src/rescue';
$root='/dev/null';
$rootsize=4096;
$uniq = 22;
$global_dir="";
$serial='';
$serialcons='';
$kargs='';
$opt_destfile='';
$tempdir='/tmp';
$grub=1;
$grubprog='grub';
$copy_rd='';
$miniroot_nfsopts = "rsize=8192,wsize=8192";
#start with an empty gateway instead of complaining if it is not defined.
$gateway='';

#############
#load config#
#############

#load the common subs AND the file /etc/replicator/replicator.conf
#specific configuration file can be loaded using the --config option on the command line
use lib (".","/usr/share/replicator");
require("repli-common");
sub loadconf;
&loadconf;

if (not -r $bootkernel){
    &error("No bootkernel found. You must provide one.
    Look in /usr/share/doc/replicator/Replicator-UserGuide.*
    section 4.5.1 \"Compiling a boot kernel for the installation floppy\"
    ");
}

$nfsroot_named = $nfsroot;
$nfsroot_named .= "-%s" if $multi_install;

######
#subs#
######
sub dosystem;
sub docopy;
sub echo_to;
sub check_var;
sub get_ip_of;

#we redefine &error to umount the loop device

sub error {
  print STDERR "ERROR: @_\n";
  if ($global_dir) {
    system("umount $global_dir");
  }
  exit 1;
}

sub usage {
  die "@_\nusage: repli-bootdisk [options]

Options:

*loading a config file after replicator.conf:

\t--config file

*adding a root filesystem on the floppy:

\t--root file : put the filesystem image \"file\" onto the floppy
\t--rootdir path : create a root filesystem from the content of \"path\"

*choosing the boot loader:

\t--alpha
\t--lilo
\t--grub

*boot options:

\t--bootkernel/--kernel/-k file
\t--miniroot_server servername
\t--gateway ipaddress
\t--nfsroot path
\t--kargs kernel-arguments
\t--serial num : boot from ttySnum
\t--disk dev : add an entry for booting from \"dev\" in the boot menu

*choosing the name of the image file:

\t--destfile filename
";
}

sub find_loopdev {
  foreach (0..3) {
    if (`losetup /dev/loop$_` =~ /$_[0]/) {
      return "/dev/loop$_";
    }
  }
  die "cannot find loop device for $_[0]";
}

#
#grub subs
#
sub grub_init_lines {
#later I will allow changing the default

  print $grub_menu "
default 1

timeout 5

root=(fd0)

title Rescue/Utility Disk
kernel=/vmlinuz root=/dev/fd0 $serialcons load_ramdisk=1 prompt_ramdisk=1 ramdisk_start=$root_start ramdisk_size=$rootsize ip=off

title Boot with DHCP and Install a DHCP target
kernel=/vmlinuz root=/dev/nfs $serialcons nfsroot=$miniroot_server:$nfsroot,$miniroot_nfsopts ip=dhcp $kargs
$grub_initrd

title Boot On Hard Disk
chainloader=(hd0)+1

";
}

sub grub_target_line {
  print $grub_menu "title Network \"$network\" : Boot \"$host\" from ${miniroot_server}:${nfsroot_one}
kernel=/vmlinuz root=/dev/nfs $serialcons ip=${ipaddr}:${miniroot_server}:${gateway}:${netmask}:${host}:eth0:off root=/dev/nfs nfsroot=${nfsroot_one},$miniroot_nfsopts $kargs
$grub_initrd
";
}

#
#aboot subs
#
sub aboot_init_lines {
  $abootindex=0;
  print $aboot_conf "$abootindex:1/vmlinuz srm_setup=1 root=/dev/fd0 $serialcons load_ramdisk=1 prompt_ramdisk=1 ramdisk_size=5120 ip=off\n";
  $abootindex += 1;
  print $aboot_conf "$abootindex:1/vmlinuz srm_setup=1\n";
  $abootindex += 1;

  $defaultnum = 0;
  if ($default eq 'floppy') {
    $defaultnum = 0;
  } elsif ($default eq 'hd') {
    $defaultnum = 1;
  } else {
    for ($i=0;$i<@targets;$i++) {
      if ($targets[$i] eq $default) {
	$defaultnum = 2 + $i;
	last;
      }
    }
  }
}

sub aboot_target_line {
  print $aboot_conf "$abootindex:1/vmlinuz srm_setup=1 $serialcons ip=${ipaddr}:${miniroot_server}:${gateway}:255.255.255.0:${host}:eth0:off root=/dev/nfs nfsroot=${nfsroot_one},$miniroot_nfsopts $kargs $lilo_initrd\n";
}

#
#lilo subs
#
sub lilo_init_lines{
  print $lilo_conf "
disk=$loop
bios=0x00
heads=2
cylinders=80
sectors=18

#disk=/dev/sda
#bios=0x80
#heads=2
#cylinders=80
#sectors=18

boot = $loop
prompt
vga = normal    # force sane state
compact
$serial

default=$default

image = /vmlinuz
  label = floppy
  append=\"$serialcons load_ramdisk=1 prompt_ramdisk=1 ramdisk_start=$root_start ramdisk_size=$rootsize ip=off\"
  root = /dev/fd0
  read-write

image = /vmlinuz
  label = linux
  append=\"$serialcons ip=off\"
  root = /dev/fd0
  read-write


other = /dev/$target_disk
  label = hd
  unsafe

";
}

sub lilo_target_line {
  print $lilo_conf "
image = /vmlinuz
  label = $host
  append=\"$serialcons ip=${ipaddr}:${miniroot_server}:${gateway}:${netmask}:${host}:eth0:off root=/dev/nfs nfsroot=${nfsroot_one},$miniroot_nfsopts $kargs\"
  $lilo_initrd
  root = /dev/fd0
  read-write
";
}

sub do_line {
  local $ipaddr = get_ip_of($host);
  local $nfsroot_one = $nfsroot_named;
  $nfsroot_one =~ s/%s/$host/;
  chomp $ipaddr;
  lilo_target_line;
  aboot_target_line;
  grub_target_line;
}

sub bootloaders_targets_lines {
  if (@networks) {
    foreach $n (@networks) {
      $network= $n->[$NETWORK];
      $targets_list=$n->[$TARGETS];
      $netmask=$n->[$NETMASK];
      $gateway= $n->[$GATEWAY];
#      $broadcast= $n->[$BROADCAST];
      foreach $host (@$targets_list) {
	do_line;
      }
    }
  }
  elsif (@targets) {
    print STDERR "WARNING: Using old style \@target syntax in config file; Please read the doc\n"; 
    foreach $host (@targets) {
      do_line;
    }
  }
}

##############################
#parse command line arguments#
##############################
GetOptions("root:s",
	   "rootdir:s"=>sub{$rootdir=$_[1];$root="";},
	   "bootkernel|kernel:s",
	   "rootsize:i","miniroot_server:s","gateway:s","nfsroot:s","kargs:s",
	   "destfile:s","alpha","lilo","grub","disk:s","config:s",
	   "serial:s"=>sub{
	     $port =$_[1];
	     $serial="serial=$port,9600n8";
	     $serialcons = "console=ttyS$port,9600n8 CONSOLE=/dev/ttyS$port";}
	  ) or usage();

#possible other arguments:
#          "default:s",
#	   "targets:s"=>\@targetscmdline,
#	   "target:s"=>sub{
#	     push @targets,$_[1];
#	     $default=$_[1];
#	   }

if (@targetscmdline){@targets=split(",",join(",",@targetscmdline))};

if ($config) {
  if (-e $config) {	
    check_perms($config);
    require($config);
  }else {error("$config doesn't exist")};
}

###############
#Sanity checks#
###############

#check if you are root
$ui=(getpwuid($<))[3];
unless ($ui eq "0"){error("You must run this script as root.")};

#default miniroot server is the machine where replicator is installed
unless ($miniroot_server) {
  $miniroot_server=`uname -n`;
  chomp($miniroot_server);
}

unless ($bootkernel) {
  $bootkernel = "$nfsroot/vmlinuz";
  $initrd = "$nfsroot/boot/initrd-miniroot" if -r "$nfsroot/boot/initrd-miniroot";
}

if ($initrd) {
  $lilo_initrd = "initrd=/initrd.img";
  $grub_initrd = "initrd /initrd.img";
}


#############################
#dump bootdisk configuration#
#############################
if (@networks) {
  print "\n--------------------------------------------\n";
  print "This bootdisk will allow you to install :\n\n"; 
  foreach $n (@networks) {
    $network= $n->[$NETWORK];
    $targets_list=$n->[$TARGETS];
    print "In network $network :\n";
    foreach $host (@$targets_list) {
      my $ip=&get_ip_of($host);
      print "\t $host : $ip\n";
     $default ||= $host; #ignored
    }
  }
   $default ||= "linux"; #ignored
}
elsif (@targets){
  $default=$targets[0]; #ignored
  check_var("netmask");
  print STDERR "
###############################################################################
#WARNING: Using old style \@target syntax in config file; Please read the doc #
###############################################################################
";
  print "\n This bootdisk will allow you to install : @targets \n"; 
}
else {
  $default="dhcp-copy"; #ignored
}
print "\nThis bootdisk will allow you to install dhcp clients (default).\n";
print "\n--------------------------------------------\n";

$quiet="";
if (!$verbose){$quiet=">/dev/null 2>&1"};

&verbose("The miniroot filesystem will be mounted with these options : $miniroot_nfsopts");

$miniroot_server=get_ip_of($miniroot_server);
chomp($miniroot_server);
if (($gateway ne '')){$gateway=get_ip_of($gateway);
chomp($gateway);}

if (@target){
  print "Default copy : $default
Bootkernel parameters for non-dhcp replication : server=$miniroot_server  gw=$gateway nfs=$nfsroot\n";
}

print "Bootkernel parameters for dhcp replication : $serialcons nfsroot=$miniroot_server:$nfsroot,$miniroot_nfsopts ip=dhcp\n";

print "Press CTRL-C to abort now. Press Enter to continue.";
$res=<STDIN>;
##################
#create rescue fs#
##################
#
#if we dont pass it as an argument, we create a second filesystem (empty) on the floppy.
#This is a root filesystem in case we want to have some tiny system on it.
#
if (!$root) {
  my ($dir,$file) = ("$tempdir/makedd.mnt.$uniq","$tempdir/makedd.raw.$uniq");
  mkpath(["$dir"],1,0755) or error "making dir";
  dosystem("dd bs=1k count=$rootsize < /dev/zero > $file");
  dosystem("mke2fs -F $file $rootsize $quiet");
  dosystem("mount $file -oloop $dir");
  dosystem("cp -a $rootdir/* $dir");
  dosystem("umount $dir");
  dosystem("gzip $file");
  $root="$file.gz";
  unlink($dir);
}



#if ($lilo) {
#  lilo_lines \*STDOUT,\*STDOUT;
#  exit 0;
#}


#
#We want to add an entry in the bootloader config to allow to boot from the target hardrive instead of the floppy.
#If $target_disk is not defined then we suppose the target computer has the same hardware than the model
#

$target_disk or $target_disk=system("sfdisk -g /dev/sda") == 0 ? "sda" : system("sfdisk -g /dev/hda") == 0 ? "hda" : system("sfdisk -g /dev/rd/c0d0") == 0 ? "rd/c0d0" : die "no appropriate disk";

{print STDERR "With the bootfloppy you will also be able to boot on the target's disk: /dev/$target_disk\n"};

################
#create boot fs#
################

#
#init the  filesystem on the floppy where we will put the kernel
#
$floppysize=1440;
$pseudosz=int(((-s $root)+1023)/1024);
$root_start=$floppysize-$pseudosz;
my ($dir,$file) =  ("$tempdir/makedd.mnt2.$uniq","$tempdir/makedd.raw2.$uniq");
dosystem("dd bs=1k count=$floppysize < /dev/zero > $file");
dosystem("mke2fs -F $file $root_start $quiet");
-d "$dir" or mkpath(["/$dir"],1,0755) or error "making $dir";
system("umount $dir");
dosystem("mount -oloop $file $dir");
$global_dir = $dir;
#use the devices provided by debootstrap, so we can deal with or without devfs (Jerome Warnier)
&dosystem("cd $dir ; tar zxvf /usr/lib/debootstrap/devices.tar.gz dev/ram* dev/fd0 dev/sda* dev/hda* dev/loop*");
#for raid devices
if ($copy_rd){
  -r "/dev/rd" && dosystem("cp -a /dev/rd $dir/dev");
}
mkdir("$dir/boot",0755) or error "creating $dir/boot";
if ($grub) {
  dosystem("cp -a $grubdir/stage1 $grubdir/stage2 $dir/boot");
} elsif ($debarch eq 'i386') {
  dosystem("cp -a /boot/*.b $dir/boot");
}
mkdir "$dir/etc",0755 or error "creating $dir/etc";

#
#create bootloader config files
#
$loop = find_loopdev $file;
$lilo_conf = new FileHandle "$dir/etc/lilo.conf","w" or error "creating lilo.conf";
$aboot_conf = new FileHandle "$dir/etc/aboot.conf","w" or error "creating aboot.conf";
$grub_menu = new FileHandle "$dir/etc/grub.menu","w" or error "creating grub.menu";



lilo_init_lines;
aboot_init_lines;
grub_init_lines;
bootloaders_targets_lines;

$lilo_conf->close;
$aboot_conf->close;
$grub_menu->close;

#
#put the kernel on the boot filesystem and install bootloader
#

docopy($bootkernel,"$dir/vmlinuz");
if ($grub) {
  dosystem("umount $dir");
  $global_dir = "";
  my $fh = new FileHandle "| $grubprog --batch --no-floppy" or die "cannot execute grub";
  print $fh "device (fd0) $file
root=(fd0)
install (fd0)/boot/stage1 (fd0) (fd0)/boot/stage2 p /etc/grub.menu
quit
" or die "executing grub commands";
  $fh->close or die "finishing grub command";
} elsif ($debarch eq 'i386') {
  dosystem("ROOT=$dir lilo -v");
  dosystem("umount $dir");
  $global_dir = "";
} elsif ($debarch eq 'alpha') {
  dosystem("umount $dir");
  $global_dir = "";
  dosystem("e2writeboot $file /boot/bootlx");
  dosystem("sdisklabel $file zero");
  dosystem("sdisklabel $file 0 0 2880 8");
  dosystem("sdisklabel $file print");
  dosystem("abootconf $file 1");
  dosystem("sdisklabel $file print");
} else {
  die "$debarch unknown!!";
}

dosystem("dd bs=1k seek=$root_start if=$root of=$file");
rmdir($dir);

if ($tftpboot) {
  docopy($file,"$tftpboot/$default.X");
} elsif (!$opt_destfile) {
  rename($file,"$tempdir/$default.img");
  print STDERR "The bootdisk image is $tempdir/$default.img\n";
  print STDERR "Press <enter> to copy it on /dev/fd0  or ctrl-c to abort \n";
  $rep=<STDIN>;
  chomp($rep);
  if(!$rep){
    docopy("$tempdir/$default.img","/dev/fd0");
  }else {
    die "\nCopy Aborted\n You can manually copy $tempdir/$default.img to your floppy\n"
  }
} else {
  docopy($file,"$opt_destfile");
}
