#!/usr/bin/perl

#Copyright (C) 1999-2005 by  Sebastien Chaumat <schaumat@debian.org>
#                        and Loic 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.

# changelog

# 08-29-2004
#   *fixed : missing \ in creation of repli-sync.conf
#
# 08-28-2004
#   *fixed : test of $debian_version in netconf
#
# 08-17-2004
#   *add : logic to choose the bootloader between lilo and grub
#   *change : create_lilo_fstab split into create_fstab and create_lilo_conf
#
# 21-12-2003
#   *add : option -n to every mount call (to allow mutliple simultaneous use of the miniroot)


use FileHandle;
use File::Path;
use File::Copy;
use Getopt::Long;

#
#set default values
#
$mount="mount";
$verbose='';
$dryrun= 1;

#if we define serial here, it will be the default lilo entry even if no serial
#parameters is set into the configuration file.
#$serial = "serial=1,9600n8";
#$serialcons = "console=ttyS1,9600n8 CONSOLE=/dev/ttyS1";
$serial='';
$serialnum = 1;

$preserve_firstpart = '';
$usr_is_nfs = '';
$root_is_nfs = '';

$locallink = '';

$custom_part='';

$confile= "/etc/replicator/replicator.conf";
$rshprog= "rsh";

$ssh_opt = "-e $rshprog";

#use a 512byte sector as unit
@autopart_specs =
#mount point    	min_size	max_size 
  ([ "/" , 		"100Mo",	"100Mo"],
   [ "swap", 		"64Mo",		"64Mo"],
   [ "/usr" , 		"800Mo",	"2000Mo"],
   [ "/export/home", 	"remaining"  ]
  );

$skip_bootloader=0;

#in the miniroot
use lib (".","/usr/share/replicator");
require("repli-common");
&loadconf;

#sub that are defined in repli-common and used here :
sub error;
sub get_ip_of;
sub check_var;
sub good_disk;
sub find_disk;
sub calc_part_bound;
sub sizeconv;

#retrieve the network config of the target knowing only its name
sub find_host_conf {
  my $host=$_[0];
  &verbose("looking for network configuration for host $host\n");
  my ($h,$n);
  if (@networks) {
    foreach $n (@networks) {
      $targets_list=$n->[$TARGETS];
      &verbose("parsing host list of network $targets_list");
      foreach $h (@$targets_list) {
	&verbose("  parsing network configuration of host $h");
	if ($h eq $host) {
	  $network=$n->[$NETWORK];
	  $netmask=$n->[$NETMASK];
	  $gateway=$n->[$GATEWAY];
	  $broadcast=$n->[$BROADCAST];
	  $domainname=$n->[$DOMAINNAME];
	  &verbose("network configuration found (good!)");
	  &check_var qw(netmask network broadcast gateway domainname);
	  return 0;
	}
      }
    }
   &verbose("sorry I was unable to find the network configuration of host $host in the list \@networks");
   check_var qw(netmask network broadcast gateway domainname);
  }
  &verbose("no \@networks variable in $conffile");
  check_var qw(netmask network broadcast gateway domainname);
}

sub dodryrun {
  my ($package,$filename,$line) = caller;
  &verbose("exec: $_[0]");
  if (!$dryrun) {
  system($_[0]) and &error("Command failed!!! at $package, $filename, $line : $_[0]\nHINT : $_[1]");
  }
}

sub drycopy{
  $dryrun or &docopy(@_);
}

sub ptypeid {
  my $p = $_[0];
  return $p->[$PID] ? $p->[$PID] : ($p->[$PDIR] eq "swap") ? "82" : "83";
}

sub ptypestring {
  my $p = $_[0];
    my $pid = ptypeid($p);
  return  $pid eq "82" ? "swap" : $pid eq "83" ? "auto" : "auto";
}

sub gen_fstab {
  my $fstab="";
  foreach $p (@autopart_specs) {
    $fstab .= join(" ",$p->[$PDEVICE], $p->[$PDIR],ptypestring($p),
		   "defaults",0,2,"\n");
  }
  $fstab .= "proc /proc proc defaults 0 0\n";
  $usr_is_nfs and $fstab .= "$model:/usr /usr $nfstype $nfsopts,ro 0 0 \n";
  $fstab_supp and $fstab .= $fstab_supp;
  return $fstab;
}

# partition a disk
sub part_disk {
  my ($disk,$diskp,$size,$cyls,$heads,$sects) = @{$_[0]};
  # take into account reserved partition (e.g. multiboot)
  my $start = $sects;
  if ($preserve_firstpart) {
    print STDERR "sfdisk -d $disk\n";
    my $f = new FileHandle "sfdisk -d $disk|";
    my $firspart;
    my $part = "${disk}1";
    my ($s,$sz,$id);
    while (<$f>) {
      if (/^$part : start=\s*(\d+), size=\s*(\d+), Id=\s*(\d+)(, bootable)?$/) {
        ($s,$sz,$id) =  ($1,$2,"$3".($4?",*":""));
        last;
      }
    }
    $s or die "cannot find $part\n";
    $s == $sects or die "start at $s != sector per track($sects)\n";
    $id eq "6,*" or $id == 83 or die;
    $firstpart = [ "/$preserve_firstpart", 0 , 0];
    $firstpart->[$PID] = $id;
    $firstpart->[$PSIZE] = $sz;
    $firstpart->[$PINIT] = 0;
    $size -= $sz;
  }


  # allocation des partitions autre que primary
  if ($size > $param{'maxsize'}) {
    foreach $p (@autopart_specs) {
      if ($p->[$PMIN] eq "remaining") {
	$p->[$PSIZE] = $size-$param{'maxsize'};
	if ($p->[$PSIZE] < 50000*2) {
          print STDERR "not enough room for home\n";
	  $#autopart_specs = $#autopart_specs - 1;
	}
      } else {
	$p->[$PSIZE] = $p->[$PMAX];
      }
    }
  } else {
    foreach $p (@autopart_specs) {
      if ($p->[$PMIN] eq "remaining") {
	$#autopart_specs = $#autopart_specs - 1;
      } else {
	$p->[$PSIZE] = $p->[$PMIN]+
	  int(($size-$param{'minsize'})*1.0*
	      ($p->[$PMAX]-$p->[$PMIN])/($param{'maxsize'} -$param{'minsize'}));
      }
    }
  }

  #now safe to add the primary partition to %autopart_specs
  $firstpart and unshift  @autopart_specs, $firstpart;

  foreach $p (@autopart_specs) {
    $p->[$PSTART] = $start;
    $start += $p->[$PSIZE];
    my $mod = $start % ($heads*$sects);
    error "automatic partitioning impossible because of problems with cylinder/head alignement"
      if ($mod > $p->[$PSIZE]/2);
    !$firstpart or $p ne $firstpart or ($p->[$PSTART] == $sects && $mod == 0)
      or die "firstpart not correct:($start,$mod)";
    $start -= $mod;
    $p->[$PSIZE] -= $mod;
  }
  my $end = $start;

  foreach $p (@autopart_specs) {
    print "part $p->[$PDIR] , start=".$p->[$PSTART].", size= ".$p->[$PSIZE]." (".($p->[$PSIZE]/1000/2)." Mbyte)\n";
  }
  my $sfdisk="unit: sectors\n";

  my $extended = (@autopart_specs > 4) && $debarch eq 'i386';
  foreach $i (0..$#autopart_specs) {
    my $p = $autopart_specs[$i];
    if ($extended && $i == 3) {
      $sfdisk .= "$p->[$PSTART],".($end-$p->[$PSTART]).",5\n";
    }
    if ($extended && $i >= 3) {
      $p->[$PSTART] += $sects;
      $p->[$PSIZE] -= $sects;
    }
    $p->[$PDEVICE] = "$diskp".($extended &&  $i >= 3 ? $i+2 : $i+1);
    $sfdisk .= "$p->[$PSTART],$p->[$PSIZE],".ptypeid($p)."\n";
    if (!$firstpart || $i > 0) {
      $p->[$PINIT] = 1;
    }
  }
  return ($sfdisk,gen_fstab());
}

sub do_custom_disk {
  my ($disk,$diskp,$size,$cyls,$heads,$sects) = @{$_[0]};
  @autopart_specs= ();
  foreach $p (@manualpart_specs) {
     print STDERR "$p->[$PDIR] => ".$diskp.$p->[1]."\n" if $verbose;
     my $tab = [ $p->[$PDIR], 0,0 ];
     $tab->[$PDEVICE] = $diskp.$p->[1];
     $tab->[$PINIT] = $p->[2];
     push @autopart_specs,$tab;
  }
  return gen_fstab();
}

# really execute sfdisk to repartition new driver

sub do_sfdisk {
  my ($disk,$sfdisk) = @_;
  my $basedisk = $disk;
  $basedisk =~ s,/dev/,,;
  my $out = `sfdisk -R $disk 2>&1`;
  print STDERR "sfdisk -R: $out\n";
  if (!$dryrun and $out) {
    die "$disk in use, will not install over it\n";
  }
  if (!$dryrun) {
    if ($debarch eq 'i386') {
      my $fh = new FileHandle "| sfdisk $disk" or error "cannot exec $sfdisk\n";
      print "Writing to sfdisk: $sfdisk\n";
      print $fh $sfdisk or error "cannot execute sfdisk";
      $fh->close or error "cannot complete sfdisk normally";
    } else {
      &dodryrun("sdisklabel $disk zero");
      foreach $i (0..$#autopart_specs) {
	my $p = $autopart_specs[$i];
	my $ptype = $p->[$PDIR] eq 'swap' ? 1 : 8;
	&dodryrun("sdisklabel $disk $i $p->[$PSTART] $p->[$PSIZE] $ptype");
      }
      &dodryrun("sfdisk -R $disk");
    }
  }
}


sub create_partitions {
  # creates filesystems
  foreach $p (@autopart_specs) {
    if (!$p->[$PINIT]) {
      print STDERR "preserving $p->[$PDEVICE] ".
	"(for $p->[$PDIR])\n";	
    } elsif ($p->[$PDIR] eq 'swap') {
      &dodryrun("mkswap $p->[$PDEVICE];sync");
    } else {
	my $mkfs_command = $p->[$PMKFS] || $mkfs;
	&dodryrun("dd if=/dev/zero of=$p->[$PDEVICE] bs=512 count=1");
	&dodryrun("$mkfs_command $p->[$PDEVICE];sync");
    }
  }
}

sub mount_partitions {
  # here I need to mount / first
  foreach $p (@autopart_specs) {
    if ($p->[$PDIR] eq '/') {
      if (!$firstpart or $firstpart ne $p) {
        my $part_fs="auto";
	&dodryrun("mkdir -p /target");
	&dodryrun("$mount -t$part_fs $p->[$PDEVICE] /target");
      }
    }
  }
  # now create mountpoint and mount everything except "/" and "swap"
  foreach $p (@autopart_specs) {
    if (($p->[$PDIR] ne 'swap') and ($p->[$PDIR] ne '/')) {
      my $dir = "/target".$p->[$PDIR];
      print STDERR "mkpath:$dir\n";
      $dryrun or mkpath([$dir],1,0755);
      if (!$firstpart or $firstpart ne $p) {
	my $part_fs="auto";
	&dodryrun("$mount -t$part_fs $p->[$PDEVICE] $dir");
      }
    }
  }
  if ($usr_is_nfs) {
    $dryrun or mkpath(["/target/usr"],1,0755);
    &dodryrun("$mount -r -t $nfstype -o$nfsopts $model:/usr /target/usr");
  }
}

sub setup_disk {
  my ($disktab) = find_disk;
  print STDERR "found disk:",join(" ",@$disktab)."\n";
  my ($sfdisk,$fstab) = part_disk $disktab;
  print "sfdisk parameters:\n$sfdisk\n";
  print "fstab file:\n$fstab\n";
  # repartition target driver
  do_sfdisk $disktab->[0],$sfdisk;
  create_partitions;
  mount_partitions;
  $dryrun or &create_fstab($disktab->[0],$disktab->[1],$fstab);
}

sub custom_disk {
  #to avoid checking disk size against @autopart_specs
  $param{'minsize'}=0;
  my ($disktab) = find_disk;
  print STDERR "found disk:",join(" ",@$disktab)."\n";
  my $fstab = do_custom_disk $disktab;
  print "fstab file:\n$fstab\n";
  # repartition target driver
  create_partitions;
  mount_partitions;
  $dryrun or &create_fstab($disktab->[0],$disktab->[1],$fstab);
}

sub setup_nfsroot {
  $dryrun or mkpath([$installtarget,"$installtarget/etc"],1,0755);
  my $f = new FileHandle "> $installtarget/etc/fstab" or error "creating fstab";
  print $f "# fstab for $myhostname
/dev/root / nfs-root defaults 0 0
none          /proc          proc               defaults        0       0
$model:/usr /usr nfs ro 0 0
";
  print $f $fstab_supp;
  $f->close;
}

sub install_fastnet {
  my ($server,$filename) = @_;
    error "do not know server and hostname parameters"
}

sub install_net {
  &check_var(qw(model myhostname));
  &dosystem("repli-update --config $confile $ssh_opt --dev $rupdate_dry $netcopy_exclude --model $model --destdir $installtarget");
}

sub create_fstab {
  my ($disk,$diskp,$fstab) = @_;
  if (! -d "/target/etc") {
    mkpath(["/target/etc"],1,0755) or error "creating /target/etc";
  }
  unless ($skip_fstab) {
    my $fh = new FileHandle "/target/etc/fstab","w" or error "writing fstab:$!";
    print $fh $fstab;
    $fh->close or error "writing fstab";
  }
}

sub create_lilo_conf {
  my $disk=$_[0];
  unless ($skip_lilo) {
    $fh = new FileHandle "/target/etc/lilo.conf","w" or error "writing lilo.conf";
    #here I need to find the root partition: either I should read the spec or use a chroot trick to do it
    my $root;
    #patch by Dominique Ponsard
    foreach $line (@autopart_specs){
      # break the loop on the first matching entry
      if ($line->[$DIR] eq "/") {$root = $line->[$PDEVICE]; last;}
    }
    $root or error "Cannot determine root partition in part_specs (for lilo)";
    print $fh "
     boot=$disk
     root=$root
    " or error "writing lilo.conf:$!";
    if ($lilo_template)
      {
	my $tmpl = new FileHandle "/etc/replicator/lilo.conf.templ" or error "opening lilo template:$!";
	while (<$tmpl>)
	  {
	    next if /^root=/ || /^boot=/;
	    print $fh $_ or error("unable to copy $lilo_template $_[0] to $_[1]");
	  }
	$tmpl->close;
      }
    else
      {
	my $default = $serial ? "serial" : "linux";
	print $fh "
prompt
timeout=50

linear

default=$default

$serial

vga=normal

image=/vmlinuz
  label=linux
  append=\"console=tty0 CONSOLE=/dev/tty0 ip=off\"

image=/vmlinuz
  label=serial
  append=\"$serialcons ip=off\"
  read-only

other=/dev/fd0
label=floppy
unsafe
"
      }
    $fh->close or error "writing lilo.conf:$!"; 
  }
}

sub install_grub {
  my $disk=$_[0];
  unless (&is_installed("grub",$installtarget)) {
    print STDERR "WARNING : grub package is not installed onto the target.
Installing grub from the miniroot\n";
  }
  &dodryrun("/sbin/grub-install --root-directory=$installtarget $disk");
}

sub my_inet {
  my $inet;
  if (!$root_is_nfs) {
    $inet=`/sbin/ifconfig eth0`;
    $inet =~ m/inet addr:([0-9.]+)/ or die "cannot parse ifconfig output:$inet\n";
    $inet = $1;
  } else {
    $inet=get_ip_of($myhostname);
    chomp($inet);
  }
  return $inet;
}

sub do_netconf {
  my $netfile =  "$installtarget/etc/network/interfaces";
  mkpath(["$installtarget/etc/dhcpc","$installtarget/etc/default"],1,0755);
  my $f = new FileHandle ">$netfile" or die "cannot open $netfile:$!";
  unless ($debian_version eq "potato") {print $f "auto lo\n"};
  print $f "iface lo inet loopback
";
  if (!$root_is_nfs) {
    if (!&use_dhcp) {
      check_var qw(netmask network broadcast);
      my $inet = my_inet;
      unless ($debian_version eq "potato"){print $f "auto eth0\n"};
      print $f "
iface eth0 inet static
     address $inet
     network $network
     netmask $netmask
     broadcast $broadcast\n" or die;
      print $f "gateway $gateway\n" if $gateway;
      #    print $d "
      #IFACE=NONE
      #OPTIONS=
      #";
    }
    else {
      unless ($debian_version eq "potato"){print $f "auto eth0\n"};
     print $f "
iface eth0 inet dhcp
    hostname $myhostname
";
    }
  }
  $f->close;
  chmod(0644,"$netfile");
}


sub do_hostconf {
  my $hostfile = $_[0];
  my $f = new FileHandle ">$hostfile" or die "creating $hostfile";
  print STDERR "creating $hostfile\n";
  my $inet = my_inet;
  print $f "127.0.0.1 localhost\n";
  print $f "$inet ".fullname($myhostname,$domainname)." $myhostname\n";
  print STDERR "$inet $myhostname.$domainname $myhostname\n";
#
#  my $mounts  = `cat /proc/mounts`;
#  $mounts =~ m/addr=([0-9.]+)/ or die "cannot parse /proc/mounts:$_\n";
#  print $f "$1 model\n";
  $hosts_supp and print $f $hosts_supp;
  $f->close;
}


sub do_configure {

  # copy passwd and group
  foreach (qw(/etc/passwd /etc/group)) {
    -r "$installtarget/$_" or drycopy($_,"$installtarget/$_");
  }
  # initialize files
  if (!$dryrun) {
    do_hostconf "$installtarget/etc/hosts";
    do_netconf;
    my $f = new FileHandle ">$installtarget/var/log/wtmp";
    $f->close;
    chown(scalar(getpwnam("root")),scalar(getgrnam("adm")),"/var/log/wtmp");
    $f = new FileHandle ">$installtarget/etc/exports";
    $f->close;  
    if (! -e "$installtarget/etc/modules") {$f = new FileHandle ">$installtarget/etc/modules";
    $f->close;}
    $f = new FileHandle ">$installtarget/etc/hostname" or die "cannot create /etc/hostname";
    print $f "$myhostname\n";
    $f->close;
    mkpath(["$installtarget/etc/replicator"],1,0755);
    $f = new FileHandle ">$installtarget/etc/replicator/repli-sync.conf" or die "cannot create /etc/replicator/repli-sync.conf";
    print $f "\$verbose = 0;\n";
    print $f "\$model = \"$model\";\n";
    my $nousr = 0;
    if ($usr_is_nfs or $root_is_nfs) {
      $nousr = 1;
    }
    print $f "\$nousr = $nousr;\n";
    print $f "\$noboot = 1;\n";
    print $f "\$norcd = 1;\n";
    print $f "#do not remove the following line..
\$the_end=1;";
    $f->close;
  }
  if ($serial) {
    dosystem("perl -i -lpe 's/#T0/T0/' $installtarget/etc/inittab");
  }
  # INSTALL BOOTLOADER
  unless ($root_is_nfs or $skip_bootloader) {
    my ($disktab) = find_disk;
    print STDERR "found disk:",join(" ",@$disktab)."\n";
    my $disk = $disktab->[0];
    if (&is_installed("lilo",$installtarget)){
      &create_lilo_conf($disk);
      &dodryrun("chroot $installtarget mount /proc");
      &dodryrun("chroot $installtarget /sbin/lilo -v");
      &dodryrun("chroot $installtarget umount /proc");
    } else {
      &install_grub($disk);
    }
  }
  $skip_bootloader && print STDERR "NOTICE : skip_bootloader selected : no bootloader installed.\n";
}


sub usage {
  print STDERR "@_\n";
  die "usage: main-install [--keyb] [--serial <num> ] [ --erase-all ] [ --usr-nfs ]
                     [ --diskless ] [ --local <localdir-link> ] [ --diskless hostname ] [ --model <model-host> ] { hdsetup | hostconf | netsetup | netcopy | configure | all\n";
}

####################
#PROGRAM START HERE#
####################


#
#read command line arguments
#

GetOptions ('verbose' => \$verbose,
            'real'   => sub { $dryrun = ''; },
	    'keyb'   => sub { $serial = ''; },
	    'serial=i' => sub { $serialnum = $_[1];
				$serial = "serial=$serialnum,9600n8";
				$serialcons = "console=ttyS$serialnum CONSOLE=/dev/ttyS$serialnum"; },
	    'erase-all' => sub{$preserve_firstpart = ''; },
	    'preserve_firstpart=s' => \$preserve_firstpart,
	    'usr-nfs' => sub { $usr_is_nfs = 1;},
	    'nfsroot=s' => \$nfsroot,
	    'diskless' => sub {$myhostname=$_[1];
			       $usr_is_nfs = 1;
			       $root_is_nfs = 1;},
	    'local' => sub {$locallink =$_[1];},
	    'model=s' => \$model,
	    'target_disk=s' =>\$target_disk,
	    'custom_part' =>\$custom_part,
	   ) or &usage;

$command=shift @ARGV or &usage;


#
#compute partitions boundaries
#
&calc_part_bound;

#
#find the name of the target we are installing
#
if ($root_is_nfs) {
  $myhostname or die "myhostname is not defined";
}
else {
  !$myhostname or die "myhostname is defined ($myhostname)??!?\n";
  $myhostname ||= `hostname`;
  chomp $myhostname;
}

#
#now load the network configuration of this host
#
find_host_conf($myhostname) unless &use_dhcp;

unless ($model){
  $mf="$confdir/$model_info";
  if (-r $mf){require($mf)}
}

if (&use_dhcp){
  $domainname=`cat /proc/net/pnp|grep domain`;
  chomp($domainname);
  $domainname=~s/domain\s//;
}

$netcopy_exclude = $usr_is_nfs ? '--nousr' : '';
if ($root_is_nfs) {
  $usr_is_nfs = 1;
  $installtarget = $nfsroot;
  $installtarget =~ s/%s/$myhostname/;
} else {
  $installtarget = "/target";
}
$rsync_dry = $dryrun ? '--dry-run' : '';
$rupdate_dry = $dryrun ? '' : '--real' ;

$_ = $command;
if ($_ eq 'netcopy') {
  install_net;
} elsif ($_ eq 'hostconf') {
  do_hostconf "/target/etc/hosts";
} elsif ($_ eq 'hdsetup' && !$root_is_nfs) {
  $custom_part ? custom_disk : setup_disk;
} elsif ($_ eq 'netsetup' && $root_is_nfs) {
  setup_nfsroot;
} elsif ($_ eq 'fastcopy') {
  install_fastnet;
} elsif ($_ eq 'configure') {
  do_configure;
} elsif ($_ eq 'all') {
  #$root_is_nfs or do_hostconf "/etc/hosts"; #pb les partitions ne sont pas montees
  if ($custom_part){
    check_list_var(qw(manualpart_specs));
    custom_disk;
  }
  elsif ($root_is_nfs){
    setup_nfsroot;
  }
  else {
    setup_disk;
  }
  #  $custom_part ? custom_disk : setup_disk;  
  #  $root_is_nfs ? setup_nfsroot : setup_disk; # les partitions sont montees
  #ajout seb
  $root_is_nfs or do_hostconf "/target/etc/hosts";
  install_net;
  do_configure;
} elsif ($_ eq 'fakedirs') {
  mkpath([
         "$installtarget/boot",
         "$installtarget/var/lib/dpkg",
	 "$installtarget/var/lib/texmf/web2c",
	 "$installtarget/etc/init.d",
	 "$installtarget/var/log"
	 ],1,0755);
} else {
  usage "unknown command type: $command";
}
