#!/usr/bin/perl

# $Id$
#*********************************************************************
#
# setup_harddisks -- create partitions and filesystems on harddisk
#
# This script is part of FAI (Fully Automatic Installation)
# Copyright (c) 1999, 2000 by ScALE Workgroup, Universitaet zu Koeln
# Copyright (c) 2000-2009 by Thomas Lange, Uni Koeln
#
#*********************************************************************
# 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.
#
# You should have received a copy of the GNU General Public License
# along with this program; see the file COPYING. If not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
# MA 02111-1307, USA.
#*********************************************************************
#
# This program first read the configfiles, partitions and formats the harddisks,
# produces fstab and FAI-variables-file.  It uses sfdisk, mke2fs, mkswap
#
# Parameters:
# [-X]                     no test, your harddisks will be formated
#                          default: only test, no real formating
# [-f<config-filename>]    default: parse classes
# [-c<class-path>]         default: $FAI/disk_config/
# [-d]                     default: no DOS alignment
#
#---------------------------------------------------
# Last changes:  see svn log
# Last changes:  31.3.2005 by Thomas Lange add sub mapdisk{}
# Last changes:  8.11.2004 by Thomas Lange add $devdisklist when calling sfdisk
# Last changes:   3.2.2004 by Thomas Lange typos
# Last changes: 14.07.2003 by Thomas Lange add xfs filesystem support
# Last changes: 23.01.2003 by Thomas Lange print info data to stdout
# Last changes: 03.12.2002 by Thomas Lange remove ida, cciss stuff. Just match everything
# Last changes: 27.11.2002 by Thomas Lange allow more that 3 primary partitions
# Last changes: 14.05.2002 by Thomas Lange use strict
# Last changes: 04.05.2002 by Thomas Lange use strict
# Last changes: 29.04.2002 by Thomas Lange add swaplist
# Last changes: 12.01.2002 by Thomas Lange
# /dev/ida/ patch 12.01.2002 by Marc Martinez <lastxit+fai@technogeeks.org>
# Last changes: 9.11.2001 by Thomas Lange
# reiserfs patch 8.11.2001 by Diane Trout <diane@caltech.edu>
# Last changes: 25.10.2001 by Thomas Lange
# Last changes: 09.07.2001 by Thomas Lange
# Last changes: 04.07.2001 by Thomas Lange
# Last changes: 06.05.2001 by Thomas Lange
# Last changes: 09.03.2001 by Thomas Lange
# Last changes: 05.12.2000 by Thomas Lange
# Last changes: 03.05.2000 by Thomas Lange
# Last changes: 03.04.2000 by Mattias Gaertner
#---------------------------------------------------
#
# config-file format:
#   lines beginning with # are comments
#
# "disk_config <device>|<diskN>|end"
#   The disk_config command starts the parsing.
#   It has to be the first command.
#    <device> is the harddisk to format in short form like "hda" or "sdc".
#    <diskN>  if first is used, the N-th disk of $disklist is used
#    "end"    = end parsing here
#   Example: "disk_config hdb"
#   Example: "disk_config disk3"
#
# Defining one partition:
# "primary|logical mountpoint|swap|- <size in mb>|preserve<No> [fstab-options][;extraordinary options]"
#    "primary|logical":
#      "primary": this are the bootable partitions like the
#         root directory "/" or the DOS "C:" disk.
#      "logical": this are all other partitions like a linux
#         "/var" or a swap partition or a DOS disk.
#
#    "mountpoint|swap|-":
#      "mountpoint": 
#         This is the mount-point for fstab.
#         For example "/","/var","/usr". There must not
#         be a space in the mountpoint.
#      "swap":
#         swap-partitions
#      "-":
#         do not mount this partition.
#
#    "<size in mb>|preserve<No>":
#      "<size in mb>":
#        The size of the partition in megabyte
#         Examples:
#          "30"     = 30 mb
#          "10-100" = 10 to 100 mb
#          "20-"    = minimum of 20 mb
#          "-500"   = 1 to 500 mb
#          The megabytes will be rounded up to cylinders.
#      "preserve<No>":
#         This is the alternative for the size attribute.
#         <No> is the partition number. For example
#         preserve3 for the third partition. If the
#         <device> was hda then this results in hda3.
#         The partition will be left unchanged. This
#         is useful if you have partitions that do not
#         need re-installation or if you want to have
#         other operation systems on the device together
#         with Linux. Extended Partitions can not be preserved.
#         The bootable flag will not be preserved.
#         Preserved partitions are mounted readonly during
#         installation.
#
#    "fstab-options":
#         These options are copied to the fstab-file. The
#         default is "default"
#
#   After the semicolon there could be extra options like:
#     -i <bytes>   : Bytes per inodes
#                    (only ext2/3 filesystem)
#     -m <blocks>% : reserved blocks percentage for superuser
#                    (only ext2/3 filesystem)
#     -j	   : format in ext3
#     -c           : check for bad blocks
#     format       : Always format this partition even if preserve
#     lazyformat   : Do not format if partition has not moved
#                    (useful for testing the installation)
#     boot         : make this partition the boot-partition (the
#                    linux root filesystem is the default)
#     ext2         : Extended 2 filesystem (this is the default)
#     swap         : swap partition
#     dosfat16     : DOS 16bit FAT file system
#     winfat32     : Win95 FAT32 file system
#     writable     : mounts a preserved partition writable
#     xfs          : xfs
#     reiser       : reiserfs
#       -h <hash>  : set reiserfs hash
#       -v <ver>   : set reiserfs version
#
use strict;
# getopts variables:
our ($opt_X, $opt_f, $opt_c, $opt_d);
my $test;

$| = 1;                     # flush always

#****************************************************
# Variables
#****************************************************

my $Version = "version 0.43fai";

my $megabyte = 1024 * 1024;    # guess
# $gigabyte = 1024 * $megabyte;
my $sectorsize = 512;

# used programs
my $sfdisk_options = "-q $ENV{sfdisk}";     # be quiet
my $mke2fs_options = "-q";     # be quiet
my $mkreiserfs_options = "";
my $mkxfs_options = "-f";
my $mkswap_options = "";

# FAI input variables
my $ClassPath = "$ENV{FAI}/disk_config";# this directory contains the classes
my $ConfigFileName = "";   # alternative classfile, only for tests
my $DOS_Alignment = "";    # align partitions for tracks

# FAI output variables
my $BootPartition = "";    # the boot partition like "hda1"
my $BOOT_DEVICE = "";      # the root device like "hda" or "sdb"
my $FAIOutputFile = "$ENV{LOGDIR}/disk_var.sh"; # write output variables to this file

# old partition tables
my %DiskUnits = ();        # unit size of each disk in sectors
my %DiskSize = ();         # size of every disk in units
my %SectorsAlignment = ();  # tracksize in sectors
my %PartOldBoot = ();      # partition was bootable. "yes"=yes
my %PartOldStart = ();     # old startunit of partition
my %PartOldEnd = ();       # old endunit of partition
my %PartOldStartSec = ();  # old startsector of partition
my %PartOldEndSec = ();    # old endsector of partition
my %PartOldID = ();        # old ID of partition
my %OldNotAligned = (); # "yes" if old partition boundaries are not DOS aligned

# mountpoints  ("/<path>" or "swap<No>" or "no<No>" or "extended<disk>")
my $NofSwapPart = 0;       # number of swap partitions
my $NofNotMoPart = 0;      # number of not mountet partitions
my %DiskMountpoints = ();  # mountpoints of every disk. separated by spaces
my %MountpointPart = ();   # partition of every mountpoint. e.g. "hda2"
my %PartMountpoint = ();   # mountpoint of every partition.
my @swaplist;              # list of all swpa devices

# size of partition/mountpoint
my %MPMinSize = ();        # minimum size of mountpoint in units
my %MPMaxSize = ();        # maximum size of mountpoint in units
my %MPPreserve = ();       # preserve partition: "yes"=yes
my %MPPrimary = ();        # primary partition: "yes"=yes
my %MPStart = ();          # start of partition in units
my %MPSize = ();           # size of partition in units
my %MPID = ();             # id of partition

# options
my %MPfstaboptions = ();   # fstab options for every mountpoint
my %MPOptions = ();        # extra options for every mountpoint

# sfdisk partition tables
my %sfdiskTables = ();     # partition tables for sfdisk

my $verbose = 0;
$verbose = $ENV{verbose} if $ENV{verbose};

# Parse command line

use Getopt::Std;
&getopts('Xf:c:d') || die "
USAGE: [-X]                     no test, your harddisks will be formated
                                default: only test, no real formating
       [-f<config-filename>]    default: parse classes
       [-c<class-path>]         default: \$FAI/disk_config/
       [-d]                     default: no DOS alignment
";

print "setup_harddisks $Version\n";
print "WARNING: setup_harddisks is deprecated and will be removed in the future.\n";
print "WARNING: Please use setup-storage(8) instead.\n";
if (defined $opt_X){
    $test = 2;
} else {
    print "TEST ONLY - no real formating\n\n";
    $test = 1;
}
$ConfigFileName = $opt_f if $opt_f;# alternative config file
$ClassPath      = $opt_c if $opt_c;# search classes here
$DOS_Alignment  = "yes" if $opt_d; # track alignment

# main part
&GetAllDisks;
&ParseAllConfigFiles;
&BuildNewPartTables;
&PartitionPersfdisk;
&FormatDisks;
&WriteFSTab;
&WriteFAIVariables;
exit 0;
#****************************************************

#****************************************************
# get a partition pathname
#****************************************************
sub PartName {
    my ($disk, $partno) = @_;
    my $ppath;
    for ($disk) {
      /^(i2o\/)?[a-z]+$/ and $ppath = "${disk}${partno}";
      /\d$/ and $ppath = "${disk}p${partno}";
    }
    return $ppath;
}

#****************************************************
# Read all partition tables of this machine
#****************************************************
sub GetAllDisks{
    my $line=""; my $disk=""; my $device=""; my $rest; my $result; my $divi;
    my $devdisklist="";

    foreach my $device(split(/\s/,$ENV{disklist})){
      $devdisklist = "$devdisklist /dev/$device";
    }
    print "Probing disks: $devdisklist\n";
    print "Disks found:";
    $result = `sh -c "LC_ALL=C sfdisk -g -q $devdisklist"`;
    foreach my $line(split(/\n/,$result)){
	if($line =~ m'^/dev/(.+?):\s+(\d+)\s+cylinders,\s+(\d+)\s+heads,\s+(\d+)\s+sectors'i){
	    $disk = $1;
	    $DiskUnits{$disk} = $3 * $4;# heads * sectors = cylinder size in sectors
	    $DiskSize{$disk} = $2;      # cylinders
	    ($DOS_Alignment eq "yes") ? ($SectorsAlignment{$disk} = $4) : ($SectorsAlignment{$disk} = 1);
	    print " $disk";
	}
    }
    $result = `sh -c "LC_ALL=C sfdisk -d -q $devdisklist"`;
    foreach my $line(split(/\n/,$result)){
#	if($line =~ m'# partition table of /dev/(cciss/c\dd\d|ida/c\dd\d|rd/c\dd\d|[a-z]+)'i){
# now just match all devices
	if($line =~ m'# partition table of /dev/(\S+)$'i){
	   $disk = $1;
        }
	if($line =~ m#^/dev/(.+?)\s*:\s+start=\s*(\d+),\s+size=\s*(\d+),\s+Id=\s*([a-z0-9]+)\b(.*)$#i){
	    $device = $1;
            # Sectors
            $PartOldStartSec{$device} = $2;
            $PartOldEndSec{$device} = $2 + $3 - 1;
            # DiskUnits
	    $PartOldStart{$device} = int ($2 / $DiskUnits{$disk});
	    $PartOldEnd{$device} = int (($2 + $3 - 1) / $DiskUnits{$disk});
	    $divi = $2 / $SectorsAlignment{$disk};
	    ($divi != int ($divi)) && ($OldNotAligned{$device} = "yes");
	    $divi = $3 / $SectorsAlignment{$disk};
	    ($divi != int ($divi)) && ($OldNotAligned{$device} = "yes");
	    $PartOldID{$device} = $4;
	    $rest = $5;
	    $PartOldBoot{$device} = ($rest =~ /bootable/) ? "yes" : "";
	}
    }
    print "\n\n";
}

#****************************************************
# parse config file or all class files
#****************************************************
sub ParseAllConfigFiles{
    my $ConfigFileExists = 0;  # no config file parsed yet
    if ($ConfigFileName){
	# Read config filename
	&ParseConfigFile($ConfigFileName);
	$ConfigFileExists = 1;
    } else {
	# Read classes
	foreach my $classfile (reverse split(/\s+/,$ENV{"classes"})){
	    my $filename = "$ClassPath/$classfile";
	    if (($classfile) && (-r $filename)) {
               &ParseConfigFile($filename);
               $ConfigFileExists = 1;
            }
	    ($ConfigFileExists) && last;
	}
    }
    ($ConfigFileExists == 0) && die "ERROR: no config file for setup_harddisk found. Please check you classes and files in disk_config.\n";
}

#****************************************************
# map "disk_config first" to real disk device
#****************************************************
sub mapdisk {

  my ($disk) = @_;
  my @dlist = split /\s+/,$ENV{disklist};
  unshift @dlist, "nodisk"; # add dummy element, so disk1 will be mapped to dlist[1]

  if ($disk =~ /disk(\d+)/) {
    my $n = $1;
    print "Mapping disk name disk$n to $dlist[$n]\n";
    $disk = $dlist[$n];
  }
  return $disk;
}

#****************************************************
# parse config-file
#****************************************************
sub ParseConfigFile{
    my $size=""; my $mountpoint=""; my $device ="";
    my $fstaboptions=""; my $options=""; my $disk=""; my $command = "";
    my $LogPartNo; my $PrimPartNo; my $NoMoreLogicals;
    my $LastPresPart; my $extmp; my $Min; my $Max;
    my $filename = shift;
    open (FILE,"$filename")
      || die "config file not found: $filename\n";
    (print "Using config file: $filename\n");
    $disk = "";
    my $a = 1, my $paras ="", my $number=0;
    while (my $line = <FILE>){
	chomp($line);
	$a++;
	next if( $line =~ /^#|^\s*$/ );

	# disk_config - command
	if ($line =~ /^disk_config(.*)/i){
	    $paras = $1;
	    if ($paras =~ / end/i){
		$disk = "";
	    } else {
#		if($paras =~ m# (/dev/)?(cciss/c\dd\d|ida/c\dd\d|rd/c\dd\d|[a-z]+)#i){
# now match all devives
		if($paras =~ m# (/dev/)?(\S+)#i){
		    $disk = mapdisk($2);
		    ($DiskMountpoints{$disk})
		      && die "ERROR: there are more than one configuration of disk $disk.\n";
		    ($DiskSize{$disk}) || die "ERROR: could not read device /dev/$disk\n";
		    ($test != 1) || (print "config: $disk\n");
		    $DiskMountpoints{$disk} = "";
		    $MPPrimary{"extended$disk"} = "";
		    $LogPartNo = 4;
		    $PrimPartNo = 0;
		    $NoMoreLogicals = 0;
		    $LastPresPart = "";
		    $extmp = "extended$disk";
		} else {
		    die "SYNTAX ERROR: in config file line $a, unknown disk_config parameter $paras\n$line\n";
		}
	    }
	}

	if ($disk){
	    # primary|partition - command
	    if($line =~ /^\s*(primary|logical)\s+(.*)$/i){
		$command = $1;
		# split variables
		$paras = $2;
		$options = "";
		if($paras =~ /(.*?)\s*;\s*(.*)$/){
		    $paras = $1;
		    $options = $2;
		}
		$size="";
		$mountpoint ="";
		$fstaboptions = "";
		($mountpoint,$size,$fstaboptions)=split(/\s+/,$paras);
		# mountpoint
		($mountpoint =~ m#^/.*|^swap$|^-$#i)
		  || die "SYNTAX ERROR in config file line $a, mountpoint: $mountpoint\n$line\n";
		($MountpointPart{$mountpoint})
		  && die "SYNTAX ERROR in config file line $a. Mountpoint $mountpoint redefined.\n$line\n";
		if($mountpoint eq "/"){
		    ($BootPartition) || ($BOOT_DEVICE = $disk);
		}
		if($mountpoint eq "-"){
		    $NofNotMoPart++;
		    $mountpoint = "no$NofNotMoPart";
		}
		if($mountpoint eq "swap"){
		    $NofSwapPart++;
		    $mountpoint = "swap$NofSwapPart";
		    ($options !~ /\bswap\b/i) && ($options .= " swap");
		    ($fstaboptions) || ($fstaboptions = "sw");
		}
		if($mountpoint =~ m#^/#){
		    ($fstaboptions) || ($fstaboptions = "defaults");
		}
		if ($command eq "primary") {
		    ($MPPrimary{$extmp} eq "yes") && ($NoMoreLogicals = 1);
		    $MPPrimary{$mountpoint} = "yes";
		    $PrimPartNo++;
#		    ($PrimPartNo == 3) && ($disk =~ /^sd/) && ($PrimPartNo++);
                    ($PrimPartNo >4 ) && die "ERROR: Too much primary partitions (max 4).".
                                " All logicals together need one primary too.\n";
		    $MountpointPart{$mountpoint} = PartName($disk,$PrimPartNo);
		    if($options =~ /\bboot\b/i){
		        ($BootPartition) && die "ERROR: only one partition can be bootable at a time.";
			$BootPartition = $MountpointPart{$mountpoint};
			$BOOT_DEVICE = $disk;
		    }
		} else {
		    ($NoMoreLogicals != 0) && die "ERROR: the logical partitions must be together.\n";
		    $MPPrimary{$mountpoint} = "";
		    $LogPartNo++;
		    $MountpointPart{$mountpoint} = PartName($disk,$LogPartNo);
		    if (!$MPPrimary{$extmp}){
		        $MPPreserve{$extmp} = "";
		        $MPPrimary{$extmp} = "yes";
			$MPMinSize{$extmp} = 0;
			$MPMaxSize{$extmp} = 0;
			$MPID{$extmp} = 5;
			$PrimPartNo++;
			($PrimPartNo == 3) && ($disk =~ /^sd/) && ($PrimPartNo++);
                        ($PrimPartNo >4 ) 
			  && die "ERROR: too much primary partitions (max 4).".
                               " All logicals together need one primary too.\n";
			$MountpointPart{$extmp} = PartName($disk,$PrimPartNo);
			$DiskMountpoints{$disk} .= " $extmp";
		    }
#		    ($options =~ /\bboot\b/i) && die "ERROR: line $a, only primary partitions can be bootable.\n";
		}
		$DiskMountpoints{$disk} .= " $mountpoint";
		# size
		($size =~ /^preserve\d+$|^\d+\-?\d*$|^-\d+$/i)
		    || die "SYNTAX ERROR in config file line $a, size: $size\n$line\n";
		if($size =~ /^preserve(\d+)$/i){
		    my $number = $1;
		    $device = PartName($disk,$number);
		    ($OldNotAligned{$device} eq "yes")
		      && die "ERROR: unable to preserve partition /dev/$device. Partition is not DOS aligned.";
		    ($command eq "primary") && ($number != $PrimPartNo)
                       && die "NUMERATION ERROR in line $a, the number of the partition can not be preserved:\n$line\n";
		    ($command eq "logical") && ($number != $LogPartNo)
                       && die "NUMERATION ERROR in line $a, the number of the partition can not be preserved:\n$line\n";
		    if ($PartOldEnd{$device}){
		        (($PartOldID{$device} == 5) || ($PartOldID{$device} == 85)) &&
			  die "ERROR in config file line $a.".
                              " Extended partitions can not be preserved. /dev/$device\n$line\n";
			$MPPreserve{$mountpoint}="yes";
			$MPMinSize{$mountpoint} = $PartOldEnd{$device}-$PartOldStart{$device}+1;
			$MPMaxSize{$mountpoint} = $MPMinSize{$mountpoint}; # Max=Min
			$MPStart{$mountpoint} = $PartOldStart{$device};
			$MPSize{$mountpoint} = $MPMinSize{$mountpoint};
			$MPID{$mountpoint} = $PartOldID{$device};
		    } else {
			die "ERROR: cannot preserve partition $device. partition not found.$PartOldEnd{$device}\n";
		    }
		    if ($LastPresPart) {
		        ($PartOldStart{$device} < $PartOldStart{$LastPresPart}) &&
			  die "ERROR: misordered partitions: cannot preserve partitions $LastPresPart and $device\n".
                              "       in this order because of their positions on disk.";
		    }
		    $LastPresPart = $device;
		    ($MPMinSize{$mountpoint} < 1)
		      && die "ERROR: unable to preserve partitions of size 0.\n$line\n ";
		  } else {
		    # If not preserve we must know the filesystemtype
	            ($options !~ /\b(ext2|ext3|auto|swap|dosfat16|winfat32|reiser|xfs)\b/i ) && ($options .= " auto");
		  }
		if($size =~ /^(\d*)(\-?)(\d*)$/){
		    $Min = $1;
		    $Min||= 1;
		    $Max = $3;
		    $MPMinSize{$mountpoint} = int (($Min * $megabyte - 1) / ($DiskUnits{$disk} * $sectorsize)) + 1;
		    if ($2 eq "-"){
			if($Max =~ /\d+/){
			    $MPMaxSize{$mountpoint} = int (($Max * $megabyte - 1) / ($DiskUnits{$disk} * $sectorsize)) + 1;
			} else {
			    $MPMaxSize{$mountpoint} = $DiskSize{$disk};
			}
		    } else {
			$MPMaxSize{$mountpoint} = $MPMinSize{$mountpoint}; # Max=Min
		    }
		    ($MPMinSize{$mountpoint} > $DiskSize{$disk})
		      && die "ERROR in config file line $a: Minsize larger than disk.\n$line\n";
		    ($MPMinSize{$mountpoint} > $MPMaxSize{$mountpoint}) 
                       && die "SYNTAX ERROR in config file line $a, MIN-MAX-size: $MPMinSize{$mountpoint}-$MPMaxSize{$mountpoint}\n$line\n";
		    ($MPMinSize{$mountpoint} < 1)
		      && die "SYNTAX ERROR in config file line $a. Minsize must be greater than 1.\n$line\n";
		    $MPPreserve{$mountpoint} = "";
		}
		# fstaboptions
		$MPfstaboptions{$mountpoint} = $fstaboptions;
		# extra options
		($options =~ /\b(ext[23]|auto)\b/i) && ($MPID{$mountpoint} = 83); # Linux native
		($options =~ /\bswap\b/i) && ($MPID{$mountpoint} = 82); # Linux swap
		($options =~ /\bdosfat16\b/i) && ($MPID{$mountpoint} = 6); # DOS FAT 16bit (>=32MB, will be changed later)
		($options =~ /\bwinfat32\b/i) && ($MPID{$mountpoint} = "b"); # Win 95 FAT 32
		$MPOptions{$mountpoint} = $options;
		if($test == 1){
		    print "$mountpoint,$MPMinSize{$mountpoint}-$MPMaxSize{$mountpoint},";
		    print "$fstaboptions,$options";
		    ($MPPreserve{$mountpoint} eq "yes") && (print " Preserve: $MountpointPart{$mountpoint}");
		    print "\n";
		}
	    }
	}
    }
    close(FILE);
}

#****************************************************
# Build all partition tables
#****************************************************
sub BuildNewPartTables{
    my ($disk, $mountpoint, $part, $PrimaryMP, $LogicalMP);
    ($test != 1) || (print "\nBuilding partition tables:\n");
    # Build PartMountpoint array
    foreach $disk(keys %DiskMountpoints) {
	$DiskMountpoints{$disk} =~ s/\s(\s)/$1/g;
	$DiskMountpoints{$disk} =~ s/^\s//;
	$DiskMountpoints{$disk} =~ s/\s$//;
	foreach $mountpoint(split(/\s/,$DiskMountpoints{$disk})) {
	    $PartMountpoint{$MountpointPart{$mountpoint}} = $mountpoint;
	}
    }
    foreach $disk(keys %DiskMountpoints) {
	&SetPartitionPositions($disk);
        # change units to sectors
        foreach $mountpoint(split(/\s/,$DiskMountpoints{$disk})) {
            if($MPPreserve{$mountpoint} eq "yes"){
	        $MPStart{$mountpoint} = $PartOldStartSec{$MountpointPart{$mountpoint}};
	        $MPSize{$mountpoint} = $PartOldEndSec{$MountpointPart{$mountpoint}} - $MPStart{$mountpoint} + 1;
	    } else {
	        $MPStart{$mountpoint} *= $DiskUnits{$disk};
	        $MPSize{$mountpoint} *= $DiskUnits{$disk};
	        # align first partition for mbr
	        if($MPStart{$mountpoint} == 0){
	            $MPStart{$mountpoint} += $SectorsAlignment{$disk};
		    $MPSize{$mountpoint} -= $SectorsAlignment{$disk};
	        }
	    }
	}
	# align all logical partitions
        foreach $mountpoint(split(/\s/,$DiskMountpoints{$disk})) {
            next if ($MPPrimary{$mountpoint} eq "yes");
	    if ($MountpointPart{$mountpoint} eq "${disk}5") {
	        # partition with number 5 is first logical partition and start of extended partition
  	        $MPStart{"extended$disk"} = $MPStart{$mountpoint};
                ($MPPreserve{$mountpoint} eq "yes") && ($MPStart{"extended$disk"} -= $SectorsAlignment{$disk});
	    }
            if ($MPPreserve{$mountpoint} ne "yes") {
  	        $MPStart{$mountpoint} += $SectorsAlignment{$disk};
	        $MPSize{$mountpoint} -= $SectorsAlignment{$disk};
	    }
	}
        &CalculateExtPartSize($disk);
        # sort mountpoints of partition number
        $PrimaryMP = "";
        $LogicalMP = "";
        foreach $mountpoint(split(/\s/,$DiskMountpoints{$disk})) {
	  ($MPPrimary{$mountpoint} eq "yes") ? ($PrimaryMP .= " $mountpoint") : ($LogicalMP .= " $mountpoint");
	}
	$DiskMountpoints{$disk} = "$PrimaryMP$LogicalMP";
	$DiskMountpoints{$disk} =~ s/^\s//;
	# print partition table
        ($test != 1) || (PrintPartitionTable($disk));
    }
    if (!$BootPartition){
        $BootPartition = $MountpointPart{"/"};
    }
}

#****************************************************
# set position for every partition
#****************************************************
sub SetPartitionPositions{
    my $disk = shift;
    my $mountpoint; my $DynGroup =""; my $StartPos; my $EndPos;
    # Build groups of unpreserved partitions between
    # preserved partitions
    $StartPos = 0;
    foreach $mountpoint(split(/\s/,$DiskMountpoints{$disk})) {
        if ($MPPreserve{$mountpoint} eq "yes") {
	    $EndPos = $PartOldStart{$MountpointPart{$mountpoint}} - 1;
            &SetGroupPos($DynGroup,$StartPos,$EndPos);
	    $DynGroup = "";
	    $StartPos = $PartOldEnd{$MountpointPart{$mountpoint}} + 1;
        } else {
	    $DynGroup .= " $mountpoint";
	}
    }
    $EndPos = $DiskSize{$disk} - 1;
    &SetGroupPos($DynGroup,$StartPos,$EndPos);
    foreach $mountpoint(split(/\s/,$DiskMountpoints{$disk})) {
	($MPOptions{$mountpoint} =~ /\bdosfat16\b/i)
	    && (($MPSize{$mountpoint} * $DiskUnits{$disk} * $sectorsize) < 32 * $megabyte)
		&& ($MPID{$mountpoint} = 4); # DOS 16-bit FAT <32MB
    }
}

#****************************************************
# set position for a group of unpreserved partitions
# between start and end
#****************************************************
sub SetGroupPos{
    my ($PartGroup,$Start,$End) = @_;
    $PartGroup =~ s/^ //;
    ($PartGroup) || return;
    my $totalsize = $End - $Start + 1;
    ($totalsize <= 0) && return;
    my $mountpoint; my $mintotal = 0; my $maxmintotal = 0; my $rest = 0; my $EndUnit = 0;
    # compute total of MinSizes and difference to MaxSizes
    foreach $mountpoint (split(/\s/,$PartGroup)) {
        $mintotal += $MPMinSize{$mountpoint};
        $maxmintotal += ($MPMaxSize{$mountpoint} - $MPMinSize{$mountpoint});
        $MPSize{$mountpoint} = $MPMinSize{$mountpoint};
    }
    # Test if partitions fit
    ($mintotal > $totalsize)
      && die "ERROR: Mountpoints $PartGroup do not fit.\n";
    # Maximize partitions
    $rest = $totalsize - $mintotal;
    ($rest > $maxmintotal) && ($rest = $maxmintotal);
    if ($rest > 0) {
        foreach $mountpoint (split(/\s/,$PartGroup)) {
            $MPSize{$mountpoint} += int ((($MPMaxSize{$mountpoint} - $MPMinSize{$mountpoint}) * $rest) / $maxmintotal);
        }
    }
    # compute rest
    $rest = $totalsize;
    foreach $mountpoint (split(/\s/,$PartGroup)) {
        $rest -= $MPSize{$mountpoint};
    }
    # Minimize rest
    foreach $mountpoint (split(/\s/,$PartGroup)) {
        if (($rest >0) && ($MPSize{$mountpoint} < $MPMaxSize{$mountpoint})){
            $MPSize{$mountpoint}++;
	    $rest--;
	}
    }
    # Set start for every partition
    foreach $mountpoint (split(/\s/,$PartGroup)) {
        $MPStart{$mountpoint} = $Start;
	$Start += $MPSize{$mountpoint};
	$EndUnit = $MPStart{$mountpoint} + $MPSize{$mountpoint} - 1;
    }
}

#****************************************************
# calculate extended partition size
#****************************************************
sub CalculateExtPartSize{
    my ($disk) = @_;
    my $extmp = "extended$disk";
    my $mountpoint; my $ExtEnd; my $NewEnd;
    ($MPPrimary{$extmp}) || return;
    $ExtEnd = $MPStart{$extmp};
    foreach $mountpoint(split(/\s/,$DiskMountpoints{$disk})) {
        next if ($MPPrimary{$mountpoint} eq "yes");
	$NewEnd = $MPStart{$mountpoint} + $MPSize{$mountpoint} - 1;
	($NewEnd > $ExtEnd) && ($ExtEnd = $NewEnd);
    }
    $MPSize{$extmp} = ($ExtEnd - $MPStart{$extmp} + 1);
}

#****************************************************
# Print partition "number - mountpoint" table
#****************************************************
sub PrintPartitionTable{
    my ($disk) = @_;
    my $part; my $mountpoint; my $mountpointname; my $end;
    foreach $part (sort %MountpointPart) {
        next if($part !~ /^$disk/);
	$mountpoint = $PartMountpoint{$part};
        if ($mountpoint =~ /^no(.*)/){
            $mountpointname = "no mountpoint ($1)";
	} else {
	    $mountpointname = $mountpoint;
	}
	$end = $MPStart{$mountpoint} + $MPSize{$mountpoint} - 1;
	print <<"EOM";
/dev/$part $mountpointname start=$MPStart{$mountpoint} size=$MPSize{$mountpoint} end=$end id=$MPID{$mountpoint}
EOM
      }
}

#****************************************************
# build all partition tables for sfdisk
#****************************************************
sub PartitionPersfdisk{
    my ($disk, $mountpoint, $line, $part, $PrimaryNo);
    my ($command, $result, $filename, $number);
    print "Creating partition table: ";
    foreach $disk(keys %DiskMountpoints) {
        $sfdiskTables{$disk} = "# partition table of device: /dev/$disk\nunit: sectors\n\n";
	$PrimaryNo = 1;
        foreach $mountpoint(split(/\s/,$DiskMountpoints{$disk})) {
	    $part = $MountpointPart{$mountpoint};
	    $part =~ /(\d+)$/;
	    ($1 < 5) && ($PrimaryNo++);
	    if ( ($1 == 5) && ($PrimaryNo < 5) ){
	        for my $number($PrimaryNo..4) {
		    $sfdiskTables{$disk} .= BuildsfdiskDumpLine(PartName($disk,$number),0,0,0)."\n";
	        }
	    }
	    $line = BuildsfdiskDumpLine($MountpointPart{$mountpoint},$MPStart{$mountpoint},$MPSize{$mountpoint},$MPID{$mountpoint});
            ($part eq $BootPartition) && ($line .= ", bootable");
            $sfdiskTables{$disk} .= "$line\n";
	}
#	print $sfdiskTables{$disk};
	$filename = "$ENV{LOGDIR}/partition." . (($disk=~ m#/#) ? join('_', split('/', $disk)) : $disk);
	if(($test != 1) && ($filename)){
	    open(FILE, ">$filename") || die "unable to write temporary file $filename\n";
	    print FILE $sfdiskTables{$disk};
	    close(FILE);
        }
	$command = "LC_ALL=C sfdisk $sfdisk_options /dev/$disk < $filename";
	if($test != 1){
            print "$command\n";
	    $result = `sh -c "$command"`;
	    (($? >> 8) == 0) || (die "\nSFDISK ERROR:\n $result\n");
	    -f "/etc/init.d/udev" && sleep 10; # when using udev, it takes some time until the device entries for each partition are available
	}
    }
}

#****************************************************
# build a sfdisk dump line
#****************************************************
sub BuildsfdiskDumpLine{

  sprintf "/dev/%-5s: start=%10s, size=%10s, Id=%3s",@_;
}

#****************************************************
# Format all disks
#****************************************************
sub FormatDisks{
    my ($disk, $device, $mountpoint, $mountpointname, $command, $result);
    print "Creating file systems:\n";
    foreach $disk(keys %DiskMountpoints) {
        foreach $mountpoint (split(/\s/,$DiskMountpoints{$disk})) {
	    $device = $MountpointPart{$mountpoint};
            if ($mountpoint =~ /^no/){
                $mountpointname = "no mountpoint";
            } else {
	        $mountpointname = $mountpoint;
	    }
	    # preserved partition
	    if ( ($MPPreserve{$mountpoint} eq "yes") && ($MPOptions{$mountpoint} !~ /\bformat\b/i)){
  	        print "Preserve partition $device";
                if ($mountpoint =~ /^no$1/){
                    print " with no mountpoint\n";
                } else {
	            print " with mountpoint $mountpoint\n";
	        }
		next;
	    }
	    # lazy format
	    if ( ( $MPOptions{$mountpoint} =~ /\blazyformat\b/i )
              && ($MPStart{$mountpoint} == $PartOldStartSec{$device})
              && (($MPStart{$mountpoint} + $MPSize{$mountpoint} - 1) == $PartOldEndSec{$device}) ){
	        print "Lazy format: $device";
                if ($mountpoint =~ /^no$1/){
                    print " with no mountpoint";
                } else {
	            print " with mountpoint $mountpoint";
	        }
                print " was neither moved nor formated.\n";
		next;
	    }
	    # swap
	    if ($mountpoint =~ /^swap/i) {
#	        print "Make swap partition:\n";
	        $command = "mkswap $mkswap_options";
		($MPOptions{$mountpoint} =~ /(\-c)\b/i) && ($command .= " $1");
		push @swaplist, "/dev/$device";
		$command .= " /dev/$device";
	        print "  $command\n";
	        if($test != 1){
	            $result = `$command`;
		    (($? >> 8) == 0) || (die "\nMKSWAP ERROR:\n $result\n");
		}
		next;
	    }
	    # Linux Reiser file system
	    if ($MPOptions{$mountpoint} =~ /\breiser\b/i) {
#	        print "Make Reiser Filesystem:\n";
	        $command = "echo y | LC_ALL=C mkreiserfs $mkreiserfs_options";
		($MPOptions{$mountpoint} =~ /(\-h\s*\w+)\b/) && ($command .= " $1");
		($MPOptions{$mountpoint} =~ /(\-v\s*\d+)\b/) && ($command .= " $1");
		$command .= " /dev/$device";
		print "  $command\n";
		if ($test != 1){
		    $result = `$command`;
		    (($? >> 8) == 0) || die "\nMKREISERFS ERROR:\n $result\n";
		}
		next;
	    }
	    # Linux XFS file system
	    if ($MPOptions{$mountpoint} =~ /\bxfs\b/i) {
#	        print "Make XFS Filesystem:\n";
	        $command = "mkfs.xfs $mkxfs_options";
		$command .= " /dev/$device";
		print "  $command\n";
		if ($test != 1){
		    $result = `$command`;
		    (($? >> 8) == 0) || die "\nMKFS.XFS ERROR:\n $result\n";
		}
		next;
	    }
	    # Linux Extended 2 file system
	    if ($MPOptions{$mountpoint} =~ /\b(ext[23]|auto)\b/i) {
#	        print "Make Extended 2/3 Filesystem:\n";
	        $command = "mke2fs $mke2fs_options";
		($MPOptions{$mountpoint} =~ /(\-c)\b/i) && ($command .= " $1");
		($MPOptions{$mountpoint} =~ /(\-i\s*\d+)\b/) && ($command .= " $1");
		($MPOptions{$mountpoint} =~ /(\-m\s*\d+)\b/) && ($command .= " $1");
		($MPOptions{$mountpoint} =~ /(\-j)\b/) && ($command .= " $1");
		$command .= " /dev/$device";
		print "  $command\n";
		if ($test != 1){
		    $result = `$command`;
		    (($? >> 8) == 0) || die "\nMKE2FS ERROR:\n $result\n";
		}
		next;
	    }
	    # DOS 16bit FAT / Win95 FAT 32
	    if ($MPOptions{$mountpoint} =~ /\b(dosfat16|winfat32)\b/i) {
	        print "Clear first sector for DOS/Windows\n";
	        $command = "dd if=/dev/zero of=/dev/$MountpointPart{$mountpoint} bs=512 count=1";
		print "  $command\n";
		if ($test != 1){
		    $result = `$command`;
		    (($? >> 8) == 0) || die "\nDD ERROR:\n $result\n";
		}
		next;
	    }
        }
    }
}

#****************************************************
# Build fstab and write it to <root>/etc/fstab
#****************************************************
sub WriteFSTab{
    my ($FileSystemTab, $device, $type, $filename);
    $FileSystemTab  = << "EOM";
# /etc/fstab: static file system information.
#
#<file sys>          <mount point>     <type>   <options>   <dump>   <pass>
EOM
    # 1. /
    $type = "ext2";
    ($MPOptions{'/'} =~ /\b(reiser)\b/i) && ($type = "reiserfs");
    ($MPOptions{'/'} =~ /\b(xfs)\b/i) && ($type = "xfs");
    ($MPOptions{'/'} =~ /\b(ext3)\b/i) && ($type = "ext3");
    ($MPOptions{'/'} =~ /\b(ext2)\b/i) && ($type = "ext2");
    $FileSystemTab .= BuildfstabLine("/dev/$MountpointPart{'/'}","/",$type,$MPfstaboptions{'/'},0,1);
    # 2. swap partitions
    foreach my $mountpoint (%PartMountpoint){
	next if( $mountpoint !~ /^swap/i);
	$FileSystemTab .= BuildfstabLine("/dev/$MountpointPart{$mountpoint}",
                           "none","swap",$MPfstaboptions{$mountpoint},0,0);
    }
    # 3. /proc
    $FileSystemTab .= BuildfstabLine("proc","/proc","proc","rw,nosuid,noexec",0,0);
    # 4. sorted others
    foreach my $mountpoint (sort %PartMountpoint){
	next if ( ($mountpoint !~ m#^/#) || ($mountpoint eq "/"));
	$device = $MountpointPart{$mountpoint};
	$type = "ext2";
	($MPOptions{$mountpoint} =~ /\b(dosfat16|winfat32)\b/i) && ($type = "vfat");
	($MPOptions{$mountpoint} =~ /\b(ntfs)\b/i) && ($type = "ntfs");
	($MPOptions{$mountpoint} =~ /\b(reiser)\b/i) && ($type = "reiserfs");
	($MPOptions{$mountpoint} =~ /\b(xfs)\b/i) && ($type = "xfs");
	($MPOptions{$mountpoint} =~ /\b(ext3)\b/i) && ($type = "ext3");
	($MPOptions{$mountpoint} =~ /\b(ext2)\b/i) && ($type = "ext2");
	$FileSystemTab .= BuildfstabLine("/dev/$device",$mountpoint,$type,$MPfstaboptions{$mountpoint},0,2);
    }
    # write it
    $filename = "$ENV{LOGDIR}/fstab";
#    print $FileSystemTab;
    print "Write fstab to $filename\n" if $verbose;
    if($test != 1){
	open(FILE, ">$filename") || die "unable to write fstab $filename\n";
	print FILE $FileSystemTab;
	close(FILE);
    }
}

#****************************************************
# Build fstab line
#****************************************************
sub BuildfstabLine{

    sprintf "%-10s   %-15s   %-6s  %-8s  %-4s %-4s\n",@_;
}

#****************************************************
# Write all FAI variables of this program to file
#****************************************************
sub WriteFAIVariables{

  my $swaps;

  print "Write FAI variables to file $FAIOutputFile\n" if $verbose;
    return if ($test == 1);
  $swaps = join ' ',@swaplist;
    open(FILE, ">$FAIOutputFile") || die "Unable to write file $FAIOutputFile\n";
    print FILE << "EOM";
BOOT_DEVICE=/dev/$BOOT_DEVICE
ROOT_PARTITION=/dev/$MountpointPart{'/'}
BOOT_PARTITION=/dev/$BootPartition
SWAPLIST="$swaps"
EOM
    close(FILE);
}
