[Fai-commit] r4293 - in people/michael/features/setup_harddisks_2: examples implementation

michael-guest at alioth.debian.org michael-guest at alioth.debian.org
Thu May 31 07:27:20 UTC 2007


Author: michael-guest
Date: 2007-05-31 07:27:20 +0000 (Thu, 31 May 2007)
New Revision: 4293

Added:
   people/michael/features/setup_harddisks_2/implementation/shdd2-volumes
Removed:
   people/michael/features/setup_harddisks_2/implementation/NOTES
Modified:
   people/michael/features/setup_harddisks_2/examples/model
   people/michael/features/setup_harddisks_2/examples/vil
   people/michael/features/setup_harddisks_2/implementation/Makefile
   people/michael/features/setup_harddisks_2/implementation/shdd2
   people/michael/features/setup_harddisks_2/implementation/shdd2-commands
   people/michael/features/setup_harddisks_2/implementation/shdd2-exec
   people/michael/features/setup_harddisks_2/implementation/shdd2-fstab
   people/michael/features/setup_harddisks_2/implementation/shdd2-init
   people/michael/features/setup_harddisks_2/implementation/shdd2-lib
   people/michael/features/setup_harddisks_2/implementation/shdd2-parser
   people/michael/features/setup_harddisks_2/implementation/shdd2-sizes
Log:
Here we go: setup_harddisks_2 seems to work; I'll send more information to
linux-fai-devel soon


Modified: people/michael/features/setup_harddisks_2/examples/model
===================================================================
--- people/michael/features/setup_harddisks_2/examples/model	2007-05-23 15:49:57 UTC (rev 4292)
+++ people/michael/features/setup_harddisks_2/examples/model	2007-05-31 07:27:20 UTC (rev 4293)
@@ -1,4 +1,4 @@
-disk_config hda   preserve:6,7   disklabel:msdos  bootable:3
+disk_config hda   preserve:6,7   disklabel:msdos  bootable:3 fstabkey:label
 primary /boot     20       ext3       rw 
 primary swap      1000     swap       sw 
 primary /         12000      ext3          rw   -b 2048

Modified: people/michael/features/setup_harddisks_2/examples/vil
===================================================================
--- people/michael/features/setup_harddisks_2/examples/vil	2007-05-23 15:49:57 UTC (rev 4292)
+++ people/michael/features/setup_harddisks_2/examples/vil	2007-05-31 07:27:20 UTC (rev 4293)
@@ -1,14 +1,8 @@
-# disk configuration for a 6 disk configuration
-
-# This is set up much like everything else; 
-
-# <type> <mountpoint> <size in mb> <mount options> <filesystem> [extra options]
-
 disk_config sda
 
-primary  /    256         - -  
-primary  swap 1024        -                   -
-primary  -		0-				 - -
+primary  -    256         -    -  
+primary  swap 1024        swap       sw
+primary  -		0-				 -    -
 
 disk_config sdb
 primary  -		0-				 - -

Modified: people/michael/features/setup_harddisks_2/implementation/Makefile
===================================================================
--- people/michael/features/setup_harddisks_2/implementation/Makefile	2007-05-23 15:49:57 UTC (rev 4292)
+++ people/michael/features/setup_harddisks_2/implementation/Makefile	2007-05-31 07:27:20 UTC (rev 4293)
@@ -7,7 +7,8 @@
 	shdd2-init \
 	shdd2-lib \
 	shdd2-parser \
-	shdd2-sizes
+	shdd2-sizes \
+	shdd2-volumes
 
 tidy:
 	perltidy -bl -i=2 $(FILES)

Deleted: people/michael/features/setup_harddisks_2/implementation/NOTES
===================================================================
--- people/michael/features/setup_harddisks_2/implementation/NOTES	2007-05-23 15:49:57 UTC (rev 4292)
+++ people/michael/features/setup_harddisks_2/implementation/NOTES	2007-05-31 07:27:20 UTC (rev 4293)
@@ -1,12 +0,0 @@
-libparse-recdescent-perl must get installed in nfsroot
-
-auto mode
-
-query commands
-
-allow for some arbitrary predefined list instead of $ENV{disklist}
-
-split into functions, size computation must be fixed
-
-getopt processing
-

Modified: people/michael/features/setup_harddisks_2/implementation/shdd2
===================================================================
--- people/michael/features/setup_harddisks_2/implementation/shdd2	2007-05-23 15:49:57 UTC (rev 4292)
+++ people/michael/features/setup_harddisks_2/implementation/shdd2	2007-05-31 07:27:20 UTC (rev 4293)
@@ -20,6 +20,9 @@
 
 use strict;
 
+# treat all warnings about uninitialised values as errors
+use warnings FATAL => qw(uninitialized);
+
 ################################################################################
 #
 # @file shdd2
@@ -37,43 +40,171 @@
 #
 ################################################################################
 
+################################################################################
+# TODO list
+# - parted, libparse-recdescent-perl must get installed in nfsroot (add Depends:
+#   to fai-nfsroot package)
+# 
+# - closes #380629, #330915, #277045, #356862
+# - no-bug: #364763
+#
+# - auto mode (something like auto:server, auto:desktop?)
+# - man page
+#
+# - resize should imply resizing the filesystem as well (unless this is done by
+#   parted already, needs to be checked)
+# - more error messages must be caught by shdd2-exec
+# - how to detect old-style config files? migration strategies?
+# - implement disklabels other than msdos and gpt
+################################################################################
+
+
 package FAI;
 
+# command line parameter handling
+use Getopt::Std;
+# the variables for getopt
+our ( $opt_X, $opt_f );
+# parse the command line
+&getopts('Xf:') || die "
+USAGE: [-X]                     no test, your harddisks will be formated
+                                default: only test, no real formating
+       [-f<config-filename>]    default: parse classes
+";
+
+# $disklist must be provided by the environment 
+defined( $ENV{disklist} ) or die "Environment variable disklist is not set";
+
+################################################################################
+#
+# @brief Really write any changes to disk
+#
+################################################################################
+$FAI::no_dry_run = 0;
+( $opt_X ) and $FAI::no_dry_run = 1;
+( $opt_X ) or warn "shdd2 is running in test-only mode!\n";
+
 # include all subparts
 require "shdd2-init";
 require "shdd2-lib";
+require "shdd2-volumes";
 require "shdd2-parser";
 require "shdd2-sizes";
 require "shdd2-commands";
 require "shdd2-fstab";
 require "shdd2-exec";
 
+# the config source file
+my $config_file = undef;
+# use the config file, if given
+if ( $opt_f )
+{
+  open($config_file, $opt_f) or die
+    "Failed to open config file $opt_f\n";
+}
+# see which class file to use
+else
+{
+  foreach my $classfile (reverse split(/\s+/,$ENV{"classes"}))
+  {
+    next unless ( -r "$ENV{'FAI'}/disk_config/$classfile" );
+    open($config_file, "$ENV{'FAI'}/disk_config/$classfile");
+    last;
+  }
+}
+
+# if we could not find any matching class file, bail out
+defined( $config_file ) or die "No matching disk_config found\n";
+
 # start the parsing - thereby $FAI::configs is filled
-&FAI::run_parser;
+&FAI::run_parser($config_file);
 
 # read the sizes and partition tables of all disks listed in $FAI::disks
 &FAI::get_current_disks;
 
-# compute the new partition sizes
-&FAI::compute_sizes;
+# see whether there are any existing LVMs
+# load the dm-mod module first, otherwise the LVM tools won't work
+`modprobe dm-mod`;
+&FAI::get_current_lvm;
 
-# TODO - debugging only: print the current contents of $FAI::configs
-&FAI::print_hash( \%FAI::configs );
+# see whether there are any existing RAID devices
+# load the md-mod module first, otherwise there is nothing that can be detected
+`modprobe md-mod`;
+&FAI::get_current_raid;
 
-# generate the command script
-&FAI::build_commands;
+# for debugging purposes to print the hash structures
+use Data::Dumper;
 
-# TODO - debugging only: print the command script
-foreach my $cmd (@FAI::commands)
+# debugging only: print the current contents of $FAI::current_config
+if ( $FAI::debug )
 {
-  print $cmd . "\n";
+  print "Current disk layout\n";
+  # make sure perl doesn't warn about it being used only once
+  our %current_config;
+  print Dumper \%current_config;
+  
+  print "Current LVM layout\n";
+  # make sure perl doesn't warn about it being used only once
+  our %current_lvm_config;
+  print Dumper \%current_lvm_config;
+ 
+  print "Current RAID layout\n";
+  # make sure perl doesn't warn about it being used only once
+  our %current_raid_config;
+  print Dumper \%current_raid_config;
+}
 
-  # &FAI::execute_command($cmd);
+# compute the new LVM and partition sizes; do the partition sizes first to have
+# them available for the the volume group size estimation
+&FAI::compute_partition_sizes;
+&FAI::compute_lv_sizes;
+
+# debugging only: print the current contents of $FAI::configs
+if ( $FAI::debug )
+{
+  print "Desired disk layout\n";
+  print Dumper \%FAI::configs;
 }
 
+# generate the command script
+&FAI::build_disk_commands;
+&FAI::build_raid_commands;
+&FAI::build_lvm_commands;
+
+# run all commands
+# debugging only: print the command script
+( $FAI::debug ) and print "$_\n" foreach ( @FAI::commands );
+# run the command (if $FAI::no_dry_run is set)
+&FAI::execute_command_std($_) foreach ( @FAI::commands );
+
 # generate the proposed fstab contents
 my @fstab = &FAI::generate_fstab( \%FAI::configs );
 
-# TODO - debugging only; print fstab
-printf "$_\n" foreach (@fstab);
+# debugging only; print fstab
+( $FAI::debug ) and print "$_\n" foreach (@fstab);
 
+# write the proposed contents of fstab to $LOGDIR/fstab, if $FAI::no_dry_run is set
+if ( $FAI::no_dry_run )
+{
+  # write fstab to $LOGDIR/fstab
+  open(FSTAB, ">$ENV{LOGDIR}/fstab") or die 
+    "Failed to open $ENV{LOGDIR}/fstab for writing\n";
+  print FSTAB "$_\n" foreach (@fstab);
+  close FSTAB;
+}
+
+# write variables to $LOGDIR/disk_var.sh
+# debugging
+( $FAI::debug ) and print "$_=$FAI::disk_var{$_}\n" 
+  foreach ( keys %FAI::disk_var );
+# do it, if $FAI::no_dry_run is set
+if ( $FAI::no_dry_run )
+{
+  open(DISK_VAR, ">$ENV{LOGDIR}/disk_var.sh") or die 
+    "Unable to write to file $ENV{LOGDIR}/disk_var.sh\n";
+  print DISK_VAR "$_=$FAI::disk_var{$_}\n" 
+    foreach ( keys %FAI::disk_var );
+  close DISK_VAR;
+}
+
+

Modified: people/michael/features/setup_harddisks_2/implementation/shdd2-commands
===================================================================
--- people/michael/features/setup_harddisks_2/implementation/shdd2-commands	2007-05-23 15:49:57 UTC (rev 4292)
+++ people/michael/features/setup_harddisks_2/implementation/shdd2-commands	2007-05-31 07:27:20 UTC (rev 4293)
@@ -37,61 +37,242 @@
 
 ################################################################################
 #
+# @brief Build the mkfs commands for the partition pointed to by $partition
+#
+# @param $device Device name of the target partition
+# @param $partition Reference to partition in the config hash
+#
+# The command is added @FAI::commands
+#
+################################################################################
+sub build_mkfs_commands
+{
+  my ( $device, $partition ) = @_;
+
+  defined( $partition->{"filesystem"} ) or die 
+    "INTERNAL ERROR: filesystem is undefined\n";
+
+  if ( $partition->{"filesystem"} eq "-" )
+  {
+    return;
+  }
+  elsif ( $partition->{"filesystem"} eq "swap" )
+  {
+    push @FAI::commands, "mkswap " . $partition->{"fs_options"} . " $device"; 
+  }
+  else
+  {
+    push @FAI::commands, "mkfs." . $partition->{"filesystem"} . " " .
+      $partition->{"fs_options"} . " $device";
+  }
+}
+
+################################################################################
+#
 # @brief Using the configurations from %FAI::configs, a list of commands is
-# build
+# built to create any RAID devices
 #
 # The list is @FAI::commands
 #
 ################################################################################
-sub build_commands
+sub build_raid_commands
 {
+  # TODO: do we need to stop anything before we continue? Do we need to issue
+  # mdadm --misc --zero-superblock /dev/hdx?
 
   # loop through all configs
   foreach my $config ( keys %FAI::configs )
   {
+    # no LVM here
+    next if ( $config =~ /^VG_(.+)$/ );
 
-    # TODO implement RAID/LVM support
-    if ( $config eq "RAID" || $config =~ /^VG_/ )
+    # no physical devices here
+    next if ( $config =~ /^PHY_(.+)$/ );
+
+    # create the RAID devices and the filesystems
+    ( $config eq "RAID" ) or die
+      "INTERNAL ERROR: Invalid config\n";
+
+    # create all raid devices
+    foreach my $id ( sort keys %{ $FAI::configs{$config}{"volumes"} } )
     {
+      # the desired RAID level
+      my $level = $FAI::configs{$config}{"volumes"}{$id}{"mode"};
+      # prepend "raid", if the mode is numeric-only
+      $level = "raid" . $level if ( $level =~ /^\d+$/ );
+      # the list of RAID devices
+      my @devs = keys %{ $FAI::configs{$config}{"volumes"}{$id}{"devices"} };
+      # create the command
+      push @FAI::commands, "yes | mdadm --create /dev/md$id --level=$level "
+        . "--raid-devices=" . scalar( @devs ) . " " . join( " ", @devs );
+      # create the filesystem on the volume
+      &FAI::build_mkfs_commands( "/dev/md$id",
+        \%{ $FAI::configs{$config}{"volumes"}{$id} } );
+    }
+  }
+}
+    
 
-      # TODO
-      next;
+################################################################################
+#
+# @brief Using the configurations from %FAI::configs, a list of commands is
+# built to setup the LVM
+#
+# The list is @FAI::commands
+#
+################################################################################
+sub build_lvm_commands
+{
+
+  # loop through all configs
+  foreach my $config ( keys %FAI::configs )
+  {
+    # no physical devices here
+    next if ( $config =~ /^PHY_(.+)$/ );
+
+    # no RAID devices here
+    next if ( $config eq "RAID" );
+
+    # create the volume groups, the logical volumes and the filesystems
+    ( $config =~ /^VG_(.+)$/ ) or die
+      "INTERNAL ERROR: Invalid config\n";
+
+    # the volume group
+    my $vg = $1;
+    
+    # find volumes that should be preserved or resized and ensure that they
+    # already exist
+    foreach
+      my $lv ( keys %{ $FAI::configs{$config}{"volumes"} } )
+    {
+      next
+        unless (
+        $FAI::configs{$config}{"volumes"}{$lv}{"size"}{"preserve"} ==
+        1
+        || $FAI::configs{$config}{"volumes"}{$lv}{"size"}{"resize"} ==
+        1 );
+
+      # preserved or resized volumes must exist already
+      defined( $FAI::current_lvm_config{$vg}{"volumes"}{$lv} )
+        or die "/dev/$vg/$lv can't be preserved, it does not exist.\n";
     }
 
-    # configure a physical device
-    elsif ( $config =~ /^PHY_(.*)$/ )
+    # create the volume group, if it doesn't exist already
+    if ( ! defined( $FAI::current_lvm_config{$vg} ) )
     {
+      # create all the devices, unless they already exist (then, pvdisplay will
+      # succeed)
+      push @FAI::commands, "pvdisplay $_ || pvcreate $_" foreach ( keys
+        %{ $FAI::configs{$config}{"devices"} } );
+      # create the volume group
+      push @FAI::commands, "vgcreate $vg " . join( " ", keys %{
+          $FAI::configs{$config}{"devices"} } );
+    }
+    # otherwise add or remove the devices for the volume group, run pvcreate
+    # where needed (using pvdisplay <bla> || pvcreate <bla>)
+    else
+    {
+      # the list of devices to be created
+      my %new_devs = ();
+      # create an undefined entry for each new device
+      @new_devs{keys %{ $FAI::configs{$config}{"devices"} }} = ();
+      # create all the devices, unless they already exist (then, pvdisplay will
+      # succeed)
+      push @FAI::commands, "pvdisplay $_ || pvcreate $_" foreach ( keys
+        %new_devs );
+      # extend the volume group by the new devices (includes the current ones)
+      push @FAI::commands, "vgextend $vg " . join( " ", keys %new_devs );
+      # the devices to be removed
+      my %rm_devs = ();
+      @rm_devs{@{$FAI::current_lvm_config{$vg}{"physical_volumes"}}} = ();
+      # remove remaining devices from the list
+      delete $rm_devs{$_} foreach ( keys %new_devs );
+      # run vgreduce to get them removed
+      push @FAI::commands, "vgreduce $vg " . join( " ", keys %rm_devs ) if (
+        scalar( keys %rm_devs ) );
+    }
+    
+    # enable the volume group
+    push @FAI::commands, "vgchange -a y $vg";
+    
+    # remove, resize, create the logical volumes
+    # remove all volumes that do not exist anymore or need not be preserved
+    foreach my $lv ( keys %{ $FAI::current_lvm_config{$vg}{"volumes"} } )
+    {
+      # skip preserved/resized volumes
+      next if ( defined( $FAI::configs{$config}{"volumes"}{$lv} ) &&
+        ( $FAI::configs{$config}{"volumes"}{$lv}{"size"}{"preserve"} == 1 ||
+          $FAI::configs{$config}{"volumes"}{$lv}{"size"}{"resize"} ) );
+      # remove $lv
+      push @FAI::commands, "lvremove -f $vg/$lv";
+    }
 
-      # the device to be configured
-      my $disk = $1;
+    # now create or resize the configured logical volumes
+    foreach my $lv ( keys %{ $FAI::configs{$config}{"volumes"} } )
+    {
+      # skip preserved partitions, but ensure that they exist
+      if ( $FAI::configs{$config}{"volumes"}{$lv}{"size"}{"preserve"} == 1 )
+      {
+        defined( $FAI::current_lvm_config{$vg}{"volumes"}{$lv} ) or die
+          "Preserved volume $vg/$lv does not exist\n";
+        next;
+      }
+      # resize the volume 
+      if ( $FAI::configs{$config}{"volumes"}{$lv}{"size"}{"resize"} == 1 )
+      {
+        defined( $FAI::current_lvm_config{$vg}{"volumes"}{$lv} ) or die
+          "Resized volume $vg/$lv does not exist\n";
+        # note that resizing a volume destroys the data on it
+        push @FAI::commands, "lvresize -L " .
+          $FAI::configs{$config}{"volumes"}{$lv}{"size"}{"eff_size"} . " $vg/$lv";
+      }
+      # create a new volume
+      else
+      {
+        push @FAI::commands, "lvcreate -n $lv -L " .
+          $FAI::configs{$config}{"volumes"}{$lv}{"size"}{"eff_size"} . " $vg";
+        # create the filesystem on the volume
+        &FAI::build_mkfs_commands( "/dev/$vg/$lv",
+          \%{ $FAI::configs{$config}{"volumes"}{$lv} } );
+      }
+    }
 
-      # the list of partitions that must be preserved
-      my @to_preserve = ();
+  }
+}
 
-      # the index of the existing extended partiton
-      my $extended = -1;
+################################################################################
+#
+# @brief Using the configurations from %FAI::configs, a list of commands is
+# built to setup the partitions
+#
+# The list is @FAI::commands
+#
+################################################################################
+sub build_disk_commands
+{
 
-      # find any existing extended partition on msdos disklabels
-      if ( $FAI::current_config{$disk}{"disklabel"} eq "msdos" )
-      {
+  # loop through all configs
+  foreach my $config ( keys %FAI::configs )
+  {
+    # no RAID devices here
+    next if ( $config eq "RAID" );
 
-        # loop over all existing partitions
-        foreach my $part_id (
-          sort keys %{ $FAI::current_config{$disk}{"partitions"} } )
-        {
-          next
-            unless ( $FAI::current_config{$disk}{"partitions"}{$part_id}
-            {"is_extended"} == 1 );
+    # no LVM here
+    next if ( $config =~ /^VG_(.+)$/ );
 
-          # TODO: should be handled properly
-          ( $extended == -1 ) or die "INTERNAL ERROR: Can't handle more than 1
-          extended partition\n";
+    # configure a physical device
+    ( $config =~ /^PHY_(.+)$/ ) or die
+      "INTERNAL ERROR: Invalid config\n";
 
-          # set the id of the extended partition
-          $extended = $part_id;
-        }
-      }
+    # the device to be configured
+    my $disk = $1;
 
+    # create partitions on non-virtual configs
+    if ( $FAI::configs{$config}{"virtual"} == 0 )
+    {
+      # the list of partitions that must be preserved
+      my @to_preserve = ();
+
       # find partitions that should be preserved or resized
       foreach
         my $part_id ( sort keys %{ $FAI::configs{$config}{"partitions"} } )
@@ -104,164 +285,338 @@
           || $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"resize"} ==
           1 );
 
-        # preserved partitions must exist already
+        # preserved or resized partitions must exist already
         defined( $FAI::current_config{$disk}{"partitions"}{$part_id} )
           or die "$part_id can't be preserved, it does not exist.\n";
 
+        # add a mapping from the configured partition to the existing one
+        # (identical here, may change for extended partitions below)
+        $FAI::configs{$config}{"partitions"}{$part_id}{"maps_to_existing"} =
+          $part_id;
+
         # add $part_id to the list of preserved partitions
         push @to_preserve, $part_id;
 
-        # in case $part_id is a logical partition, the corresponding extended
-        # partition must be preserved as well
-        if ( $extended > -1
-          && $part_id > 4
-          && $FAI::current_config{$disk}{"disklabel"} eq "msdos" )
-        {
-          push @to_preserve, $extended;
-
-          # set $extended to -1 to avoid adding it multiple times; do not rely
-          # on $extended later on!
-          $extended = -1;
-        }
       }
 
       # sort the list of preserved partitions
       @to_preserve = sort { $a <=> $b } @to_preserve;
-
-      # check, whether a new disk label is required
-      if ( $FAI::configs{$config}{'disklabel'} ne
-        $FAI::current_config{$disk}{'disklabel'} )
+     
+      # add the extended partition as well, if logical partitions must be
+      # preserved; and mark it as resize
+      if ( $FAI::configs{$config}{"disklabel"} eq "msdos" )
       {
+        # we assume there are no logical partitions
+        my $has_logical = 0;
+        my $extended = -1;
+        # now check all entries; the array is sorted
+        foreach my $part_id (@to_preserve)
+        {
+          # the extended partition may already be listed; then, the id of the
+          # extended partition must not change
+          if ( $FAI::current_config{$disk}{"partitions"}{$part_id}{"is_extended"} == 1 )
+          {
+            ( defined( $FAI::configs{$config}{"partitions"}{$extended}{"size"}{"extended"} ) &&
+              defined( $FAI::current_config{$disk}{"partitions"}{$extended}{"is_extended"} ) &&
+              $FAI::configs{$config}{"partitions"}{$extended}{"size"}{"extended"} == 1 &&
+              $FAI::current_config{$disk}{"partitions"}{$extended}{"is_extended"}
+              == 1 ) or die "ID of extended partition changes\n";
+            # make sure resize is set
+            $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"resize"} =
+              1;
+            $extended = $part_id;
+            last;
+          }
+          # there is some logical partition
+          if ( $part_id > 4 )
+          {
+            $has_logical = 1;
+            last;
+          }
+        }
 
-        # A new disk label may only be written if no partitions need to be
-        # preserved
-        ( scalar(@to_preserve) > 0 )
-          and die "Can't change disklabel, partitions are to be preserved!\n";
+        # if the extended partition is not listed yet, find and add it now; note
+        # that we need to add the existing one
+        if ( 1 == $has_logical && -1 == $extended )
+        {
+          foreach
+            my $part_id ( sort keys %{ $FAI::current_config{$disk}{"partitions"} } )
+          {
+            # no extended partition
+            next unless (
+              $FAI::current_config{$disk}{"partitions"}{$part_id}{"is_extended"} == 1
+              );
+          
+            # find the configured extended partition to set the mapping
+            foreach
+              my $p ( sort keys %{ $FAI::configs{$config}{"partitions"} } )
+            {
+              next unless (
+                $FAI::configs{$config}{"partitions"}{$p}{"size"}{"extended"} == 1
+                );
+              
+              # make sure resize is set
+              $FAI::configs{$config}{"partitions"}{$p}{"size"}{"resize"} =
+                1;
+            
+              # store the id for further checks
+              $extended = $p;
 
-        # add a command to set the disklabel
-        push @FAI::commands, "$FAI::system_commands{'parted'} $disk mklabel"
-          . $FAI::configs{$config}{'disklabel'};
+              # add a mapping entry to the existing extended partition
+              $FAI::configs{$config}{"partitions"}{$p}{"maps_to_existing"} =
+                $part_id;
+
+              # add it to the preserved partitions
+              push @to_preserve, $p;
+
+              last;
+            }
+        
+            # sort the list of preserved partitions (again)
+            @to_preserve = sort { $a <=> $b } @to_preserve;
+     
+            last;
+          }
+        }
+        
+        # a sanity check: if there are logical partitions, they extended must
+        # have been added
+        ( 0 == $has_logical || -1 != $extended ) or die
+          "INTERNAL ERROR: Required extended partition not detected for preserve\n";
       }
-      else
+
+      # A new disk label may only be written if no partitions need to be
+      # preserved
+      ( ( $FAI::configs{$config}{'disklabel'} eq
+        $FAI::current_config{$disk}{'disklabel'} ) || ( scalar(@to_preserve) == 0
+        ) ) or die "Can't change disklabel, partitions are to be preserved\n";
+
+      # write the disklabel to drop the previous partition table
+      push @FAI::commands, "$FAI::system_commands{'parted'} $disk mklabel "
+        . $FAI::configs{$config}{'disklabel'};
+      
+      # once we rebuild partitions, their ids are likely to change; this counter
+      # helps keeping track of this
+      my $part_nr = 0;
+      
+      # now rebuild all preserved partitions
+      foreach my $part_id (@to_preserve)
       {
+        # get the existing id
+        my $mapped_id =
+          $FAI::configs{$config}{"partitions"}{$part_id}{"maps_to_existing"};
+        # get the original starts and ends
+        my $start =
+          $FAI::current_config{$disk}{"partitions"}{$mapped_id}{"begin_byte"};
+        my $end =
+          $FAI::current_config{$disk}{"partitions"}{$mapped_id}{"end_byte"};
 
-        # the disk label is unchanged, now all partitions must be removed unless
-        # they are to be preserved
-        foreach my $part_id (
-          sort keys %{ $FAI::current_config{$disk}{"partitions"} } )
+        # the type of the partition defaults to primary
+        my $part_type = "primary";
+        if ( $FAI::configs{$config}{"disklabel"} eq "msdos" )
         {
 
-          # partition $part_id is to be preserved, skip it
-          if ( $to_preserve[0] == $part_id )
+          # change the partition type to extended or logical as appropriate
+          if ( $FAI::configs{$config}{"partitions"}{$part_id}{"size"}
+            {"extended"} == 1 )
           {
-
-            # and remove it from the list
-            shift @to_preserve;
-            next;
+            $part_type = "extended";
           }
+          elsif ( $part_id > 4 )
+          {
+            $part_type = "logical";
+            $part_nr = 4 if ( $part_nr < 4 );
+          }
+        }
 
-          # add a command to remove $part_id
-          push @FAI::commands,
-            "$FAI::system_commands{'parted'} $disk rm $part_id";
-        }
+        # increase the partition counter for the partition created next and
+        # write it to the configuration
+        $part_nr++;
+        $FAI::current_config{$disk}{"partitions"}{$mapped_id}{"new_id"} =
+          $part_nr;
+
+        # build a parted command to create the partition
+        push @FAI::commands, "$FAI::system_commands{'parted'} $disk mkpart "
+          . "$part_type $start" . "B " . $end . "B";
       }
 
-      # the byte count where the next parition should start
-      my $next_start = 0;
-
-      # generate the commands for resizing or creating partitions
-      foreach
-        my $part_id ( sort keys %{ $FAI::configs{$config}{"partitions"} } )
+      # resize partitions; first we shrink partitions, then grow others;
+      # furthermore we start from the end to shrink logical partitions before
+      # the extended one, but grow partitions starting from the beginning
+      my @shrink_list = reverse sort (@to_preserve);
+      my @grow_list = ();
+      # iterate over the worklists
+      foreach my $part_id (@shrink_list)
       {
+        # anything to be done?
+        next unless (
+          $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"resize"} == 1 );
 
-        # check, whether $part_id must be preserved; we have checked it for
-        # existence above already
+        # get the existing id
+        my $mapped_id =
+          $FAI::configs{$config}{"partitions"}{$part_id}{"maps_to_existing"};
+        
+        # if partition is to be grown, move it to then grow_list
         if (
-          $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"preserve"} ==
-          1 )
+          $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"eff_size"} >
+          $FAI::current_config{$disk}{"partitions"}{$mapped_id}{"count_byte"} )
         {
-
-          # the next partition may start after the preserved partition
-          $next_start =
-            $FAI::current_config{$disk}{"partitions"}{$part_id}{"end_byte"} + 1
-            unless ( $FAI::configs{$config}{"disklabel"} eq "msdos"
-            && $FAI::configs{$config}{"partitions"}{$part_id}{"size"}
-            {"extended"} == 1 );
+          unshift @grow_list, $part_id;
+          next;
         }
+          
+        # get the new partition id
+        my $p = $FAI::current_config{$disk}{"partitions"}{$mapped_id}{"new_id"};
+        # get the new starts and ends
+        my $start =
+          $FAI::configs{$config}{"partitions"}{$part_id}{"start_byte"};
+        my $end =
+          $FAI::configs{$config}{"partitions"}{$part_id}{"end_byte"};
 
-        # check, whether $part_id must be resized; we have checked it for
-        # existence above already
-        elsif (
-          $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"resize"} ==
-          1 )
-        {
+        # build an appropriate command
+        push @FAI::commands,
+          "$FAI::system_commands{'parted'} $disk resize $p $start"
+          . "B " . $end . "B";
+      }
+      # grow the remaining partitions
+      foreach my $part_id (@grow_list)
+      {
+        # get the existing id
+        my $mapped_id =
+          $FAI::configs{$config}{"partitions"}{$part_id}{"maps_to_existing"};
+        # get the new partition id
+        my $p = $FAI::current_config{$disk}{"partitions"}{$mapped_id}{"new_id"};
+        # get the new starts and ends
+        my $start =
+          $FAI::configs{$config}{"partitions"}{$part_id}{"start_byte"};
+        my $end =
+          $FAI::configs{$config}{"partitions"}{$part_id}{"end_byte"};
 
-          # resizing a partition, which must be formatted later makes no sense
-          ( $FAI::current_config{$disk}{"partitions"}{"filesystem"} ne
-              $FAI::configs{$config}{"partitions"}{$part_id}{"filesystem"} )
-            and die
-"Filesystem change has been requested for $part_id, resizing makes no sense\n";
+        # build an appropriate command
+        push @FAI::commands,
+          "$FAI::system_commands{'parted'} $disk resize $p $start"
+          . "B " . $end . "B";
+      }
+      
+      # write the disklabel again to drop the partition table
+      push @FAI::commands, "$FAI::system_commands{'parted'} $disk mklabel "
+        . $FAI::configs{$config}{'disklabel'};
 
-          # compute end byte of the resized partition
-          my $part_end =
-            $FAI::configs{$config}{"partitions"}{"size"}{"eff_size"} +
-            $next_start - 1;
+      # generate the commands for creating all partitions
+      foreach
+        my $part_id ( sort keys %{ $FAI::configs{$config}{"partitions"} } )
+      {
 
-          # build an appropriate command
-          push @FAI::commands,
-            "$FAI::system_commands{'parted'} $disk resize $part_id $next_start"
-            . "B "
-            . $part_size . "B";
+        # get the new starts and ends
+        my $start =
+          $FAI::configs{$config}{"partitions"}{$part_id}{"start_byte"};
+        my $end =
+          $FAI::configs{$config}{"partitions"}{$part_id}{"end_byte"};
 
-          # set $next_start unless this is an extended partition
-          $next_start = $part_size + 1
-            unless ( $FAI::configs{$config}{"disklabel"} eq "msdos"
-            && $FAI::configs{$config}{"partitions"}{$part_id}{"size"}
-            {"extended"} == 1 );
-        }
-
-        # $part_id neither needs to be resized nor preserved, but newly created
-        else
+        # the type of the partition defaults to primary
+        my $part_type = "primary";
+        if ( $FAI::configs{$config}{"disklabel"} eq "msdos" )
         {
 
-          # the type of the partition defaults to primary
-          my $part_type = "primary";
-          if ( $FAI::configs{$config}{"disklabel"} eq "msdos" )
+          # change the partition type to extended or logical as appropriate
+          if ( $FAI::configs{$config}{"partitions"}{$part_id}{"size"}
+            {"extended"} == 1 )
           {
-
-            # change the partition type to extended or logical as appropriate
-            if ( $FAI::configs{$config}{"partitions"}{$part_id}{"size"}
-              {"extended"} == 1 )
-            {
-              $part_type = "extended";
-            }
-            elsif ( $part_id > 4 )
-            {
-              $part_type = "logical";
-            }
+            $part_type = "extended";
           }
+          elsif ( $part_id > 4 )
+          {
+            $part_type = "logical";
+          }
+        }
 
-          # compute end byte of the new partition
-          my $part_end =
-            $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"eff_size"} +
-            $next_start - 1;
-
-          # build a parted command to create the partition
-          push @FAI::commands, "$FAI::system_commands{'parted'} $disk mkpart
-            $part_type $next_start" . "B " . $part_size . "B";
-          $next_start = $part_size + 1
-            unless ( $FAI::configs{$config}{"disklabel"} eq "msdos"
-            && $FAI::configs{$config}{"partitions"}{$part_id}{"size"}
-            {"extended"} == 1 );
-        }
+        # build a parted command to create the partition
+        push @FAI::commands, "$FAI::system_commands{'parted'} $disk mkpart "
+          . "$part_type $start" . "B " . $end . "B";
       }
+     
+      # set the bootable flag, if requested at all
+      push @FAI::commands, "$FAI::system_commands{'parted'} $disk set "
+        . $FAI::configs{$config}{"bootable"} . " boot on" if
+        ( $FAI::configs{$config}{"bootable"} > -1 );
+        
+      # sleep a little to wait for udev to set up all devices
+      push @FAI::commands, "sleep 1";
     }
-    else
+
+    # generate the commands for creating all filesystems
+    foreach
+      my $part_id ( sort keys %{ $FAI::configs{$config}{"partitions"} } )
     {
-      die "INTERNAL ERROR: Invalid config\n";
+      # skip preserved/resized/extended partitions
+      next
+        if (
+        $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"preserve"} ==
+        1
+        || $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"resize"} ==
+        1
+        || $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"extended"} ==
+        1 );
+      
+      # create the filesystem on $disk$part_id
+      &FAI::build_mkfs_commands( $disk . $part_id,
+        \%{ $FAI::configs{$config}{"partitions"}{$part_id} } );
     }
   }
 }
 
+################################################################################
+#
+# @brief Whatever happened, write the previous partition table to the disk again
+#
+################################################################################
+sub restore_partition_table
+{
+  # loop through all existing configs
+  foreach my $disk ( keys %FAI::current_config )
+  {
+    # write the disklabel again to drop the partition table
+    &FAI::execute_command( "$FAI::system_commands{'parted'} $disk mklabel "
+      . $FAI::current_config{$disk}{'disklabel'} );
+
+    # generate the commands for creating all partitions
+    foreach
+      my $part_id ( sort keys %{ $FAI::current_config{$disk}{"partitions"} } )
+    {
+
+      # get the starts and ends
+      my $start =
+        $FAI::current_config{$disk}{"partitions"}{$part_id}{"begin_byte"};
+      my $end =
+        $FAI::current_config{$disk}{"partitions"}{$part_id}{"end_byte"};
+
+      # the type of the partition defaults to primary
+      my $part_type = "primary";
+      if ( $FAI::current_config{$disk}{"disklabel"} eq "msdos" )
+      {
+
+        # change the partition type to extended or logical as appropriate
+        if ( $FAI::current_config{$disk}{"partitions"}{$part_id}{"is_extended"}
+          == 1 )
+        {
+          $part_type = "extended";
+        }
+        elsif ( $part_id > 4 )
+        {
+          $part_type = "logical";
+        }
+      }
+
+      # build a parted command to create the partition
+      &FAI::execute_command( "$FAI::system_commands{'parted'} $disk mkpart "
+        . "$part_type $start" . "B " . $end . "B" );
+    }
+    warn "Partition table of disk $disk has been restored\n";
+  }
+   
+  die "shdd2 failed, but the partition tables have been restored\n";
+}
+
+
 1;
 

Modified: people/michael/features/setup_harddisks_2/implementation/shdd2-exec
===================================================================
--- people/michael/features/setup_harddisks_2/implementation/shdd2-exec	2007-05-23 15:49:57 UTC (rev 4292)
+++ people/michael/features/setup_harddisks_2/implementation/shdd2-exec	2007-05-31 07:27:20 UTC (rev 4293)
@@ -52,7 +52,7 @@
 $FAI::error_codes = [
   {
     error   => "parted_1",
-    message => "Parted produced error. Couldn't remove Partition",
+    message => "Parted produced error. Couldn't remove partition\n",
     stderr_regex =>
       ".*Error: Could not stat device rm - No such file or directory.*",
     stdout_regex => "",
@@ -61,15 +61,15 @@
   },
   {
     error        => "parted_2",
-    message      => "Parted produced error. Could not read disk label.",
+    message      => "Parted produced error. Could not read disk label.\n",
     stderr_regex => ".*Error: Unable to open .* - unrecognised disk label.*",
     stdout_regex => "",
     program      => "parted",
-    response     => "die",
+    response     => "warn",
   },
   {
     error   => "parted_3",
-    message => "Parted produced error. Could not open disk",
+    message => "Parted produced error. Could not open disk\n",
     stderr_regex =>
       ".*Error: Could not stat device .* - No such file or directory.*",
     stdout_regex => "",
@@ -77,13 +77,29 @@
     response     => "die"
   },
   {
-    error        => "port_1",
-    message      => "test_error",
-    stderr_regex => ".*Error: .* Port test not found.*",
+    error   => "parted_4",
+    message => "parted not found\n",
+    stderr_regex => ".*(parted: command not found|/sbin/parted: No such file or directory)",
     stdout_regex => "",
-    program      => "port",
+    program      => "parted",
     response     => "die"
   },
+  {
+    error   => "parted_5",
+    message => "Parted was unable to create the partition\n",
+    stderr_regex => "Warning: You requested a partition from .* to .*\\.\$",
+    stdout_regex => "",
+    program      => "parted",
+    response     => \&FAI::restore_partition_table,
+  },
+  {
+    error   => "mkfs.xfs_1",
+    message => "mkfs.xfs refused to create a filesystem. Probably you should add -f to the mkfs options in your disk_config file.\n",
+    stderr_regex => "mkfs.xfs: /dev/.* appears to contain an existing filesystem",
+    stdout_regex => "",
+    program      => "mkfs.xfs",
+    response     => "die",
+  },
 ];
 
 ################################################################################
@@ -99,10 +115,8 @@
 {
   my ($error) = @_;
   my @treffer = grep { $_->{error} eq "$error" } @$FAI::error_codes;
-  foreach my $m (@treffer)    #returns the first found error message.
-  {
-    return "$m->{'message'}";
-  }
+  # returns the first found error message.
+  return $treffer[ 0 ]->{'message'};
 }
 
 ################################################################################
@@ -119,10 +133,8 @@
 {
   my ( $error, $field ) = @_;
   my @treffer = grep { $_->{error} eq "$error" } @$FAI::error_codes;
-  foreach my $m (@treffer)    #returns the first found error message.
-  {
-    return "$m->{$field}";
-  }
+  # returns the first found error message.
+  return $treffer[ 0 ]->{$field};
 }
 ################################################################################
 #
@@ -145,21 +157,17 @@
 {
   my ( $command, $stdout_ref, $stderr_ref ) = @_;
   my $err = &execute_command( $command, $stdout_ref, $stderr_ref );
-  unless ( $err eq "" )
+  if ( $err ne "" )
   {
     my $response = &get_error( $err, "response" );
     my $message  = &get_error( $err, "message" );
 
-    if ( $response eq "die" )
-    {
-      die $message;
-    }
+    $response->() if ( ref( $response ) );
 
-    if ( $response eq "warn" )
-    {
-      warn $message;
-    }
+    die $message if ( $response eq "die" );
 
+    warn $message if ( $response eq "warn" );
+
     return $err;
   }
   return "";
@@ -176,7 +184,7 @@
 # output of the bash command
 #
 # @reference stderr_ref reference to a list, that should contain the standard
-# errer output of the bash command
+# error output of the bash command
 #
 # @return the identifier of the error
 #
@@ -189,27 +197,22 @@
   my @stdout      = ();
   my $stderr_line = "";
   my $stdout_line = "";
-  my $run         = 0;
-  if ( defined( $ENV{"NO_DRY_RUN"} ) && !( $ENV{"NO_DRY_RUN"} =~ /^\s*$/ ) )
-  {
-    $run = 1;
-  }
 
   #make tempfile, get perl filehandle and filename of the file
-  ( my $stderr_fh, my $stderr_filename ) = File::Temp::tempfile();
-  ( my $stdout_fh, my $stdout_filename ) = File::Temp::tempfile();
+  ( my $stderr_fh, my $stderr_filename ) = File::Temp::tempfile( UNLINK => 1 );
+  ( my $stdout_fh, my $stdout_filename ) = File::Temp::tempfile( UNLINK => 1 );
 
   # do only execute the given command, when in no_dry_mode
-  if ( $run == 1 )
+  if ( $FAI::no_dry_run )
   {
-
+    
+    ( $FAI::debug ) and print "(CMD) $command 1> $stdout_filename 2> $stderr_filename\n";
     # execute the bash command, write stderr and stdout into the testfiles
     `$command 1> $stdout_filename 2> $stderr_filename`;
   }
   else
   {
-    printf
-"would run command $command, to run in NO_DRY_MODE, please set environment variable NO_DRY_RUN to 1 \n";
+    print "would run command $command; to have them executed, use -X \n";
   }
 
   # read the tempfile into lists, each element of the list one line
@@ -220,31 +223,22 @@
   close($stderr_fh);
   close($stdout_fh);
 
+  ( $FAI::debug ) and print "(STDERR) $_" foreach ( @stderr );
+  ( $FAI::debug ) and print "(STDOUT) $_" foreach ( @stdout );
+
   #if the stderr contains information, get the first line for error recognition
-  if ( scalar(@stderr) > 0 )
-  {
-    $stderr_line = $stderr[0];
-  }
+  $stderr_line = $stderr[0] if ( scalar(@stderr) > 0 );
 
   #see last comment
-  if ( scalar(@stdout) > 0 )
-  {
-    $stdout_line = $stdout[0];
-  }
+  $stdout_line = $stdout[0] if ( scalar(@stdout) > 0 );
 
   #if an array is passed to the function, it is filled with the stdout
-  if ( 'ARRAY' eq ref($stdout_ref) )
-  {
-    @$stdout_ref = @stdout;
-  }
+  @$stdout_ref = @stdout if ( 'ARRAY' eq ref($stdout_ref) );
 
   #see above
-  if ( 'ARRAY' eq ref($stderr_ref) )
-  {
-    @$stderr_ref = @stderr;
-  }
+  @$stderr_ref = @stderr if ( 'ARRAY' eq ref($stderr_ref) );
 
-  #get the error, eventually happened
+  #get the error, if there was any
   foreach my $err (@$FAI::error_codes)
   {
     if (

Modified: people/michael/features/setup_harddisks_2/implementation/shdd2-fstab
===================================================================
--- people/michael/features/setup_harddisks_2/implementation/shdd2-fstab	2007-05-23 15:49:57 UTC (rev 4292)
+++ people/michael/features/setup_harddisks_2/implementation/shdd2-fstab	2007-05-31 07:27:20 UTC (rev 4293)
@@ -38,105 +38,210 @@
 ################################################################################
 #
 # @brief this function generates the fstab file from our representation of the
-# partitions to be created. Call this function only with one parameter,
-# the rest is used for recursion.
+# partitions to be created.
 #
-# @reference hash_ref to our representation of the partitions to be created
-# @param modus help variable for the recursion. holds the value wheter we are
-# opertationing in physical partition, lvm or raid mode
+# @reference config Reference to our representation of the partitions to be 
+# created
 #
-# @param device help variable, holds the actual device
+# @return list of fstab lines
 #
-# @reference listref help variable, holding the reference to the list which
-# contains the lines of the fstab file to be generated
-#
-# @param call_count holds the actual recursion depth
-#
-# @return 0
-#
 ################################################################################
 sub generate_fstab
 {
+  # config structure is the only input
+  my ( $config ) = @_;
 
-  # MT: TODO - CONT HERE
-  my ( $hash_ref, $modus, $device, $listref, $call_count ) = @_;
+  # the file to be returned, a list of lines
+  my @fstab = ();
 
-  #first check if we have already a list, when not, create one
-  if ( !$listref )
+  # walk through all configured parts
+  # the order of entries is most likely wrong, it is fixed at the end
+  foreach my $c ( keys %$config )
   {
-    my @newlist = ();
-    $listref = \@newlist;
-  }
+    # entry is a physical device
+    if ( $c =~ /^PHY_(.+)$/ )
+    {
+      my $device = $1;
+      # make sure the desired fstabkey is defined at all
+      defined( $config->{$c}->{"fstabkey"} ) or die
+        "INTERNAL ERROR: fstabkey undefined\n";
+      # create a line in the output file for each partition
+      foreach my $p ( sort keys %{ $config->{$c}->{"partitions"} } )
+      {
+        # keep a reference to save some typing
+        my $p_ref = $config->{$c}->{"partitions"}->{$p};
+        # skip extended partitions
+        next if ( $p_ref->{"size"}->{"extended"} );
+        # skip entries without a mountpoint
+        next if ( $p_ref->{"mountpoint"} eq "-" );
+        # each line is a list of values
+        my @fstab_line = ();
+        # write the device name as the first entry; if the user prefers uuids
+        # or labels, use these if available
+        my @uuid = ();
+        &execute_command_std( "/lib/udev/vol_id -u $device" .
+          $p_ref->{"number"}, \@uuid, 0 );
+        # every device must have a uuid, otherwise this is an error (unless we
+        # are testing only)
+        ( $FAI::no_dry_run == 0 || scalar( @uuid ) == 1 ) or die
+          "Failed to obtain UUID for $device" . $p_ref->{"number"} . "\n";
+        # get the label -- this is likely empty
+        my @label = ();
+        &execute_command_std( "/lib/udev/vol_id -l $device" .
+          $p_ref->{"number"}, \@label, 0 );
+        # using the fstabkey value the desired device entry is defined
+        if ( $config->{$c}->{"fstabkey"} eq "uuid" )
+        {
+          chomp( $uuid[ 0 ] );
+          push @fstab_line, "UUID=" . $uuid[ 0 ];
+        }
+        elsif ( $config->{$c}->{"fstabkey"} eq "label" && scalar( @label ) == 1 )
+        {
+          chomp( $label[ 0 ] );
+          push @fstab_line, "LABEL=" . $label[ 0 ];
+        }
+        else
+        {
+          # otherwise, use the usual device path
+          push @fstab_line, $device . $p_ref->{"number"};
+        }
+        # next is the mountpoint
+        push @fstab_line, $p_ref->{"mountpoint"};
+        # the filesystem to be used
+        push @fstab_line, $p_ref->{"filesystem"};
+        # add the mount options
+        push @fstab_line, $p_ref->{"mount_options"};
+        # never dump
+        push @fstab_line, 0;
+        # order of filesystem checks; the root filesystem gets a 1, the others 2
+        push @fstab_line, 2;
+        $fstab_line[-1] = 1 if ( $p_ref->{"mountpoint"} eq "/" );
 
-#check if call_count is set, if not, the function is called the first time, set it
-  if ( !$call_count )
-  {
-    $call_count = 1;
-  }
-  else
-  {
-
-    #if we go deaper, increment it
-    $call_count++;
-  }
-
-  # go through all drives, lvms, raids
-  foreach my $key ( keys %$hash_ref )
-  {
-
-    if ( ref( $hash_ref->{$key} ) && $key =~ /^PHY_(.*)$/ )
+        # join the columns of one line with tabs, and push it to our fstab line array
+        push @fstab, join( "\t", @fstab_line );
+          
+        # set the ROOT_PARTITION variable, if this is the mountpoint for /
+        $FAI::disk_var{ "ROOT_PARTITION" } = $device . $p_ref->{"number"} 
+          if ( $p_ref->{"mountpoint"} eq "/" );
+        # add to the swaplist, if the filesystem is swap
+        $FAI::disk_var{ "SWAPLIST" } .= " " . $device . $p_ref->{"number"} if (
+          $p_ref->{"filesystem"} eq "swap" );
+      }
+    }
+    elsif ( $c =~ /^VG_(.+)$/ )
     {
+      my $device = $1;
+      # create a line in the output file for each logical volume
+      foreach my $l ( sort keys %{ $config->{$c}->{"volumes"} } )
+      {
+        # keep a reference to save some typing
+        my $l_ref = $config->{$c}->{"volumes"}->{$l};
+        # skip entries without a mountpoint
+        next if ( $l_ref->{"mountpoint"} eq "-" );
+        # each line is a list of values
+        my @fstab_line = ();
+        # write the logical volume path as the first entry
+        push @fstab_line, "/dev/$device/$l";
+        # next is the mountpoint
+        push @fstab_line, $l_ref->{"mountpoint"};
+        # the filesystem to be used
+        push @fstab_line, $l_ref->{"filesystem"};
+        # add the mount options
+        push @fstab_line, $l_ref->{"mount_options"};
+        # never dump
+        push @fstab_line, 0;
+        # order of filesystem checks; the root filesystem gets a 1, the others 2
+        push @fstab_line, 2;
+        $fstab_line[-1] = 1 if ( $l_ref->{"mountpoint"} eq "/" );
 
-      #set the modus to physical drive partition
-      $modus  = "PHY";
-      $device = $1;
+        # join the columns of one line with tabs, and push it to our fstab line array
+        push @fstab, join( "\t", @fstab_line );
+          
+        # set the ROOT_PARTITION variable, if this is the mountpoint for /
+        $FAI::disk_var{ "ROOT_PARTITION" } = "/dev/$device/$l" if ( $l_ref->{"mountpoint"} eq
+          "/" );
+        # add to the swaplist, if the filesystem is swap
+        $FAI::disk_var{ "SWAPLIST" } .= " /dev/$device/$l" if (
+          $l_ref->{"filesystem"} eq "swap" );
+      }
     }
-
-    # check if we are at the important place for the fstab file
-    if ( ref( $hash_ref->{$key} ) && $key eq "partitions" )
+    elsif ( $c eq "RAID" )
     {
-
-# every partition means one line in fstab, let's get the information about each one
-      foreach my $partition ( keys %{ $hash_ref->{$key} } )
+      # create a line in the output file for each device
+      foreach my $r ( sort keys %{ $config->{$c}->{"volumes"} } )
       {
-        my $p_ref = $hash_ref->{$key}->{$partition};
-        if ( !$p_ref->{'size'}->{'extended'} )
-        {
-          my @fstab_line = ();
-          push @fstab_line, $device . $partition;
-          push @fstab_line, $p_ref->{'mountpoint'};
-          push @fstab_line, $p_ref->{'filesystem'};
-          push @fstab_line, $p_ref->{'mount_options'};
-          push @fstab_line, 0;
-          if ( $p_ref->{'mountpoint'} eq "/root" )
-          {
-            push @fstab_line, 1;
-          }
-          else
-          {
-            push @fstab_line, 2;
-          }
+        # keep a reference to save some typing
+        my $r_ref = $config->{$c}->{"volumes"}->{$r};
+        # skip entries without a mountpoint
+        next if ( $r_ref->{"mountpoint"} eq "-" );
+        # each line is a list of values
+        my @fstab_line = ();
+        # write the device name as the first entry
+        push @fstab_line, "/dev/md" . $r;
+        # next is the mountpoint
+        push @fstab_line, $r_ref->{"mountpoint"};
+        # the filesystem to be used
+        push @fstab_line, $r_ref->{"filesystem"};
+        # add the mount options
+        push @fstab_line, $r_ref->{"mount_options"};
+        # never dump
+        push @fstab_line, 0;
+        # order of filesystem checks; the root filesystem gets a 1, the others 2
+        push @fstab_line, 2;
+        $fstab_line[-1] = 1 if ( $r_ref->{"mountpoint"} eq "/" );
 
-#lets join the columns of one line with tabs, and the push it to our fstab line array
-          push @$listref, join( "\t", @fstab_line );
-        }
-
+        # join the columns of one line with tabs, and push it to our fstab line array
+        push @fstab, join( "\t", @fstab_line );
+          
+        # set the ROOT_PARTITION variable, if this is the mountpoint for /
+        $FAI::disk_var{ "ROOT_PARTITION" } = "/dev/md" . $r if ( $r_ref->{"mountpoint"} eq
+          "/" );
+        # add to the swaplist, if the filesystem is swap
+        $FAI::disk_var{ "SWAPLIST" } .= " /dev/md$r" if (
+          $r_ref->{"filesystem"} eq "swap" );
       }
-
     }
-    if ( ref( $hash_ref->{$key} ) )
+    else
     {
-      &generate_fstab( $hash_ref->{$key}, $modus, $device, $listref,
-        $call_count );
+      die "INTERNAL ERROR: Unexpected key $c\n";
     }
+  }
 
-  }
-  if ( $call_count eq 1 )
+  # cleanup the swaplist (remove leading space)
+  $FAI::disk_var{ "SWAPLIST" } =~ s/^\s+//;
+
+  # sort the lines in @fstab to enable all sub mounts
+  for ( my $i = 0; $i < scalar( @fstab ); $i++ )
   {
+    # take out the mountpoint
+    ($_, my $mp_1) = split( "\t", $fstab[ $i ] );
+    # partitions without a mountpoint are fine
+    next if ( $mp_1 eq "none" );
 
-    #if we are at highes level, lets return the listref
-    return @$listref;
+    for ( my $j = $i + 1; $j < scalar( @fstab ); $j++ )
+    {
+      # take out the other mountpoint
+      ($_, my $mp_2) = split( "\t", $fstab[ $j ] );
+      # remove the trailing / (even though this might make it the empty string
+      $mp_2 =~ s/\/$//;
+
+      # $mp_1 depends on $mp_2 being mounted, swap them
+      if ( $mp_1 =~ /^\Q$mp_2\E\// )
+      {
+        my $line_i = $fstab[ $i ];
+        $fstab[ $i ] = $fstab[ $j ];
+        $fstab[ $j ] = $line_i;
+        $mp_1 = $mp_2;
+      }
+    }
   }
+
+  # add a nice header to fstab
+  unshift @fstab, "# <file sys>\t<mount point>\t<type>\t<options>\t<dump>\t<pass>";
+  unshift @fstab, "#";
+  unshift @fstab, "# /etc/fstab: static file system information.";
+  # return the list of lines
+  return @fstab;
 }
 
 1;

Modified: people/michael/features/setup_harddisks_2/implementation/shdd2-init
===================================================================
--- people/michael/features/setup_harddisks_2/implementation/shdd2-init	2007-05-23 15:49:57 UTC (rev 4292)
+++ people/michael/features/setup_harddisks_2/implementation/shdd2-init	2007-05-31 07:27:20 UTC (rev 4293)
@@ -37,15 +37,12 @@
 
 ################################################################################
 #
-# @brief Enable debugging by setting $FAI::debug to a value greater than 0
+# @brief Enable debugging by setting $debug to a value greater than 0
 #
 ################################################################################
-$FAI::debug = 1;
+$FAI::debug = 0;
+defined( $ENV{debug} ) and $FAI::debug = $ENV{debug};
 
-# TODO - REMOVE THIS
-$ENV{disklist} = "hda
-";
-
 ################################################################################
 #
 # @brief The lists of disks of the system
@@ -55,15 +52,67 @@
 
 ################################################################################
 #
+# @brief The variables later written to disk_var.sh
+#
+################################################################################
+%FAI::disk_var = ();
+$FAI::disk_var{ "SWAPLIST" } = "";
+
+################################################################################
+#
 # @brief The hash of all configurations specified in the disk_config file
 #
 # The structure is as follows:
+# PHY_<DEVICE>
+#   virtual (0|1)
+#   disklabel STRING
+#   bootable -1..n
+#   partitions
+#     <1..n>
+#       size
+#         extended (0|1)
+#         preserve (0|1)
+#         resize (0|1)
+#         range
+#         eff_size
+#       number 1..n
+#       maps_to_existing 1..n
+#       start_byte
+#       end_byte
+#       mountpoint
+#       mount_options
+#       filesystem
+#       fs_options
+#       label
 # VG_<NAME>
-#   TODO
-# PHY_<DEVICE>
-#   TODO
+#   devices
+#   estimated_size
+#   volumes
+#     <logical-volume-name>
+#       size
+#         preserve (0|1)
+#         resize (0|1)
+#         range
+#         eff_size
+#       mountpoint
+#       mount_options
+#       filesystem
+#       fs_options
+#       label
 # RAID
-#   TODO
+#   volumes
+#     <0..n>
+#       mode
+#       devices
+#         /dev/<device-name>
+#           options
+#             spare (0|1)
+#             missing (0|1)
+#       mountpoint
+#       mount_options
+#       filesystem
+#       fs_options
+#       label
 #
 ################################################################################
 %FAI::configs = ();
@@ -74,13 +123,53 @@
 #
 # The structure is as follows:
 # <DEVICE>
-#   TODO
+#   bios_cylinders
+#   bios_heads
+#   bios_sectors_per_track
+#   sector_size
+#   disklabel
+#   begin_byte
+#   end_byte
+#   partitions
+#     <1..n>
+#       begin_byte
+#       end_byte
+#       count_byte
+#       is_extended
+#       filesystem
 #
 ################################################################################
 %FAI::current_config = ();
 
 ################################################################################
 #
+# @brief The current LVM configuration
+#
+# The structure is as follows:
+# <VG>
+#   physical_volumes
+#   size
+#   volumes
+#     <lv-name>
+#       size
+#
+################################################################################
+%FAI::current_lvm_config = ();
+
+################################################################################
+#
+# @brief The current RAID configuration
+#
+# The structure is as follows:
+# <0..n>
+#   devices
+#   mode
+#
+################################################################################
+%FAI::current_raid_config = ();
+
+################################################################################
+#
 # @brief The list of commands to be executed
 #
 ################################################################################
@@ -90,12 +179,12 @@
 #
 # @brief A map of commands
 #
-# TODO check for availability, exact version?
+# checks for availability within $PATH are performed from within the parser
 #
 ################################################################################
 %FAI::system_commands = (
-  "mdadm"  => "/sbin/mdadm",
-  "parted" => "/sbin/parted -s"
+  "mdadm"  => "mdadm",
+  "parted" => "parted -s"
 );
 
 1;

Modified: people/michael/features/setup_harddisks_2/implementation/shdd2-lib
===================================================================
--- people/michael/features/setup_harddisks_2/implementation/shdd2-lib	2007-05-23 15:49:57 UTC (rev 4292)
+++ people/michael/features/setup_harddisks_2/implementation/shdd2-lib	2007-05-31 07:27:20 UTC (rev 4293)
@@ -90,8 +90,12 @@
   {
 
     # if there is another hash, recursively call the function with this hash
-    if ( ref( $hash_ref->{$key} ) )
+    if ( ref( $hash_ref->{$key} ) eq 'ARRAY' )
     {
+      print "$k KEY: $key VAL: " . join( " ", @{ $hash_ref->{$key} } ) . "\n";
+    }
+    elsif ( ref( $hash_ref->{$key} ) )
+    {
       print "$k KEY: $key VAL: hash\n";
       &print_hash( $hash_ref->{$key}, $k );
     }

Modified: people/michael/features/setup_harddisks_2/implementation/shdd2-parser
===================================================================
--- people/michael/features/setup_harddisks_2/implementation/shdd2-parser	2007-05-23 15:49:57 UTC (rev 4292)
+++ people/michael/features/setup_harddisks_2/implementation/shdd2-parser	2007-05-31 07:27:20 UTC (rev 4293)
@@ -50,12 +50,16 @@
 #            /* preserve partitions */
 #            | resize:[[:digit:]]+(,[[:digit:]]+)*
 #            /* attempt to resize partitions */
-#            | disklabel:(msdos|sun)
+#            | disklabel:(msdos|gpt)
 #            /* write a disklabel - default is msdos */
 #            | bootable:[[:digit:]]+
 #            /* mark a partition bootable, default is / */
 #            | virtual
 #            /* do not assume the disk to be a physical device, use with xen */
+#            | fstabkey:(device|label|uuid)
+#            /* when creating the fstab, the key used for defining the device
+#            may be the device (/dev/xxx), a label given using -L, or the uuid
+#            */
 #
 # volume ::= <type> <mountpoint> <size> <filesystem> <mount_options> <fs_options>
 #            | vg <name> <size>
@@ -145,8 +149,8 @@
   if ( $disk =~ /^\d+$/ )
   {
 
-    # $disk-1 must not be valid index in the map of all disks in the system
-    ( $FAI::disks < $disk )
+    # $disk-1 must be a valid index in the map of all disks in the system
+    ( scalar( @FAI::disks ) >= $disk )
       or die "this system does not have a physical disk $disk\n";
 
     # fetch the (short) device name
@@ -172,6 +176,7 @@
     "virtual"    => 0,
     "disklabel"  => "msdos",
     "bootable"   => -1,
+    "fstabkey"   => "device",
     "partitions" => {}
   };
 }
@@ -352,6 +357,7 @@
         {
           $return = 1;
         }
+        | <error>
 
     line: <skip: qr/[ \t]*/> "\\n"
         | <skip: qr/[ \t]*/> comment "\\n"
@@ -385,12 +391,18 @@
         }
         | /^disk(\d+)/
         {
+          # check, whether parted is available
+          ( &FAI::in_path( "parted" ) == 1 ) or 
+            die "parted not found in PATH\n";
           # initialise the entry of the hash corresponding to disk$1
           &FAI::init_disk_config( $1 );
         }
         option(s?)
         | /^\S+/
         {
+          # check, whether parted is available
+          ( &FAI::in_path( "parted" ) == 1 ) or 
+            die "parted not found in PATH\n";
           # initialise the entry of the hash corresponding to $item[1]
           &FAI::init_disk_config( $item[ 1 ] );
         }
@@ -406,23 +418,34 @@
           # set the resize flag for all ids
           $FAI::configs{ $FAI::device }{ "partitions" }{ $_ }{ "size" }{ "resize" } = 1 foreach ( split( ",", $1 ) );
         }
-        | /^disklabel:(msdos|sun|gpt|mac)/
+        | /^disklabel:(msdos|gpt)/
         {
           # set the disk label - actually not only the above, but all types 
-          # supported by parted could be allowed
+          # supported by parted could be allowed, but others are not implemented
+          # yet
           $FAI::configs{ $FAI::device }{ "disklabel" } = $1;
         }
         | /^bootable:(\d+)/
         {
           # specify a partition that should get the bootable flag set
           $FAI::configs{ $FAI::device }{ "bootable" } = $1;
+          ( $FAI::device =~ /^PHY_(.+)$/ ) or die 
+            "INTERNAL ERROR: unexpected device name\n";
+          # set the BOOT_DEVICE and BOOT_PARTITION variables
+          $FAI::disk_var{ "BOOT_DEVICE" } = $1; 
+          $FAI::disk_var{ "BOOT_PARTITION" } = $1 . 
+            $FAI::configs{ $FAI::device }{ "bootable" }; 
         }
         | 'virtual'
         {
           # this is a configuration for a virtual disk
-          # TODO not yet implemented
           $FAI::configs{ $FAI::device }{ "virtual" } = 1;
         }
+        | /^fstabkey:(device|label|uuid)/
+        {
+          # the information preferred for fstab device identifieres
+          $FAI::configs{ $FAI::device }{ "fstabkey" } = $1;
+        }
 
     volume: 'vg' name devices
         | /^raid([0156])\s+/
@@ -466,6 +489,9 @@
             die "Logical volume $2 has been defined already.\n";
           # initialise the new hash
           $FAI::configs{$FAI::device}{"volumes"}{$2} = {};
+          # initialise the preserve and resize flags
+          $FAI::configs{$FAI::device}{"volumes"}{$2}{"size"}{"preserve"} = 0;
+          $FAI::configs{$FAI::device}{"volumes"}{$2}{"size"}{"resize"} = 0;
           # set the reference to the current volume
           # the reference is used by all further processing of this config line
           $FAI::partition_pointer = ( \%FAI::configs )->{$FAI::device}->{"volumes"}->{$2};
@@ -479,28 +505,42 @@
         | 'swap'
         {
           # this partition is swap space, not mounted
-          $FAI::partition_pointer->{ "mountpoint" } = "swap";
+          $FAI::partition_pointer->{ "mountpoint" } = "none";
         }
         | m{^/\S*}
         {
           # set the mount point
           $FAI::partition_pointer->{ "mountpoint" } = $item[ 1 ];
+          # mark the bootable partition, if it is not set yet and this is a PHY_
+          # device
+          if ( $item[ 1 ] eq "/" && $FAI::device =~ /^PHY_(.+)$/ &&
+            $FAI::configs{$FAI::device}{"bootable"} == -1 )
+          {
+            $FAI::configs{$FAI::device}{"bootable"} =
+              $FAI::partition_pointer->{"number"};
+            # set the BOOT_DEVICE and BOOT_PARTITION variables
+            $FAI::disk_var{ "BOOT_DEVICE" } = $1; 
+            $FAI::disk_var{ "BOOT_PARTITION" } = $1 . 
+              $FAI::configs{ $FAI::device }{ "bootable" };
+          }
         }
 
-    name: m{^([^/\s\-]+)\s+}
+    name: m{^([^/\s\-]+)}
         {
-          # make sure this line is part of an LVM configuration
-          ( $FAI::device =~ /^VG_/ ) or
-            die "vg is invalid in a non LVM-context.\n";
           # set the device name to VG_ and the name of the volume group
           $FAI::device = "VG_$1";
           # make sure, the volume group $1 not has been defined already
           defined( $FAI::configs{$FAI::device} ) and
             die "Volume group $1 has been defined already.\n";
+          # make sure this line is part of an LVM configuration
+          ( $FAI::device =~ /^VG_/ ) or
+            die "vg is invalid in a non LVM-context.\n";
           # initialise the new hash
           $FAI::configs{$FAI::device}{"volumes"} = {};
           # initialise the list of physical devices
           $FAI::configs{$FAI::device}{"devices"} = ();
+          # the rule must not return undef
+          1;
         }
 
     size: /^(\d+%?(-(\d+%?)?)?)(:resize)?\s+/
@@ -533,18 +573,13 @@
         | /^preserve(\d+)\s+/
         {
           # set the preserve flag, if the index of the partition hasn't changed
-          if( $1 == $FAI::partition_pointer->{ "number" } )
-          {
-            $FAI::partition_pointer->{ "size" }->{ "preserve" } = 1;
-          }
-          else
-          {
-            die "partition number of to-be-preserved partition $1 changed to $FAI::partition_pointer->{number}\n";
-          }
+          ( $1 == $FAI::partition_pointer->{ "number" } ) or die
+            "Partition number of to-be-preserved partition $1 changed to $FAI::partition_pointer->{number}\n";
+          $FAI::partition_pointer->{ "size" }->{ "preserve" } = 1;
         }
         | <error: invalid partition size near "$text">
 
-    devices: /^([^\d,:\s\-][^,:\s]*(:(spare|missing))*(,[^,:\s]+(:(spare|missing))*)*)\s+/
+    devices: /^([^\d,:\s\-][^,:\s]*(:(spare|missing))*(,[^,:\s]+(:(spare|missing))*)*)/
         {
           # split the device list by ,
           foreach my $dev ( split( ",", $1 ) )
@@ -579,9 +614,11 @@
             }
             else
             {
-              push @{ $FAI::configs{$FAI::device}{"devices"} }, $dev;
+              # create an empty hash for each device
+              $FAI::configs{$FAI::device}{"devices"}{$dev} = {};
             }
           }
+          1;
         }
         | <error: invalid device spec "$text">
 
@@ -600,11 +637,12 @@
         }
         | /^\S+/
         {
-          ( &FAI::in_path("mkfs.$item[1]") == 1 ) or die "unknown/invalid filesystem type $item[1]";
+          ( &FAI::in_path("mkfs.$item[1]") == 1 ) or 
+            die "unknown/invalid filesystem type $item[1] (mkfs.$item[1] not found in PATH)\n";
           $FAI::partition_pointer->{ "filesystem" } = $item[ 1 ];
         }
 
-    fs_options: /.*/
+    fs_options: /[^;\n]*/
         {
           $FAI::partition_pointer->{ "fs_options" } = $item[ 1 ];
         }
@@ -613,21 +651,28 @@
 
 ################################################################################
 #
-# @brief Parse the data from <STDIN> using @ref $FAI::Parser
+# @brief Parse the data from <$IN> using @ref $FAI::Parser
 #
+# @param IN file handle for input file, may be STDIN
+#
 ################################################################################
 sub run_parser
 {
+  my ( $IN ) = @_;
 
-  # read <STDIN> to a single string (not a list), thus $/ has to be unset
+  # read <$IN> to a single string (not a list), thus $/ has to be unset
   my $ifs = $/;
   undef $/;
-  my $input = <STDIN>;
+  my $input = <$IN>;
   $/ = $ifs;
 
-  # print the contents of <STDIN> for debugging purposes
+  # print the contents of <$IN> for debugging purposes
   ( $FAI::debug > 0 ) and print "Input was:\n" . $input;
 
+  # check for old-style configuration files
+  ( $input =~ m{(^|\n)[^\n#]+;} ) and die 
+    "Old style configuration files are not supported\n";
+
   # attempt to parse $input - any error will lead to termination
   defined $FAI::Parser->file($input) or die "Syntax error\n";
 }

Modified: people/michael/features/setup_harddisks_2/implementation/shdd2-sizes
===================================================================
--- people/michael/features/setup_harddisks_2/implementation/shdd2-sizes	2007-05-23 15:49:57 UTC (rev 4292)
+++ people/michael/features/setup_harddisks_2/implementation/shdd2-sizes	2007-05-31 07:27:20 UTC (rev 4293)
@@ -24,8 +24,7 @@
 #
 # @file shdd2-sizes
 #
-# @brief A set of functions to obtain the current partition table and to
-# compute the size of the partitions to be created
+# @brief Compute the size of the partitions and volumes to be created
 #
 # $Id$
 #
@@ -40,293 +39,207 @@
 
 ################################################################################
 #
-# @brief Collect the current partition information from all disks listed both
-# in $FAI::disks and $FAI::configs{PHY_<disk>}
+# @brief Estimate the size of the device $dev
 #
+# @param $dev Device the size of which should be determined. This may be a
+# a partition, a RAID device or an entire disk.
+#
+# @return the size of the device in megabytes
+#
 ################################################################################
-sub get_current_disks
+sub estimate_size
 {
+  my ( $dev ) = @_;
 
-  # backup value of $ENV{"NO_DRY_RUN"}
-  my $no_dry_run = "";
-  defined( $ENV{"NO_DRY_RUN"} ) and $no_dry_run = $ENV{"NO_DRY_RUN"};
+  # try the entire disk first; we then use the data from the current
+  # configuration; this matches in fact for than the allowable strings, but
+  # this should be caught later on
+  if ( $dev =~ /^\/dev\/[sh]d[a-z]$/ )
+  {
+    defined( $FAI::current_config{$dev}{"end_byte"} ) or die 
+      "$dev is not a valid block device\n";
 
-  # obtain the current state of all disks
-  foreach my $disk (@FAI::disks)
+    # the size is known, return it
+    return ( $FAI::current_config{$dev}{"end_byte"} -
+      $FAI::current_config{$dev}{"begin_byte"} ) / ( 1024 * 1024 );
+  }
+  # try a partition
+  elsif ( $dev =~ /^(\/dev\/[sh]d[a-z])(\d+)$/ )
   {
+    # the size is configured, return it
+    defined( $FAI::configs{"PHY_$1"}{"partitions"}{$2}{"size"}{"eff_size"} ) and
+      return $FAI::configs{"PHY_$1"}{"partitions"}{$2}{"size"}{"eff_size"} / ( 1024 * 1024 );
+    # the size is known from the current configuration on disk, return it
+    defined( $FAI::current_config{$1}{"partitions"}{$2}{"count_byte"} ) and
+      return $FAI::current_config{$1}{"partitions"}{$2}{"count_byte"} / ( 1024 * 1024 );
+    # the size is not known (yet?)
+    die "Cannot determine size of $dev\n";
+  }
+  # try RAID; estimations here are very limited and possible imprecise
+  elsif ( $dev =~ /^\/dev\/md(\d+)$/ )
+  {
+    # the list of underlying devices
+    my @devs = ();
+    # the raid level, like raid0, raid5, linear, etc.
+    my $level = "";
 
-    # create full paths
-    ( $disk =~ m{^/} ) or $disk = "/dev/$disk";
+    # let's see, whether there is a configuration of this volume
+    if ( defined( $FAI::configs{"RAID"}{"volumes"}{$1}{"devices"} ) )
+    {
+      @devs = keys %{ $FAI::configs{"RAID"}{"volumes"}{$1}{"devices"} };
+      $level = $FAI::configs{"RAID"}{"volumes"}{$1}{"mode"};
+    }
+    elsif( defined( $FAI::current_raid_config{$1}{"devices"} ) )
+    {
+      @devs = $FAI::current_raid_config{$1}{"devices"};
+      $level = $FAI::current_raid_config{$1}{"mode"};
+    }
+    else
+    {
+      die "$dev is not a known RAID device\n";
+    }
+    
+    # prepend "raid", if the mode is numeric-only
+    $level = "raid" . $level if ( $level =~ /^\d+$/ );
+    
+    # the number of devices in the volume
+    my $dev_count = scalar( @devs );
 
-    # make sure, $disk is a proper block device
-    ( -b $disk ) or die "$disk is not a block special device!\n";
-
-    # skip this disk, if it is not listed in $FAI::configs
-    defined( $FAI::configs{"PHY_$disk"} ) or next;
-
-    # initialise the hash
-    $FAI::current_config{$disk}{"partitions"} = {};
-
-    # the list to hold the output of parted commands as parsed below
-    my @parted_print = ();
-
-    # set NO_DRY_RUN to perform read-only commands always
-    $ENV{"NO_DRY_RUN"} = "1";
-
-    # try to obtain the partition table for $disk
-    # it might fail with parted_2 in case the disk has no partition table
-    my $error =
-      &FAI::execute_command(
-      $FAI::system_commands{"parted"} . " $disk unit TiB print",
-      \@parted_print, 0 );
-
-    # reset NO_DRY_RUN
-    $ENV{"NO_DRY_RUN"} = $no_dry_run;
-
-    # parted_2 happens when the disk has no disk label, because parted then
-    # provides no information about the disk
-    if ( $error eq "parted_2" )
+    # now do the mode-specific size estimations
+    if ( $level =~ /^raid[015]$/ )
     {
+      my $min_size = &estimate_size( shift @devs );
+      foreach ( @devs )
+      {
+        my $s = &estimate_size( $_ );
+        $min_size = $s if ( $s < $min_size );
+      }
 
-      # write the disk label as configured
-      $error =
-        &FAI::execute_command( $FAI::system_commands{"parted"}
-          . " $disk mklabel "
-          . $FAI::configs{$disk}{"disklabel"} );
-
-      # set NO_DRY_RUN to perform read-only commands always
-      $ENV{"NO_DRY_RUN"} = "1";
-
-      # retry partition-table print
-      $error =
-        &FAI::execute_command(
-        $FAI::system_commands{"parted"} . " $disk unit TiB print",
-        \@parted_print, 0 );
-
-      # reset NO_DRY_RUN
-      $ENV{"NO_DRY_RUN"} = $no_dry_run;
+      return $min_size * POSIX::floor( $dev_count / 2 ) if ( $level eq "raid1" );
+      return $min_size * $dev_count if ( $level eq "raid0" );
+      return $min_size * ( $dev_count - 1 ) if ( $level eq "raid5" );
     }
-
-    # check, whether there is still an error
-    if ( $error ne "" )
+    else
     {
-      my $response = &FAI::get_error( $error, "response" );
-      ( $response eq "die" ) and die &FAI::get_error( $error, "message" );
-      ( $response eq "warn" ) and warn &FAI::get_error( $error, "message" );
+      # probably some more should be implemented
+      die "Don't know how to estimate the size of a $level device\n";
     }
+  }
+  # otherwise we are clueless
+  else
+  {
+    die "Cannot determine size of $dev\n";
+  }
+}
 
-# the following code parses the output of parted print, using various units
-# (TiB, B, chs)
-# the parser is capable of reading the output of parted version 1.7.1, which
-# looks like
+################################################################################
 #
-# $ /sbin/parted -s /dev/hda unit B print
-# WARNING: You are not superuser.  Watch out for permissions.
+# @brief Compute the desired sizes of logical volumes
 #
-# Disk /dev/hda: 80026361855B
-# Sector size (logical/physical): 512B/512B
-# Partition Table: mac
-#
-# Number  Start         End           Size          File system  Name     Flags
-#  1      512B          32767B        32256B                     primary
-#  5      32768B        1033215B      1000448B      hfs          primary  boot
-#  3      134250496B    32212287487B  32078036992B  hfs+         primary
-#  6      32212287488B  46212287487B  14000000000B  ext3         primary
-#  2      46212287488B  47212287999B  1000000512B   linux-swap   primary  swap
-#  4      47212288000B  80026361855B  32814073856B  ext3         primary
-#
-# Note that the output contains an additional column on msdos, indicating,
-# whether the type of a partition is primary, logical or extended.
-#
-# $ parted -s /dev/hda unit B print
-#
-# Disk /dev/hda: 82348277759B
-# Sector size (logical/physical): 512B/512B
-# Partition Table: msdos
-#
-# Number  Start         End           Size          Type      File system  Flags
-#  1      32256B        24675839B     24643584B     primary   ext3
-#  2      24675840B     1077511679B   1052835840B   primary   linux-swap
-#  3      1077511680B   13662190079B  12584678400B  primary   ext3         boot
-#  4      13662190080B  82343278079B  68681088000B  extended
-#  5      13662222336B  14715025919B  1052803584B   logical   ext3
-#  6      14715058176B  30449986559B  15734928384B  logical   ext3
-#  7      30450018816B  32547432959B  2097414144B   logical   ext3
-#  8      32547465216B  82343278079B  49795812864B  logical   ext3
-#
+################################################################################
+sub compute_lv_sizes
+{
+  # loop through all device configurations
+  foreach my $config ( keys %FAI::configs )
+  {
+    # for RAID, there is nothing to be done here
+    next if ( $config eq "RAID" );
+    # device is an effective disk
+    next if ( $config =~ /^PHY_(.+)$/ );
+    
+    # configure a volume group
+    ( $config =~ /^VG_(.+)$/ ) or die
+      "INTERNAL ERROR: invalid config entry $config.\n";
+    
+    # the volume group name
+    my $vg = $1;
+    
+    # compute the size of the volume group; this is not exact, but should at
+    # least give a rough estimation, we assume 1 % of overhead; the value is
+    # stored in megabytes
+    my $vg_size = 0;
+    foreach my $dev ( keys %{ $FAI::configs{$config}{"devices"} } )
+    {
+      # $dev may be a partition, an entire disk or a RAID device; otherwise we
+      # cannot deal with it
+      $vg_size += &estimate_size( $dev );
+    }
+    # now subtract 1% of overhead
+    $vg_size *= 0.99;
 
-    # As shown above, the file system might be blank, if its type is not known
-    # to parted. Thus the exact columns of "File system" have to be extracted
-    # These two variables keep the indices
-    my $parted_fs_before = 0;
-    my $parted_fs_len    = 0;
+    # the volumes that require redistribution of free space
+    my @redist_list = ();
 
-    # Parse the output line by line
-    foreach my $line (@parted_print)
+    # the minimum space required in this volume group
+    my $min_space = 0;
+      
+    # the maximum space used in this volume group
+    my $max_space = 0;
+      
+    # set effective sizes where available
+    foreach
+      my $lv ( keys %{ $FAI::configs{$config}{"volumes"} } )
     {
+      # make sure the size specification is a range (even though it might be
+      # something like x-x) and store the dimensions
+      ( $FAI::configs{$config}{"volumes"}{$lv}{"size"}{"range"} =~
+          /^(\d+%?)-(\d+%?)$/ )
+        or die "INTERNAL ERROR: Invalid range\n";
+      my $start = $1;
+      my $end   = $2;
 
-      # print the line read - for debugging purposes only
-      ( $FAI::debug > 0 ) and print "$line";
+      # start may be given in percents of the size, rewrite it to megabytes
+      $start = POSIX::floor( $vg_size * $1 / 100 ) if ( $start =~ /^(\d+)%$/);
 
-      # now we test line by line - some of them may be ignored
-      if ( $line =~ /^Disk /
-        || $line =~ /^\s*$/
-        || $line =~ /^WARNING: You are not superuser/
-        || $line =~ /^Sector / )
-      {
-        next;
-      }
+      # end may be given in percents of the size, rewrite it to megabytes
+      $end = POSIX::ceil( $vg_size * $1 / 100 ) if ( $end =~ /^(\d+)%$/);
 
-      # read and store the current disk label
-      elsif ( $line =~ /^Partition Table: (.*)$/ )
-      {
-        $FAI::current_config{$disk}{"disklabel"} = $1;
-      }
+      # make sure that $end >= $start
+      ( $end >= $start ) or die "INTERNAL ERROR: end < start\n";
 
-      # the line containing the table headers
-      elsif ( $line =~ /^(Number.*\s+)(File system\s+)\S+/ )
+      # increase the used space
+      $min_space += $start;
+      $max_space += $end;
+      
+      # write back the range in MB
+      $FAI::configs{$config}{"volumes"}{$lv}{"size"}{"range"} = "$start-$end";
+      
+      # the size is fixed
+      if ( $start == $end )
       {
-
-        # the number of characters before File system
-        $parted_fs_before = length($1) + 1;
-
-        # the length of the File system column
-        $parted_fs_len = length($2);
+        # write the size back to the configuration
+        $FAI::configs{$config}{"volumes"}{$lv}{"size"}{"eff_size"} = $start;
       }
-
-      # one of the partitions
       else
       {
-
-        # we must have seen the header, otherwise probably the format has
-        # changed
-        ( $parted_fs_len > 0 )
-          or die "INTERNAL ERROR: Table header not seen yet\n";
-
-        # get the partition number
-        $line =~ /^\s*(\d+)/;
-        my $id = $1;
-
-        # extract the set of characters
-        $line =~ /^.{$parted_fs_before}(.{$parted_fs_len})/;
-        my $fs = $1;
-
-        # remove any trailing space
-        $fs =~ s/\s*$//g;
-
-        # store the information in the hash
-        $FAI::current_config{$disk}{"partitions"}{$id}{"filesystem"} = $fs;
+        # add this volume to the redistribution list
+        push @redist_list, $lv;
       }
     }
 
-    # set NO_DRY_RUN to perform read-only commands always
-    $ENV{"NO_DRY_RUN"} = "1";
+    # test, whether the configuration fits on the volume group at all
+    ( $min_space < $vg_size ) or die 
+      "Volume group $vg requires $min_space MB\n";
 
-    # reset the output list
-    @parted_print = ();
+    # the extension factor
+    my $redist_factor = 0;
+    $redist_factor = ( $vg_size - $min_space ) / ( $max_space - $min_space ) if
+      ( $max_space > $min_space );
 
-    # obtain the partition table using bytes as units
-    # TODO: when to use _std, when should one use execute_command
-    my $error =
-      &FAI::execute_command_std(
-      "$FAI::system_commands{'parted'}} $disk unit B print",
-      \@parted_print, 0 );
-
-    # reset NO_DRY_RUN
-    $ENV{"NO_DRY_RUN"} = $no_dry_run;
-
-    # check, whether an error has occured
-    # TODO: is this necessary?
-    if ( $error ne "" )
+    # update all sizes that are still ranges
+    foreach my $lv ( @redist_list )
     {
-      my $response = &FAI::get_error( $error, "response" );
-      ( $response eq "die" ) and die &FAI::get_error( $error, "message" );
-      ( $response eq "warn" ) and warn &FAI::get_error( $error, "message" );
-    }
+      # get the range again
+      ( $FAI::configs{$config}{"volumes"}{$lv}{"size"}{"range"} =~
+          /^(\d+%?)-(\d+%?)$/ )
+        or die "INTERNAL ERROR: Invalid range\n";
+      my $start = $1;
+      my $end   = $2;
 
-    # Parse the output of the byte-wise partition table
-    foreach my $line (@parted_print)
-    {
-
-      # The size of the disk
-      if ( $line =~ /^Disk .*: (\d+)B$/i )
-      {
-        $FAI::current_config{$disk}{"begin_byte"} = 0;
-        $FAI::current_config{$disk}{"end_byte"}   = $1;
-      }
-
-      # One of the partition lines, see above example
-      next
-        unless ( $line =~
-        /^\s*(\d+)*\s+(\d+)B\s+(\d+)B\s+(\d+)B(\s+(primary|logical|extended))?/i
-        );
-
-      # set the corresponding entries
-      $FAI::current_config{$disk}{"partitions"}{$1}{"begin_byte"} = $2;
-      $FAI::current_config{$disk}{"partitions"}{$1}{"end_byte"}   = $3;
-      $FAI::current_config{$disk}{"partitions"}{$1}{"count_byte"} = $4;
-
-      # is_extended defaults to false/0
-      $FAI::current_config{$disk}{"partitions"}{$1}{"is_extended"} = 0;
-
-      # but may be true/1 on msdos disk labels
-      (      ( $FAI::current_config{$disk}{"disklabel"} eq "msdos" )
-          && ( $6 eq "extended" ) )
-        and $FAI::current_config{$disk}{"partitions"}{$1}{"is_extended"} = 1;
+      # write the final size
+      $FAI::configs{$config}{"volumes"}{$lv}{"size"}{"eff_size"} = $start + ( (
+        $end - $start ) * $redist_factor );
     }
-
-    # set NO_DRY_RUN to perform read-only commands always
-    $ENV{"NO_DRY_RUN"} = "1";
-
-    # reset the output list
-    @parted_print = ();
-
-    # obtain the partition table using bytes as units
-    # TODO: when to use _std, when should one use execute_command
-    my $error =
-      &FAI::execute_command_std(
-      "$FAI::system_commands{'parted'}} $disk unit chs print",
-      \@parted_print, 0 );
-
-    # reset NO_DRY_RUN
-    $ENV{"NO_DRY_RUN"} = $no_dry_run;
-
-    # check, whether an error has occured
-    # TODO: is this necessary?
-    if ( $error ne "" )
-    {
-      my $response = &FAI::get_error( $error, "response" );
-      ( $response eq "die" ) and die &FAI::get_error( $error, "message" );
-      ( $response eq "warn" ) and warn &FAI::get_error( $error, "message" );
-    }
-
-    # Parse the output of the CHS partition table
-    foreach my $line (@parted_print)
-    {
-
-      # The partition geometry
-      if ( $line =~ /^\s*(\d+)\s+(\d+),(\d+),(\d+)\s+(\d+),(\d+),(\d+)/i )
-      {
-        $FAI::current_config{$disk}{"partitions"}{$1}{"begin_cylinder"} = $2;
-        $FAI::current_config{$disk}{"partitions"}{$1}{"begin_head"}     = $3;
-        $FAI::current_config{$disk}{"partitions"}{$1}{"begin_sector"}   = $4;
-        $FAI::current_config{$disk}{"partitions"}{$1}{"end_cylinder"}   = $5;
-        $FAI::current_config{$disk}{"partitions"}{$1}{"end_head"}       = $6;
-        $FAI::current_config{$disk}{"partitions"}{$1}{"end_sector"}     = $7;
-      }
-
-      # The disk geometry
-      if ( $line =~ /^Disk .*: (\d+),(\d+),(\d+)$/i )
-      {
-        $FAI::current_config{$disk}{"begin_cylinder"} = 0;
-        $FAI::current_config{$disk}{"begin_head"}     = 0;
-        $FAI::current_config{$disk}{"begin_sector"}   = 0;
-        $FAI::current_config{$disk}{"end_cylinder"}   = $1;
-        $FAI::current_config{$disk}{"end_head"}       = $2;
-        $FAI::current_config{$disk}{"end_sector"}     = $3;
-      }
-    }
-
   }
 }
 
@@ -336,306 +249,472 @@
 # thereof.
 #
 ################################################################################
-sub compute_sizes
+sub compute_partition_sizes
 {
 
   # loop through all device configurations
   foreach my $config ( keys %FAI::configs )
   {
-    if ( $config eq "RAID" || $config =~ /^VG_/ )
-    {
+    # for RAID, there is nothing to be done here
+    next if ( $config eq "RAID" );
+    # don't configure the sizes of logical volumes here
+    next if ( $config =~ /^VG_(.+)$/ );
 
-      # TODO compute the sizes of lvms, RAIDs
-      next;
-    }
+    # device is an effective disk
+    ( $config =~ /^PHY_(.+)$/ ) or die 
+      "INTERNAL ERROR: invalid config entry $config.\n";
+      
+    # nothing to be done, if this is a configuration for a virtual disk
+    next if ( $FAI::configs{$config}{"virtual"} == 1 );
 
-    # device is an effective disk
-    elsif ( $config =~ /^PHY_(.*)$/ )
+    # the device name of the disk
+    my $disk = $1;
+    
+    # at various points the following code highly depends on the desired disk label!
+    # initialise variables
+    # the id of the extended partition to be created, if required
+    my $extended = -1;
+
+    # the id of the current extended partition, if any; this setup only caters
+    # for a single existing extended partition!
+    my $current_extended = -1;
+    
+    # find the first existing extended partition
+    foreach
+      my $part_id ( sort keys %{ $FAI::current_config{$disk}{"partitions"} } )
     {
+      if ( 1 == $FAI::current_config{$disk}{"partitions"}{$part_id}{"is_extended"} )
+      {
+        $current_extended = $part_id;
+        last;
+      }
+    }
 
-      # initialise variables
-      # the id of the extended partition to be created, if required
-      my $extended = -1;
+    # the space required on the disk
+    my $min_req_total_space = 0;
 
-      # the device name of the disk
-      my $disk = $1;
+    # the start byte for the next partition
+    my $next_start = 0;
+    
+    # on msdos disk labels, the first partitions starts at head #1
+    if ( $FAI::configs{$config}{"disklabel"} eq "msdos" )
+    {
+      $next_start = $FAI::current_config{$disk}{"bios_sectors_per_track"} *
+        $FAI::current_config{$disk}{"sector_size"};
 
-      # remaining free space is to be shared by those partitions whose size is a
-      # range; $redist_space keeps track of the maximum space to be
-      # redistributed
-      my $redist_space = 0;
+      # the MBR requires space, too
+      $min_req_total_space += $FAI::current_config{$disk}{"bios_sectors_per_track"} *
+        $FAI::current_config{$disk}{"sector_size"};
+    }
 
-      # minimum space required by all partitions, i.e., the lower ends of the
-      # ranges
-      # $min_req_space counts up to the next preserved partition
-      my $min_req_space = 0;
+    # on GPT disk labels the first 34 and last 34 sectors must be left alone
+    if ( $FAI::configs{$config}{"disklabel"} eq "gpt" )
+    {
+      $next_start = 34 * $FAI::current_config{$disk}{"sector_size"};
+      
+      # modify the disk to claim the space for the second partition table
+      $FAI::current_config{$disk}{"end_byte"} -= 34 *
+        $FAI::current_config{$disk}{"sector_size"};
+      
+      # the space required by the GPTs  
+      $min_req_total_space += 2 * 34 * $FAI::current_config{$disk}{"sector_size"};
+    }
 
-      # $min_req_total_space counts for the entire disk
-      my $min_req_total_space = 0;
+    # the list of partitions that we need to find start and end bytes for
+    my @worklist = ( sort keys %{ $FAI::configs{$config}{"partitions"} } );
 
-      # list of partition ids that require redistribution
-      my @redist_list = ();
+    while ( scalar( @worklist ) > 0 )
+    {
+      # work on the first entry of the list
+      my $part_id = $worklist[ 0 ];
 
-      # the start of a range of non-preserved disk space
-      my $range_start = 0;
-
-      # loop through all configured partitions in a sorted manner
-      foreach
-        my $part_id ( sort keys %{ $FAI::configs{$config}{"partitions"} } )
+      # the partition $part_id must be preserved
+      if (
+        $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"preserve"} ==
+        1 )
       {
 
-        # find/handle the extended partition, if any
-        if (
-          $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"extended"} ==
-          1 )
-        {
+        # a partition that should be preserved must exist already
+        defined( $FAI::current_config{$disk}{"partitions"}{$part_id} )
+          or die "$part_id can't be preserved, it does not exist.\n";
 
-          # make sure that there is only one extended partition
-          ( $extended == -1 )
-            or die "INTERNAL ERROR: More than 1 extended partition\n";
+        ( $next_start >
+          $FAI::current_config{$disk}{"partitions"}{$part_id}{"begin_byte"} )
+          and die 
+          "Previous partitions overflow begin of preserved partition $part_id\n";
 
-          # ensure that it is a primary partition
-          ( $part_id <= 4 )
-            or die
-            "INTERNAL ERROR: Extended partition wouldn't be a primary one\n";
+        # set the effective size to the value known already
+        $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"eff_size"} =
+          $FAI::current_config{$disk}{"partitions"}{$part_id}{"count_byte"};
 
-          # set the local variable to this id
-          $extended = $part_id;
+        # copy the start_byte and end_byte information
+        $FAI::configs{$config}{"partitions"}{$part_id}{"start_byte"} =
+          $FAI::current_config{$disk}{"partitions"}{$part_id}{"begin_byte"};
+        $FAI::configs{$config}{"partitions"}{$part_id}{"end_byte"} =
+          $FAI::current_config{$disk}{"partitions"}{$part_id}{"end_byte"};
 
-          # initialise the size of the extended partition to 0
-          $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"eff_size"} =
-            0;
-        }
+        # and add it to the total disk space required by this config
+        $min_req_total_space +=
+          $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"eff_size"};
 
-        # the partition $pard_id need not be preserved
-        elsif (
-          $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"preserve"} ==
-          0 )
+        # set the next start
+        $next_start =
+          $FAI::configs{$config}{"partitions"}{$part_id}{"end_byte"} + 1;
+        
+        # several msdos specific parts
+        if ( $FAI::configs{$config}{"disklabel"} eq "msdos" )
         {
 
-          # make sure the size specification is a range (even though it might be
-          # something like x-x) and store the dimensions
-          ( $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"range"} =~
-              /^(\d+%?)-(\d+%?)$/ )
-            or die "INTERNAL ERROR: Invalid range\n";
-          my $start = $1;
-          my $end   = $2;
+          # make sure the partition ends at a cylinder boundary
+          ( 0 == ( $FAI::current_config{$disk}{"partitions"}{$part_id}{"end_byte"} + 1 ) %
+            ( $FAI::current_config{$disk}{"sector_size"} *
+              $FAI::current_config{$disk}{"bios_sectors_per_track"} *
+              $FAI::current_config{$disk}{"bios_heads"} ) ) or die
+            "Preserved partition $part_id does not end at a cylinder boundary\n";
 
-          # start may be given in is percents of the size
-          if ( $start =~ /^(\d+)%$/ )
-          {
+          # add one head of disk usage if this is a logical partition
+          $min_req_total_space += $FAI::current_config{$disk}{"bios_sectors_per_track"} *
+            $FAI::current_config{$disk}{"sector_size"} if ( $part_id > 4);
 
-            # rewrite it to bytes
-            $start =
-              POSIX::floor(
-              $FAI::current_config{$disk}{"end_byte"} * $1 / 100 );
-          }
-          else
+          # extended partitions consume no space
+          if (
+            $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"extended"} ==
+            1 )
           {
+            # revert the addition of the size
+            $min_req_total_space -=
+              $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"eff_size"};
 
-            # it is given in megabytes, make it bytes
-            $start = $start * 1024.0 * 1024.0;
+            # set the next start to the start of the extended partition
+            $next_start =
+              $FAI::configs{$config}{"partitions"}{$part_id}{"start_byte"};
           }
 
-          # end may be given in is percents of the size
-          if ( $end =~ /^(\d+)%$/ )
-          {
+        } 
+        
+        # on gpt, ensure that the partition ends at a sector boundary
+        if ( $FAI::configs{$config}{"disklabel"} eq "gpt" )
+        {
+          ( 0 == ( $FAI::current_config{$disk}{"partitions"}{$part_id}{"end_byte"} + 1 ) %
+            $FAI::current_config{$disk}{"sector_size"} ) or die
+            "Preserved partition $part_id does not end at a sector boundary\n";
+        }
 
-            # rewrite it to bytes
-            $end =
-              POSIX::ceil( $FAI::current_config{$disk}{"end_byte"} * $1 / 100 );
-          }
-          else
-          {
+        # partition done
+        shift @worklist;
+      }
+      # msdos specific: deal with extended partitions
+      elsif (
+        $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"extended"} ==
+        1 )
+      {
+        ( $FAI::configs{$config}{"disklabel"} eq "msdos" ) or die 
+          "found an extended partition on a non-msdos disklabel\n";
 
-            # it is given in megabytes, make it bytes
-            $end = $end * 1024.0 * 1024.0;
-          }
+        # make sure that there is only one extended partition
+        ( $extended == -1 || 1 == scalar( @worklist ) )
+          or die "INTERNAL ERROR: More than 1 extended partition\n";
 
-          # write back the size spec in bytes
-          $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"range"} =
-            $start . "-" . $end;
+        # ensure that it is a primary partition
+        ( $part_id <= 4 ) or die
+          "INTERNAL ERROR: Extended partition wouldn't be a primary one\n";
 
-          # check, whether the size is fixed
-          if ( $end == $start )
+        # set the local variable to this id
+        $extended = $part_id;
+
+        # the size cannot be determined now, push it to the end of the
+        # worklist; the check against $extended being == -1 ensures that
+        # there is no indefinite loop
+        if( scalar( @worklist ) > 1 )
+        {
+          push @worklist, shift @worklist;
+        }
+        # determine the size of the extended partition
+        else
+        {
+          my $epbr_size = $FAI::current_config{$disk}{"bios_sectors_per_track"} *
+            $FAI::current_config{$disk}{"sector_size"};
+        
+          # initialise the size and the start byte
+          $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"eff_size"} = 0;
+          $FAI::configs{$config}{"partitions"}{$part_id}{"start_byte"} = -1;
+
+          foreach my $p ( sort keys %{ $FAI::configs{$config}{"partitions"} } )
           {
+            next if ( $p < 5 );
 
-            # then set eff_size to a proper value
-            $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"eff_size"} =
-              $start;
+            if ( -1 ==
+              $FAI::configs{$config}{"partitions"}{$part_id}{"start_byte"} )
+            {
+              $FAI::configs{$config}{"partitions"}{$part_id}{"start_byte"} =
+                $FAI::configs{$config}{"partitions"}{$p}{"start_byte"} -
+                $epbr_size;
+            }
+          
+            $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"eff_size"} +=
+              $FAI::configs{$config}{"partitions"}{$p}{"size"}{"eff_size"} +
+              $epbr_size;
+            
+            $FAI::configs{$config}{"partitions"}{$part_id}{"end_byte"} =
+              $FAI::configs{$config}{"partitions"}{$p}{"end_byte"};
           }
-          else
-          {
+          
+          ( $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"eff_size"}
+            > 0 ) or die
+            "Extended partition has a size of 0\n";
+          
+          # partition done
+          shift @worklist;
+        }
+      }
+      else
+      {
+        # make sure the size specification is a range (even though it might be
+        # something like x-x) and store the dimensions
+        ( $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"range"} =~
+            /^(\d+%?)-(\d+%?)$/ )
+          or die "INTERNAL ERROR: Invalid range\n";
+        my $start = $1;
+        my $end   = $2;
 
-            # make sure that $end > $start
-            ( $end > $start ) or die "INTERNAL ERROR: end < start\n";
+        # start may be given in percents of the size
+        if ( $start =~ /^(\d+)%$/ )
+        {
 
-            # effective size is not yet known, use -1 to indicate this
-            $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"eff_size"} =
-              -1;
+          # rewrite it to bytes
+          $start = POSIX::floor(
+            $FAI::current_config{$disk}{"size"} * $1 / 100 );
+        }
+        else
+        {
 
-            # add the valid range to the space to be redistributed among the
-            # non-fixed sizes
-            $redist_space += $end - $start;
+          # it is given in megabytes, make it bytes
+          $start = $start * 1024.0 * 1024.0;
+        }
 
-            # add this $part_id to the redistribution list
-            push @redist_list, $part_id;
-          }
+        # end may be given in percents of the size
+        if ( $end =~ /^(\d+)%$/ )
+        {
 
-          # add the minimum size to the required space (locally)
-          $min_req_space += $start;
+          # rewrite it to bytes
+          $end =
+            POSIX::ceil( $FAI::current_config{$disk}{"size"} * $1 / 100 );
+        }
+        else
+        {
 
-          # add the minimum size to the required space (entire disk)
-          $min_req_total_space += $start;
+          # it is given in megabytes, make it bytes
+          $end = $end * 1024.0 * 1024.0;
         }
+          
+        # make sure that $end >= $start
+        ( $end >= $start ) or die "INTERNAL ERROR: end < start\n";
 
-        # partition must be preserved
-        else
+        # check, whether the size is fixed
+        if ( $end != $start )
         {
+          # the end of the current range (may be the end of the disk or some
+          # preserved partition
+          my $end_of_range = -1;
 
-          # a partition that should be preserved must exist already
-          defined( $FAI::current_config{$disk}{"partitions"}{$part_id} )
-            or die "$part_id can't be preserved, it does not exist.\n";
+          # minimum space required by all partitions, i.e., the lower ends of the
+          # ranges
+          # $min_req_space counts up to the next preserved partition or the
+          # end of the disk
+          my $min_req_space = 0;
 
-          # set the effective size to the value known already
-          $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"eff_size"} =
-            $FAI::current_config{$disk}{"partitions"}{$part_id}{"count_byte"};
+          # maximum useful space
+          my $max_space = 0;
 
-          # and add it to the total disk space required by this config
-          $min_req_total_space +=
-            $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"eff_size"};
-
-          # check, whether there are partitions that require redistribution
-          if ( scalar(@redist_list) > 0 )
+          # inspect all remaining entries in the worklist
+          foreach my $p ( @worklist )
           {
+            # we have found the delimiter
+            if (
+              $FAI::configs{$config}{"partitions"}{$p}{"size"}{"preserve"} ==
+              1 )
+            {
+              $end_of_range = 
+                $FAI::current_config{$disk}{"partitions"}{$p}{"begin_byte"};
+              # logical partitions require the space for the EPBR to be left
+              # out
+              if ( ( $FAI::configs{$config}{"disklabel"} eq "msdos" ) && 
+                ( $p > 4 ) )
+              {
+                $end_of_range -= $FAI::current_config{$disk}{"bios_sectors_per_track"} *
+                  $FAI::current_config{$disk}{"sector_size"};
+              }
+              last;
+            }
+            elsif ( 
+              $FAI::configs{$config}{"partitions"}{$p}{"size"}{"extended"} == 1 )
+            {
+              next;
+            }
+            else
+            {
+              # below is a slight duplication of the code
+              # make sure the size specification is a range (even though it might be
+              # something like x-x) and store the dimensions
+              ( $FAI::configs{$config}{"partitions"}{$p}{"size"}{"range"} =~
+                  /^(\d+%?)-(\d+%?)$/ )
+                or die "INTERNAL ERROR: Invalid range\n";
+              my $min_size = $1;
+              my $max_size = $2;
 
-            # compute the available space and the resulting multiplier of all
-            # ranges
-            my $free_space =
-              $FAI::current_config{$disk}{"partitions"}{$part_id}
-              {"begin_byte"} - 1 - $range_start - $min_req_space;
-            ( $free_space >= 0 ) or die "INTERNAL ERROR: negative free space\n";
+              # start may be given in percents of the size
+              if ( $min_size =~ /^(\d+)%$/ )
+              {
 
-         # the multiplier for the amount added to the lower bounds of the ranges
-            my $redist_factor = 1.0;
-            ( $free_space < $redist_space )
-              and $redist_factor = $free_space / $redist_space;
-            ( $FAI::debug > 0 ) and print "redist factor is $redist_factor\n";
+                # rewrite it to bytes
+                $min_size = POSIX::floor(
+                  $FAI::current_config{$disk}{"size"} * $1 / 100 );
+              }
+              else
+              {
 
-            # redistribute the free space
-            foreach my $part (@redist_list)
-            {
+                # it is given in megabytes, make it bytes
+                $min_size *= 1024.0 * 1024.0;
+              }
+              
+              # end may be given in percents of the size
+              if ( $max_size =~ /^(\d+)%$/ )
+              {
 
-              # make sure the entry requires redistribution
-              ( $FAI::configs{$config}{"partitions"}{$part}{"size"}
-                  {"eff_size"} == -1 )
-                or die "INTERNAL ERROR: invalid entry in redist_list\n";
+                # rewrite it to bytes
+                $max_size =
+                  POSIX::ceil( $FAI::current_config{$disk}{"size"} * $1 / 100 );
+              }
+              else
+              {
 
-              # re-check that the size is indeed a range and obtain the start
-              # and end-points
-              ( $FAI::configs{$config}{"partitions"}{$part}{"size"}{"range"} =~
-                  /^(\d+%?)-(\d+%?)$/ )
-                or die "INTERNAL ERROR: invalid range spec\n";
+                # it is given in megabytes, make it bytes
+                $max_size *= 1024.0 * 1024.0;
+              }
 
-              # store the start and end
-              my $start = $1;
-              my $end   = $2;
+              # logical partitions require the space for the EPBR to be left
+              # out
+              if ( ( $FAI::configs{$config}{"disklabel"} eq "msdos" ) && 
+                ( $p > 4 ) )
+              {
+                $min_size += $FAI::current_config{$disk}{"bios_sectors_per_track"} *
+                  $FAI::current_config{$disk}{"sector_size"};
+                $max_size += $FAI::current_config{$disk}{"bios_sectors_per_track"} *
+                  $FAI::current_config{$disk}{"sector_size"};
+              }
 
-              # set the new effective size according to $redist_factor
-              $FAI::configs{$config}{"partitions"}{$part_id}{"size"}
-                {"eff_size"} =
-                POSIX::floor( $start + ( ( $end - $start ) * $redist_factor ) );
+              $min_req_space += $min_size;
+              $max_space += $max_size;
             }
           }
 
-          # empty $redist_list;
-          @redist_list = ();
+          # set the end if we have reached the end of the disk
+          $end_of_range = $FAI::current_config{$disk}{"end_byte"} if ( -1 ==
+            $end_of_range );
 
-          # set the range start past the preserved partition
-          $range_start =
-            $FAI::current_config{$disk}{"partitions"}{$part_id}{"end_byte"} + 1;
+          my $available_space = $end_of_range - $next_start + 1;
 
-          # reset $redist space and $min_req_space to 0
-          $redist_space  = 0;
-          $min_req_space = 0;
+          # the next boundary is closer than the minimal space that we need
+          ( $available_space < $min_req_space ) and die 
+            "Insufficient space available for partition $part_id\n";
+          
+          # the new size
+          my $scaled_size = $end;
+          $scaled_size = POSIX::floor( ( $end - $start ) * ( ( $available_space -
+                $min_req_space ) / ( $max_space - $min_req_space ) ) ) +
+                $start if ( $max_space > $available_space );
 
+          ( $scaled_size >= $start ) or die 
+            "INTERNAL ERROR: scaled size is smaller than the desired minimum\n";
+
+          $start = $scaled_size;
+          $end = $start;
         }
-      }
+        
+        # now we compute the effective locations on the disk
+        # msdos specific offset for logical partitions
+        if ( ( $FAI::configs{$config}{"disklabel"} eq "msdos" ) && 
+          ( $part_id > 4 ) )
+        {
+          # add one head of disk usage if this is a logical partition
+          $min_req_total_space += $FAI::current_config{$disk}{"bios_sectors_per_track"} *
+            $FAI::current_config{$disk}{"sector_size"};
+          # move the start byte as well
+          $next_start += $FAI::current_config{$disk}{"bios_sectors_per_track"} *
+            $FAI::current_config{$disk}{"sector_size"};
+        }
 
-      # check, whether there are partitions that require redistribution
-      if ( scalar(@redist_list) > 0 )
-      {
+        # partition starts at where we currently are
+        $FAI::configs{$config}{"partitions"}{$part_id}{"start_byte"} =
+          $next_start;
 
-        # compute the available space and the resulting multiplier of all
-        # ranges
-        my $free_space =
-          $FAI::current_config{$disk}{"partitions"}{$part_id}{"begin_byte"} -
-          1 - $range_start - $min_req_space;
-        ( $free_space >= 0 ) or die "INTERNAL ERROR: negative free space\n";
+        # the end may need some alignment, depending on the disk label
+        my $end_byte = $next_start + $start - 1;
 
-        # the multiplier for the amount added to the lower bounds of the ranges
-        my $redist_factor = 1.0;
-        ( $free_space < $redist_space )
-          and $redist_factor = $free_space / $redist_space;
-        ( $FAI::debug > 0 ) and print "redist factor is $redist_factor\n";
+        # on msdos, ensure that the partition ends at a cylinder boundary
+        if ( $FAI::configs{$config}{"disklabel"} eq "msdos" )
+        {
+          $end_byte -= ( $end_byte + 1 ) %
+            ( $FAI::current_config{$disk}{"sector_size"} *
+              $FAI::current_config{$disk}{"bios_sectors_per_track"} *
+              $FAI::current_config{$disk}{"bios_heads"} );
+        }
 
-        # redistribute the free space
-        foreach my $part (@redist_list)
+        # on gpt, ensure that the partition ends at a sector boundary
+        if ( $FAI::configs{$config}{"disklabel"} eq "gpt" )
         {
+          $end_byte -= ( $end_byte + 1 ) %
+            $FAI::current_config{$disk}{"sector_size"};
+        }
 
-          # make sure the entry requires redistribution
-          ( $FAI::configs{$config}{"partitions"}{$part}{"size"}{"eff_size"} ==
-              -1 )
-            or die "INTERNAL ERROR: invalid entry in redist_list\n";
+        # set $start and $end to the effective values
+        $start = $end_byte - $next_start + 1;
+        $end = $start;
+        
+        # write back the size spec in bytes
+        $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"range"} =
+          $start . "-" . $end;
 
-          # re-check that the size is indeed a range and obtain the start
-          # and end-points
-          ( $FAI::configs{$config}{"partitions"}{$part}{"size"}{"range"} =~
-              /^(\d+%?)-(\d+%?)$/ )
-            or die "INTERNAL ERROR: invalid range spec\n";
+        # then set eff_size to a proper value
+        $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"eff_size"} =
+          $start;
 
-          # store the start and end
-          my $start = $1;
-          my $end   = $2;
+        # write the end byte to the configuration
+        $FAI::configs{$config}{"partitions"}{$part_id}{"end_byte"} =
+          $end_byte;
+        
+        # and add it to the total disk space required by this config
+        $min_req_total_space +=
+          $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"eff_size"};
 
-          # set the new effective size according to $redist_factor
-          $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"eff_size"} =
-            POSIX::floor( $start + ( ( $end - $start ) * $redist_factor ) );
-        }
+        # set the next start
+        $next_start =
+          $FAI::configs{$config}{"partitions"}{$part_id}{"end_byte"} + 1;
+        
+        # partition done
+        shift @worklist;
       }
+    }
 
-      # check, whether there is sufficient space on the disk
-      ( $min_req_total_space > $FAI::current_config{$disk}{"end_byte"} )
-        and die
-        "Disk is too small - at least $min_req_total_space is required\n";
+    # check, whether there is sufficient space on the disk
+    ( $min_req_total_space > $FAI::current_config{$disk}{"size"} )
+      and die
+      "Disk $disk is too small - at least $min_req_total_space bytes are required\n";
 
-      # make sure, extended partitions are only created on msdos disklabels
-      ( $FAI::configs{$config}{"disklabel"} ne "msdos" && $extended > -1 )
-        and die
-"INTERNAL ERROR: extended partitions are not supported by this disklabel\n";
+    # make sure, extended partitions are only created on msdos disklabels
+    ( $FAI::configs{$config}{"disklabel"} ne "msdos" && $extended > -1 )
+      and die
+      "INTERNAL ERROR: extended partitions are not supported by this disklabel\n";
 
-      # compute the size of the extended partition, if any
-      next
-        unless ( $FAI::configs{$config}{"disklabel"} eq "msdos"
-        && $extended > -1 );
-      foreach
-        my $part_id ( sort keys %{ $FAI::configs{$config}{"partitions"} } )
-      {
-
-        # logical partitions have an id > 4
-        next if ( $part_id <= 4 );
-
-      # add the effective size of $part_id to the size of the extended partition
-        $FAI::configs{$config}{"partitions"}{$extended}{"size"}{"eff_size"} +=
-          $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"eff_size"};
-      }
+    # ensure that we have done our work
+    foreach
+      my $part_id ( sort keys %{ $FAI::configs{$config}{"partitions"} } )
+    {
+      ( defined( $FAI::configs{$config}{"partitions"}{$part_id}{"start_byte"} ) && 
+        defined( $FAI::configs{$config}{"partitions"}{$part_id}{"end_byte"} ) )
+        or die "INTERNAL ERROR: start or end of partition $part_id not set\n";
     }
 
-    # an invalid config entry has been found
-    else
-    {
-      die "INTERNAL ERROR: invalid config entry $config.\n";
-    }
   }
 }
 

Copied: people/michael/features/setup_harddisks_2/implementation/shdd2-volumes (from rev 4292, people/michael/features/setup_harddisks_2/implementation/shdd2-sizes)
===================================================================
--- people/michael/features/setup_harddisks_2/implementation/shdd2-volumes	                        (rev 0)
+++ people/michael/features/setup_harddisks_2/implementation/shdd2-volumes	2007-05-31 07:27:20 UTC (rev 4293)
@@ -0,0 +1,552 @@
+#!/usr/bin/perl -w
+
+#*********************************************************************
+# 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-licences/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.
+#*********************************************************************
+
+use strict;
+
+################################################################################
+#
+# @file shdd2-volumes
+#
+# @brief Parse the current partition table and LVM/RAID configurations
+#
+# $Id$
+#
+# @author Christian Kern, Michael Tautschnig
+# @date Sun Jul 23 16:09:36 CEST 2006
+#
+################################################################################
+
+package FAI;
+
+################################################################################
+#
+# @brief Collect the current partition information from all disks listed both
+# in $FAI::disks and $FAI::configs{PHY_<disk>}
+#
+################################################################################
+sub get_current_disks
+{
+
+  # backup value of $FAI::no_dry_run
+  my $no_dry_run = $FAI::no_dry_run;
+
+  # obtain the current state of all disks
+  foreach my $disk (@FAI::disks)
+  {
+
+    # create full paths
+    ( $disk =~ m{^/} ) or $disk = "/dev/$disk";
+
+    # make sure, $disk is a proper block device
+    ( -b $disk ) or die "$disk is not a block special device!\n";
+
+    # initialise the hash
+    $FAI::current_config{$disk}{"partitions"} = {};
+
+    # the list to hold the output of parted commands as parsed below
+    my @parted_print = ();
+
+    # set no_dry_run to perform read-only commands always
+    $FAI::no_dry_run = 1;
+
+    # try to obtain the partition table for $disk
+    # it might fail with parted_2 in case the disk has no partition table
+    my $error =
+      &FAI::execute_command_std(
+      $FAI::system_commands{"parted"} . " $disk unit TiB print",
+      \@parted_print, 0 );
+
+    # reset no_dry_run
+    $FAI::no_dry_run = $no_dry_run;
+
+    # parted_2 happens when the disk has no disk label, because parted then
+    # provides no information about the disk
+    if ( $error eq "parted_2" )
+    {
+
+      # if there is no disk configuration, write an msdos disklabel
+      if ( ! defined( $FAI::configs{$disk}{"disklabel"} ) )
+      {
+        # write the disk label as configured
+        $error =
+          &FAI::execute_command( $FAI::system_commands{"parted"}
+            . " $disk mklabel msdos" );
+      }
+      else
+      {
+        # write the disk label as configured
+        $error =
+          &FAI::execute_command( $FAI::system_commands{"parted"}
+            . " $disk mklabel "
+            . $FAI::configs{$disk}{"disklabel"} );
+      }
+
+      # set no_dry_run to perform read-only commands always
+      $FAI::no_dry_run = 1;
+
+      # retry partition-table print
+      $error =
+        &FAI::execute_command(
+        $FAI::system_commands{"parted"} . " $disk unit TiB print",
+        \@parted_print, 0 );
+
+      # reset no_dry_run
+      $FAI::no_dry_run = $no_dry_run;
+    }
+
+    # check, whether there is still an error
+    if ( $error ne "" )
+    {
+      my $response = &FAI::get_error( $error, "response" );
+      ( $response eq "die" ) and die &FAI::get_error( $error, "message" );
+      ( $response eq "warn" ) and warn &FAI::get_error( $error, "message" );
+    }
+
+# the following code parses the output of parted print, using various units
+# (TiB, B, chs)
+# the parser is capable of reading the output of parted version 1.7.1, which
+# looks like
+#
+# $ /sbin/parted -s /dev/hda unit B print
+# WARNING: You are not superuser.  Watch out for permissions.
+#
+# Disk /dev/hda: 80026361855B
+# Sector size (logical/physical): 512B/512B
+# Partition Table: mac
+#
+# Number  Start         End           Size          File system  Name     Flags
+#  1      512B          32767B        32256B                     primary
+#  5      32768B        1033215B      1000448B      hfs          primary  boot
+#  3      134250496B    32212287487B  32078036992B  hfs+         primary
+#  6      32212287488B  46212287487B  14000000000B  ext3         primary
+#  2      46212287488B  47212287999B  1000000512B   linux-swap   primary  swap
+#  4      47212288000B  80026361855B  32814073856B  ext3         primary
+#
+# Note that the output contains an additional column on msdos, indicating,
+# whether the type of a partition is primary, logical or extended.
+#
+# $ parted -s /dev/hda unit B print
+#
+# Disk /dev/hda: 82348277759B
+# Sector size (logical/physical): 512B/512B
+# Partition Table: msdos
+#
+# Number  Start         End           Size          Type      File system  Flags
+#  1      32256B        24675839B     24643584B     primary   ext3
+#  2      24675840B     1077511679B   1052835840B   primary   linux-swap
+#  3      1077511680B   13662190079B  12584678400B  primary   ext3         boot
+#  4      13662190080B  82343278079B  68681088000B  extended
+#  5      13662222336B  14715025919B  1052803584B   logical   ext3
+#         14715058176B  30449986559B  15734928384B            
+#  7      30450018816B  32547432959B  2097414144B   logical   ext3
+#  8      32547465216B  82343278079B  49795812864B  logical   ext3
+#
+
+    # As shown above, some entries may be blank. Thus the exact column starts
+    # and lengths must be parsed from the header line. This is stored in the
+    # following hash
+    my %cols = ();
+
+    # Parse the output line by line
+    foreach my $line (@parted_print)
+    {
+
+      # now we test line by line - some of them may be ignored
+      if ( $line =~ /^Disk /
+        || $line =~ /^\s*$/
+        || $line =~ /^WARNING: You are not superuser/ )
+      {
+        next;
+      }
+
+      # determine the logical sector size
+      elsif ( $line =~ /^Sector size \(logical\/physical\): (\d+)B\/(\d+)B$/ )
+      {
+        $FAI::current_config{$disk}{"sector_size"} = $1;
+      }
+
+      # read and store the current disk label
+      elsif ( $line =~ /^Partition Table: (.+)$/ )
+      {
+        $FAI::current_config{$disk}{"disklabel"} = $1;
+      }
+
+      # the line containing the table headers
+      elsif ( $line =~ /^(Number\s+)(\S+\s+)+/ )
+      {
+        my $col_start = 0;
+        # check the length of each heading; note that they might contain spaces
+        while ( $line =~ /^(\S+( [a-z]\S+)?\s*)([A-Z].*)?$/ )
+        {
+          my $heading = $1;
+          # set the line to the remainder
+          $line = "";
+          $line = $3 if defined( $3 );
+          # the width of the column includes any whitespace
+          my $col_width = length( $heading );
+          $heading =~ s/(\S+)\s*$/$1/;
+          # build the hash entry
+          # this start counter starts at 0, which is useful below
+          $cols{$heading} = {
+            "start" => $col_start,
+            "length" => $col_width
+          };
+          $col_start += $col_width;
+        }
+      }
+
+      # one of the partitions
+      else
+      {
+        # we must have seen the header, otherwise probably the format has
+        # changed
+        defined( $cols{"File system"}{"start"} ) or die
+          or die "INTERNAL ERROR: Table header not seen yet\n";
+
+        # the info for the partition number
+        my $num_cols_before = $cols{"Number"}{"start"};
+        my $num_col_width = $cols{"Number"}{"length"};
+
+        # the info for the file system column
+        my $fs_cols_before = $cols{"File system"}{"start"};
+        my $fs_col_width = $cols{"File system"}{"length"};
+
+        # get the partition number, if any
+        $line =~ /^.{$num_cols_before}(.{$num_col_width})/;
+        my $id = $1;
+        $id =~ s/\s*//g;
+        # if there is no partition number, then it must be free space, so no
+        # file system either
+        next if ( $id eq "" );
+
+        # extract the set of characters
+        $line =~ /^.{$fs_cols_before}(.{$fs_col_width})/;
+        my $fs = $1;
+
+        # remove any trailing space
+        $fs =~ s/\s*$//g;
+
+        # store the information in the hash
+        $FAI::current_config{$disk}{"partitions"}{$id}{"filesystem"} = $fs;
+      }
+    }
+
+    # set no_dry_run to perform read-only commands always
+    $FAI::no_dry_run = 1;
+
+    # reset the output list
+    @parted_print = ();
+
+    # obtain the partition table using bytes as units
+    $error =
+      &FAI::execute_command_std(
+      "$FAI::system_commands{'parted'} $disk unit B print free",
+      \@parted_print, 0 );
+
+    # reset no_dry_run
+    $FAI::no_dry_run = $no_dry_run;
+
+    # check, whether an error has occured - already handled by
+    # execute_command_std
+    # if ( $error ne "" )
+    # {
+      # my $response = &FAI::get_error( $error, "response" );
+      # ( $response eq "die" ) and die &FAI::get_error( $error, "message" );
+      # ( $response eq "warn" ) and warn &FAI::get_error( $error, "message" );
+    # }
+    
+    # Parse the output of the byte-wise partition table
+    foreach my $line (@parted_print)
+    {
+      # the disk size line (Disk /dev/hda: 82348277759B)
+      if ( $line =~ /Disk \Q$disk\E: (\d+)B$/ )
+      {
+        $FAI::current_config{$disk}{"begin_byte"} = 0;
+        $FAI::current_config{$disk}{"end_byte"}   = ( $1 - 1 );
+        $FAI::current_config{$disk}{"size"} = $1;
+
+        # nothing else to be done
+        next;
+      }
+
+      # One of the partition lines, see above example
+      next
+        unless ( $line =~
+        /^\s*(\d+)\s+(\d+)B\s+(\d+)B\s+(\d+)B(\s+(primary|logical|extended))?/i
+        );
+
+      # mark the bounds of existing partitions
+      $FAI::current_config{$disk}{"partitions"}{$1}{"begin_byte"} = $2;
+      $FAI::current_config{$disk}{"partitions"}{$1}{"end_byte"}   = $3;
+      $FAI::current_config{$disk}{"partitions"}{$1}{"count_byte"} = $4;
+
+      # is_extended defaults to false/0
+      $FAI::current_config{$disk}{"partitions"}{$1}{"is_extended"} = 0;
+
+      # but may be true/1 on msdos disk labels
+      (      ( $FAI::current_config{$disk}{"disklabel"} eq "msdos" )
+          && ( $6 eq "extended" ) )
+        and $FAI::current_config{$disk}{"partitions"}{$1}{"is_extended"} = 1;
+    }
+      
+    # set no_dry_run to perform read-only commands always
+    $FAI::no_dry_run = 1;
+
+    # reset the output list
+    @parted_print = ();
+
+    # obtain the partition table using bytes as units
+    $error =
+      &FAI::execute_command_std(
+      "$FAI::system_commands{'parted'} $disk unit chs print free",
+      \@parted_print, 0 );
+
+    # reset no_dry_run
+    $FAI::no_dry_run = $no_dry_run;
+
+    # Parse the output of the CHS partition table
+    foreach my $line (@parted_print)
+    {
+      # find the BIOS geometry that looks like this:
+      # BIOS cylinder,head,sector geometry: 10011,255,63.  Each cylinder is 8225kB.
+      if ( $line =~ /^BIOS cylinder,head,sector geometry:\s*(\d+),(\d+),(\d+)\.\s*Each cylinder is \d+kB\.$/ )
+      {
+        $FAI::current_config{$disk}{"bios_cylinders"} = $1;
+        $FAI::current_config{$disk}{"bios_heads"} = $2;
+        $FAI::current_config{$disk}{"bios_sectors_per_track"} = $3;
+      }
+    }
+
+    # make sure we have determined all the necessary information
+    ( $FAI::current_config{$disk}{"begin_byte"} == 0 ) or die
+      "Invalid start byte\n";
+    ( $FAI::current_config{$disk}{"end_byte"} > 0 ) or die
+      "Invalid end byte\n";
+    defined( $FAI::current_config{$disk}{"size"} ) or die
+      "Failed to determine disk size\n";
+    defined( $FAI::current_config{$disk}{"sector_size"} ) or die
+      "Failed to determine sector size\n";
+    defined( $FAI::current_config{$disk}{"bios_sectors_per_track"} ) or die
+      "Failed to determine the number of sectors per track\n";
+
+  }
+}
+
+################################################################################
+#
+# @brief Collect the current LVM configuration
+#
+################################################################################
+sub get_current_lvm
+{
+  # backup value of $FAI::no_dry_run
+  my $no_dry_run = $FAI::no_dry_run;
+
+  # the list to hold the output of vgdisplay commands as parsed below
+  my @vgdisplay_print = ();
+
+  # set no_dry_run to perform read-only commands always
+  $FAI::no_dry_run = 1;
+
+  # try to obtain the list of volume groups
+  my $error = &FAI::execute_command_std( "vgdisplay -s", \@vgdisplay_print, 0 );
+
+  # the expected output (if any) contains lines like the following
+  #
+  # $ vgdisplay -s
+  #   "XENU" 453.36 GB [451.93 GB used / 1.43 GB free]
+
+  # parse the output line by line and call vgdisplay -v <VG>
+  foreach my $line ( @vgdisplay_print )
+  {
+    ( $line =~ /^\s*"(\S+)"\s+\d+\.\d+ GB\s+\[\d+\.\d+ GB\s+used \/ \d+\.\d+ GB\s+free\]$/ )
+      or die "Unexpected vgdisplay output $line";
+
+    # the name of the volume group
+    my $vg = $1;
+          
+    # initialise the hash entry
+    $FAI::current_lvm_config{$vg}{"physical_volumes"} = ();
+
+    # get the detailed configuration for $vg
+    my @vgdisplay_v_print = ();
+    
+    # try to obtain the detailed information for the volume group $vg
+    my $error = &FAI::execute_command_std( "vgdisplay -v $vg", \@vgdisplay_v_print, 0 );
+
+    # the expected output (if any) looks like this:
+# $ vgdisplay -v XENU
+#     Using volume group(s) on command line
+#     Finding volume group "XENU"
+#   --- Volume group ---
+#   VG Name               XENU
+#   System ID             
+#   Format                lvm2
+#   Metadata Areas        4
+#   Metadata Sequence No  65
+#   VG Access             read/write
+#   VG Status             resizable
+#   MAX LV                0
+#   Cur LV                53
+#   Open LV               46
+#   Max PV                0
+#   Cur PV                4
+#   Act PV                4
+#   VG Size               453.36 GB
+#   PE Size               4.00 MB
+#   Total PE              116060
+#   Alloc PE / Size       115693 / 451.93 GB
+#   Free  PE / Size       367 / 1.43 GB
+#   VG UUID               09JCPv-v2RU-NWEZ-ilNA-mNLk-Scw3-aURtE6
+#    
+#   --- Logical volume ---
+#   LV Name                /dev/XENU/mole_
+#   VG Name                XENU
+#   LV UUID                WBcBDw-1z2J-F3b2-FGAk-u7Ki-IEgF-lMEURK
+#   LV Write Access        read/write
+#   LV Status              available
+#   # open                 1
+#   LV Size                1000.00 MB
+#   Current LE             250
+#   Segments               1
+#   Allocation             inherit
+#   Read ahead sectors     0
+#   Block device           254:0
+#    
+#   --- Physical volumes ---
+#   PV Name               /dev/sda8     
+#   PV UUID               4i7Tpi-k9io-Ud44-gWJd-nSuG-hbh7-CE1m43
+#   PV Status             allocatable
+#   Total PE / Free PE    29015 / 0
+#    
+#   PV Name               /dev/sda9     
+#   PV UUID               VXSxq1-vEwU-5VrY-QVC8-3Wf1-AY45-ayD9KY
+#   PV Status             allocatable
+#   Total PE / Free PE    29015 / 0
+#    
+
+    # parse the output to select the interesting parts
+    # there are 3 main groups: the volume group, logical volumes and physical
+    # volumes; use mode to indicate this
+    my $mode = "";
+    # we need to remember the logical volume name across the lines
+    my $lv_name = "";
+    # do the line-wise parsing
+    foreach my $line_v ( @vgdisplay_v_print )
+    {
+      $mode = "vg" if ( $line_v =~ /^\s*--- Volume group ---\s*$/ );
+      $mode = "lv" if ( $line_v =~ /^\s*--- Logical volume ---\s*$/ );
+      $mode = "pv" if ( $line_v =~ /^\s*--- Physical volumes ---\s*$/ );
+      $mode = "" if ( $mode ne "pv" && $line_v =~ /^\s*$/ );
+      next if ( $mode eq "" );
+
+      # Now select the interesting information for each mode
+      if ( $mode eq "vg" )
+      {
+        # for a volume group only the size is needed
+        if ( $line_v =~ /^\s*Alloc PE \/ Size\s+\d+ \/ (\d+\.\d+) ([MG]B)\s*$/ )
+        {
+          # extract the floatingpoint value
+          $FAI::current_lvm_config{$vg}{"size"} = $1;
+          # make it megabytes
+          $FAI::current_lvm_config{$vg}{"size"} *= 1024 if ( $2 eq "GB" );
+        }
+      }
+      elsif ( $mode eq "lv" )
+      {
+        # we need the name and the size of each existing logical volume
+        if ( $line_v =~ /^\s*LV Name\s+\/dev\/\Q$vg\E\/(\S+)\s*$/ )
+        {
+          $lv_name = $1;
+        }
+        elsif ( $line_v =~ /^\s*LV Size\s+(\d+\.\d+) ([MG]B)\s*$/ )
+        {
+          # extract the floatingpoint value
+          $FAI::current_lvm_config{$vg}{"volumes"}{$lv_name}{"size"} = $1;
+          # make it megabytes
+          $FAI::current_lvm_config{$vg}{"volumes"}{$lv_name}{"size"} *= 1024 if ( $2 eq "GB" );
+        }
+      }
+      elsif ( $mode eq "pv" )
+      {
+        # get the physical devices that are part of this volume group
+        if ( $line_v =~ /^\s*PV Name\s+(\S+)\s*$/ )
+        {
+          push @{ $FAI::current_lvm_config{$vg}{"physical_volumes"} }, $1;
+        }
+      }
+
+    }
+  }
+  
+  # reset no_dry_run
+  $FAI::no_dry_run = $no_dry_run;
+}
+
+################################################################################
+#
+# @brief Collect the current RAID device information from all partitions
+# currently active in the system
+#
+################################################################################
+sub get_current_raid
+{
+  # backup value of $FAI::no_dry_run
+  my $no_dry_run = $FAI::no_dry_run;
+
+  # the list to hold the output of mdadm commands as parsed below
+  my @mdadm_print = ();
+
+  # set no_dry_run to perform read-only commands always
+  $FAI::no_dry_run = 1;
+
+  # try to obtain the list of existing RAID arrays
+  my $error = &FAI::execute_command_std( 
+    "mdadm --detail --scan --verbose -c partitions", \@mdadm_print, 0 );
+
+# the expected output is as follows
+# $ mdadm --detail --scan --verbose -c partitions
+# ARRAY /dev/md0 level=linear num-devices=2 UUID=7e11efd6:93e977fd:b110d941:ce79a4f6
+#    devices=/dev/hda1,/dev/hda2
+# ARRAY /dev/md1 level=raid0 num-devices=2 UUID=50d7a6ec:4207f0db:b110d941:ce79a4f6
+#    devices=/dev/md0,/dev/hda3
+
+  # the id of the RAID
+  my $id;
+  # parse the output line by line
+  foreach my $line ( @mdadm_print )
+  {
+    if ( $line =~ /^ARRAY \/dev\/md(\d+) level=(\S+) num-devices=\d+ UUID=/ )
+    {
+      $id = $1;
+      $FAI::current_raid_config{$id}{"mode"} = $2;
+    }
+    elsif ( $line =~ /^\s*devices=(\S+)$/ )
+    {
+      @{ $FAI::current_raid_config{$id}{"devices"} } = split(",", $1);
+    }
+  }
+  
+  # reset no_dry_run
+  $FAI::no_dry_run = $no_dry_run;
+}
+
+1;
+




More information about the Fai-commit mailing list