[Fai-commit] r4868 - in people/michael/features/setup_harddisks_2: . oo-rewrite-that-may-happen-or-not
mt at alioth.debian.org
mt at alioth.debian.org
Sat Apr 12 13:39:29 UTC 2008
Author: mt
Date: 2008-04-12 13:39:28 +0000 (Sat, 12 Apr 2008)
New Revision: 4868
Added:
people/michael/features/setup_harddisks_2/oo-rewrite-that-may-happen-or-not/
people/michael/features/setup_harddisks_2/oo-rewrite-that-may-happen-or-not/README
people/michael/features/setup_harddisks_2/oo-rewrite-that-may-happen-or-not/README.dev
people/michael/features/setup_harddisks_2/oo-rewrite-that-may-happen-or-not/storage-magic
people/michael/features/setup_harddisks_2/setup-storage.8
Log:
- backup of the an OO attempt of setup-storage made at the FAI-WS'07
- added man-page for current setup-storage
Added: people/michael/features/setup_harddisks_2/oo-rewrite-that-may-happen-or-not/README
===================================================================
--- people/michael/features/setup_harddisks_2/oo-rewrite-that-may-happen-or-not/README (rev 0)
+++ people/michael/features/setup_harddisks_2/oo-rewrite-that-may-happen-or-not/README 2008-04-12 13:39:28 UTC (rev 4868)
@@ -0,0 +1,6 @@
+At the 2007 FAI workshop, an object oriented version of setup-storage has been
+discussed. There have been many ideas but only few solutions, but time will
+tell, whether this happens or not.
+
+For the moment, this is a dead branch, but feel free to revive it.
+
Added: people/michael/features/setup_harddisks_2/oo-rewrite-that-may-happen-or-not/README.dev
===================================================================
--- people/michael/features/setup_harddisks_2/oo-rewrite-that-may-happen-or-not/README.dev (rev 0)
+++ people/michael/features/setup_harddisks_2/oo-rewrite-that-may-happen-or-not/README.dev 2008-04-12 13:39:28 UTC (rev 4868)
@@ -0,0 +1,222 @@
+################################################################################
+# TODO list
+# - parted, libparse-recdescent-perl must get installed in nfsroot (add Depends:
+# to fai-nfsroot package)
+#
+# - closes #380629, #330915, #277045, #356862, #416633
+# - 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)
+# - LVMs definitely require resizing the filesystem
+# - 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
+# - the RAID commands are surely incomplete and lack any management of
+# unanticipated situations
+# - try to get libparted-swig-perl and use that one instead of the manual
+# parsing
+################################################################################
+
+################################################################################
+#
+# @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>
+# 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
+# volumes
+# <0..n>
+# mode
+# devices
+# /dev/<device-name>
+# options
+# spare (0|1)
+# missing (0|1)
+# mountpoint
+# mount_options
+# filesystem
+# fs_options
+# label
+#
+################################################################################
+
+################################################################################
+#
+# @brief The current disk configuration
+#
+# The structure is as follows:
+# <DEVICE>
+# 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
+#
+################################################################################
+
+################################################################################
+#
+# @brief The current LVM configuration
+#
+# The structure is as follows:
+# <VG>
+# physical_volumes
+# size
+# volumes
+# <lv-name>
+# size
+#
+################################################################################
+
+################################################################################
+#
+# @brief The current RAID configuration
+#
+# The structure is as follows:
+# <0..n>
+# devices
+# mode
+#
+################################################################################
+
+################################################################################
+#
+# @file shdd2-parser
+#
+# @brief A parser for the disk_config files within FAI, based on the EBNF
+# listed below. The implementation makes use of the RecDescent package.
+#
+# file ::= <lines> EOF
+#
+# lines ::= EOL
+# /* empty lines or whitespace only */
+# | <comment> EOL
+# | <config> EOL
+#
+# comment ::= #.*
+#
+# config ::= disk_config lvm
+# | disk_config raid
+# | disk_config end
+# | disk_config disk[[:digit:]]+( <option>)*
+# | disk_config [^[:space:]]+( <option>)*
+# /* fully qualified device-path or short form, like hda, whereby full
+# * path is assumed to be /dev/hda */
+# | <volume>
+#
+# option ::= /* empty */
+# | preserve:[[:digit:]]+(,[[:digit:]]+)*
+# /* preserve partitions */
+# | resize:[[:digit:]]+(,[[:digit:]]+)*
+# /* attempt to resize partitions */
+# | 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>
+# /* lvm vg */
+#
+# type ::= primary
+# /* for physical disks only */
+# | logical
+# /* for physical disks only */
+# | raid[0156]
+# /* raid level */
+# | [^/[:space:]]+-[^/[:space:]]+
+# /* lvm logical volume: vg name and lv name*/
+#
+# mountpoint ::= -
+# /* do not mount */
+# | swap
+# /* swap space */
+# | /[^[:space:]]*
+# /* fully qualified path */
+#
+# name ::= [^/[:space:]]+
+# /* lvm volume group name */
+#
+# size ::= [[:digit:]]+[kMGTP%]?(-([[:digit:]]+[kMGTP%]?)?)?(:resize)?
+# /* size in kilo, mega (default), giga, tera or petabytes or %,
+# * possibly given as a range; physical
+# * partitions or lvm logical volumes only; */
+# | -[[:digit:]]+[kMGTP%]?(:resize)?
+# /* size in kilo, mega (default), giga, tera or petabytes or %,
+# * given as upper limit; physical partitions
+# * or lvm logical volumes only */
+# | [^,:[:space:]]+(:(spare|missing))*(,[^,:[:space:]]+(:(spare|missing))*)*
+# /* devices and options for a raid or lvm vg */
+#
+# mount_options ::= [^[:space:]]+
+#
+# filesystem ::= -
+# | swap
+# | [^[:space:]]
+# /* mkfs.xxx must exist */
+#
+# fs_options ::= .*
+# /* options appended to mkfs.xxx call */
+#
+#
+# $Id: shdd2-parser 4631 2007-10-18 15:23:23Z andreas $
+#
+# @author Christian Kern, Michael Tautschnig, Sam Vilain, Andreas Schuldei
+# @date Sun Jul 23 16:09:36 CEST 2006
+#
+################################################################################
+
Added: people/michael/features/setup_harddisks_2/oo-rewrite-that-may-happen-or-not/storage-magic
===================================================================
--- people/michael/features/setup_harddisks_2/oo-rewrite-that-may-happen-or-not/storage-magic (rev 0)
+++ people/michael/features/setup_harddisks_2/oo-rewrite-that-may-happen-or-not/storage-magic 2008-04-12 13:39:28 UTC (rev 4868)
@@ -0,0 +1,3244 @@
+#!/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.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+#*********************************************************************
+
+use strict;
+# treat all warnings about uninitialised values as errors
+use warnings FATAL => qw(uninitialized);
+
+################################################################################
+#
+# @brief Storage Magic - the tool to configure the partitioning from within FAI.
+#
+# This is an implementation from scratch to properly support LVM and RAID. The
+# input format is documented in @ref shdd2-parser
+#
+# $Id: shdd2 4614 2007-10-09 18:36:33Z andreas $
+#
+# @author Christian Kern, Andreas Schuldei, Michael Tautschnig
+# @date Sun Jul 23 16:09:36 CEST 2006
+#
+################################################################################
+
+package Command;
+
+################################################################################
+#
+# @brief Test, whether @ref $cmd is available on the system using $PATH
+#
+# This is a static method, use Command::in_path
+#
+# @param $cmd Command that is to be found in $PATH
+#
+# @return 1, if the command is found, else 0
+#
+################################################################################
+sub in_path
+{
+ my ($cmd) = @_;
+
+ # split $PATH into its components, search all of its components and test for
+ # $cmd being executable
+ ( -x "$_/$cmd" ) and return 1 foreach ( split( ":", $ENV{"PATH"} ) )
+ return 0;
+}
+
+sub new {
+ my ($class, $cmd_string) = @_;
+ return bless {
+ "cmd" => $cmd_string,
+ "stdout" => (),
+ "stderr" => ()
+ }, $class;
+}
+
+sub get_stdout {
+ my ($self) = @_;
+ return $self->{ "stdout" };
+}
+
+sub get_stderr {
+ my ($self) = @_;
+ return $self->{ "stderr" };
+}
+
+sub run {
+ my ($self) = @_;
+
+ # clear the output lists
+ $self->{ "stdout" } = ();
+ $self->{ "stderr" } = ();
+
+ # 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( $self->{ "cmd" },
+ \@{ $self->{ "stdout" } }, \@{ $self->{ "stderr" } } );
+
+ return $error;
+}
+
+sub force_run {
+ my ($self) = @_;
+
+ # backup value of $FAI::no_dry_run
+ my $no_dry_run = $FAI::no_dry_run;
+
+ # 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( $self->{ "cmd" },
+ \@{ $self->{ "stdout" } }, \@{ $self->{ "stderr" } } );
+
+ # reset no_dry_run
+ $FAI::no_dry_run = $no_dry_run;
+
+ return $error;
+}
+
+package Geometry;
+
+sub new {
+ my ($class) = @_;
+ return bless {}, $class;
+}
+
+package File_System;
+
+sub new {
+ my ($class) = @_;
+ return bless {}, $class;
+}
+
+package Mount_Options;
+
+sub new {
+ my ($class) = @_;
+ return bless {}, $class;
+}
+
+package Storage_Container;
+# has_a Volume
+
+sub new {
+ my ($class, $name) = @_;
+ return bless {
+ "name" => $name,
+ "volumes" => {}
+ }, $class;
+}
+
+sub add_volume {
+ my ($self, $volume) = @_;
+ @$self->{ "volumes" } = (
+ @$self->{ "volumes" },
+ $volume
+ };
+}
+
+sub get_name {
+ my ($self) = @_;
+ return $self->{ "name" };
+}
+
+
+package Physical_Disk;
+use base "Storage_Container";
+# has_a Geometry
+
+################################################################################
+#
+# @brief Initialise a new entry in @ref $FAI::configs for a physical disk.
+#
+# Besides creating the entry in the hash, the fully path of the device is
+# computed (see @ref $disk) and it is tested, whether this is a block device.
+# The device name is then used to define @ref $FAI::device.
+#
+# @param $disk Either an integer, occurring in the context of, e.g., disk2, or
+# a device name. The latter may be fully qualified, such as /dev/hda, or a short
+# name, such as sdb, in which case /dev/ is prepended.
+#
+################################################################################
+sub new {
+ my ($class, $disk) = @_;
+
+ # test $disk for being numeric
+ if ( $disk =~ /^\d+$/ ) {
+
+ # $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
+ $disk = $FAI::disks[ $disk - 1 ];
+ }
+
+ # test, whether the device name starts with a / and prepend /dev/, if
+ # appropriate
+ ( $disk =~ m{^/} ) or $disk = "/dev/$disk";
+
+ # test, whether $disk is a block special device
+ ( -b $disk ) or die "$disk is not a valid device name\n";
+
+ # test, whether this is the first disk_config stanza to configure $disk
+ defined( $FAI::configs{$FAI::device} )
+ and die "Duplicate configuration for disk $FAI::disks[ $1-1 ]\n";
+
+ # Initialise the entry in $FAI::configs
+ my $self = $class->SUPER::new("PHY_$disk");
+ %$self = (
+ "virtual" => 0,
+ "disklabel" => "msdos",
+ "bootable" => -1,
+ "fstabkey" => "device",
+ %$self,
+ "geometry" => Geometry->new()
+ );
+ return bless ($self, $class);
+}
+
+package Volume;
+
+sub new {
+ my ($class) = @_;
+ return bless {}, $class;
+}
+
+package Disk_Partition;
+use base "Volume";
+# has_a Mount_Options
+# has_a File_System
+
+sub new {
+ my ($class) = @_;
+ return bless {}, $class;
+}
+
+package Extended_Partition
+ at ISA = qw( Storage_Container, Volume );
+
+sub new {
+ my ($class) = @_;
+ my $self = $class->Storage_Container::new("extended");
+ %$self = ( %$self, $class->Volume::new() );
+ return bless ($self, $class);
+}
+
+package Fstab;
+
+sub new {
+ my ($class) = @_;
+ return bless {}, $class;
+}
+
+package Disk_Var;
+
+sub new {
+ my ($class) = @_;
+ return bless {}, $class;
+}
+
+sub add_variable {
+ my ($self, $var, $entry) = @_;
+ defined( $self->{ $var } )
+ and die "INTERNAL ERROR (Disk_Var::add_variable)\n";
+ $self->{ $var } = $entry;
+}
+
+sub get_value {
+ my ($self, $var) = @_;
+ defined( $self->{ $var } )
+ or die "INTERNAL ERROR (Disk_Var::get_value)\n";
+ return $self->{ $var };
+}
+
+package Disk_Config;
+
+sub new {
+ my ($class) = @_;
+ return bless {}, $class;
+}
+
+package System_Config;
+
+sub new {
+ my ($class) = @_;
+ return bless {}, $class;
+}
+
+sub add_container {
+ my ($self, $container) = @_;
+ %$self = ( %$self, $container );
+}
+
+################################################################################
+#
+# @brief Collect the current partition information from all disks listed both
+# in $FAI::disks and $FAI::configs{PHY_<disk>}
+#
+################################################################################
+sub get_current_disks
+{
+ my ($self) = @_;
+
+ # 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 a new container
+ my $disk_ref = Physical_Disk->new($disk);
+ $self->add_container($disk_ref);
+
+ # try to obtain the partition table for $disk
+ # it might fail with parted_2 in case the disk has no partition table
+ my $print_cmd = Command->new( "parted -s $disk unit TiB print" );
+ my $error = $print_cmd->force_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{"PHY_$disk"}{"disklabel"} ) ) {
+ # write the disk label as configured
+ $error =
+ &FAI::execute_command(
+ "parted -s $disk mklabel msdos" );
+ } else {
+ # write the disk label as configured
+ $error =
+ &FAI::execute_command( "parted -s $disk mklabel "
+ . $FAI::configs{"PHY_$disk"}{"disklabel"} );
+ }
+
+ $error = $print_cmd->force_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
+ next if ( $line =~ /^Disk /
+ || $line =~ /^\s*$/
+ || $line =~ /^WARNING: You are not superuser/ );
+
+ if ( $line =~ /^Sector size \(logical\/physical\): (\d+)B\/(\d+)B$/ ) { # determine the logical sector size
+ $FAI::current_config{$disk}{"sector_size"} = $1;
+ } elsif ( $line =~ /^Partition Table: (.+)$/ ) { # read and store the current disk label
+ $FAI::current_config{$disk}{"disklabel"} = $1;
+ } elsif ( $line =~ /^(Number\s+)(\S+\s+)+/ ) { # the line containing the table headers
+ my $col_start = 0;
+ my $heading;
+ my $col_width;
+
+ # check the length of each heading; note that they might contain spaces
+ while ( $line =~ /^(\S+( [a-z]\S+)?\s*)([A-Z].*)?$/ ) {
+ $heading = $1;
+
+ # set the line to the remainder
+ $line = "";
+ $line = $3 if defined($3);
+
+ # the width of the column includes any whitespace
+ $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 "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( "parted -s $disk unit B print free",
+ \@parted_print, 0 );
+
+ # reset no_dry_run
+ $FAI::no_dry_run = $no_dry_run;
+
+ # Parse the output of the byte-wise partition table
+ foreach my $line (@parted_print) {
+
+ if ( $line =~ /Disk \Q$disk\E: (\d+)B$/ ) { # the disk size line (Disk /dev/hda: 82348277759B)
+ $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( "parted -s $disk unit chs print free",
+ \@parted_print, 0 );
+
+ # reset no_dry_run
+ $FAI::no_dry_run = $no_dry_run;
+
+ foreach my $line (@parted_print) { # Parse the output of the CHS partition table
+
+ # find the BIOS geometry that looks like this:
+ # BIOS cylinder,head,sector geometry: 10011,255,63. Each cylinder is 8225kB.
+ next unless ( $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";
+
+ }
+}
+
+use Linux::LVM;
+
+################################################################################
+#
+# @brief Collect the current LVM configuration
+#
+################################################################################
+sub get_current_lvm
+{
+
+ # get the existing volume groups
+ foreach my $vg (get_volume_group_list())
+ {
+ # initialise the hash entry
+ $FAI::current_lvm_config{$vg}{"physical_volumes"} = ();
+ # store the vg size in MB
+ my %vg_info = get_volume_group_information($vg);
+ $FAI::current_lvm_config{$vg}{"size"} =
+ &FAI::convert_unit( $vg_info{"alloc_pe_size"} .
+ $vg_info{"alloc_pe_size_unit"} );
+ # store the logical volumes and their sizes
+ my %lv_info = get_logical_volume_information($vg);
+ foreach my $lv_name (sort keys %lv_info) {
+ my $short_name = $lv_name;
+ $short_name =~ "s{/dev/\Q$vg\E/}{}";
+ $FAI::current_lvm_config{$vg}{"volumes"}{$short_name}{"size"} =
+ &FAI::convert_unit( $lv_info{$lv_name}->{"lv_size"} .
+ $lv_info{$lv_name}->{"lv_size_unit"} );
+ }
+ # store the physical volumes
+ $FAI::current_lvm_config{$vg}{"physical_volumes"} =
+ sort keys get_physical_volume_information($vg);
+ }
+
+}
+
+################################################################################
+#
+# @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;
+}
+
+
+
+package main;
+
+# command line parameter handling
+use Getopt::Std;
+
+# the variables for getopt
+our ( $opt_X, $opt_f );
+
+# parse the command line options
+&getopts('Xf:') || die <<EOF;
+USAGE: [-X] no test, your harddisks will be formated
+ default: only test, no real formating
+ [-f<config-filename>] default: parse classes
+EOF
+
+# $disklist must be provided by the environment
+defined( $ENV{disklist} ) or die "Environment variable disklist is not set";
+
+$FAI::no_dry_run = 0; # Really write any changes to disk if set to 1
+($opt_X) and $FAI::no_dry_run = 1;
+($opt_X) or warn "shdd2 is running in test-only mode!\n";
+
+$FAI::debug = 0; # Enable debugging by setting $debug to a value greater than 0
+defined( $ENV{debug} ) and $FAI::debug = $ENV{debug};
+
+
+ at FAI::disks = split( /\n/, $ENV{disklist} ); # The lists of disks of the system
+( $FAI::debug > 0 ) and print "disklist was:\n" . $ENV{disklist};
+
+my $disk_var = Disk_Var->new(); # The variables later written to disk_var.sh
+$disk_var->add_variable( "SWAPLIST", "" );
+
+my $disk_config = Disk_Config->new(); # The desired configuration
+
+my $system_config = System_Config->new(); # The current disk, LVM, RAID layouts
+
+my @commands = (); # The list of system commands to be executed
+
+
+use Parse::RecDescent;
+
+################################################################################
+#
+# @brief the name of the device currently being configured, including a prefix
+# such as PHY_ or VG_ to indicate physical devices or LVM volume groups. For
+# RAID, the entry is only "RAID"
+#
+################################################################################
+$FAI::device = "";
+
+################################################################################
+#
+# @brief Initialise the entry of a partition in @ref $FAI::configs
+#
+# @param $type The type of the partition. It must be either primary or logical.
+#
+################################################################################
+sub init_part_config
+{
+
+ # the type of the partition to be created
+ my ($type) = @_;
+
+ # type must either be primary or logical, nothing else may be accepted by the
+ # parser
+ ( $type eq "primary" || $type eq "logical" ) or die "INTERNAL PARSER ERROR\n";
+
+ # check that a physical device is being configured; logical partitions are
+ # only supported on msdos disk labels.
+ ( $FAI::device =~ /^PHY_/ && ( $type ne "logical"
+ || $FAI::configs{$FAI::device}{"disklabel"} eq "msdos" ) )
+ or die "Syntax error: invalid partition type";
+
+ # the index of the new partition
+ my $part_number = 0;
+
+ # create a primary partition
+ if ( $type eq "primary" ) {
+
+ # find all previously defined primary partitions
+ foreach my $part_id ( sort keys %{ $FAI::configs{$FAI::device}{"partitions"} } ) {
+
+ # break, if the partition has not been created by init_part_config
+ defined( $FAI::configs{$FAI::device}{"partitions"}{$part_id}{"size"}{"extended"} ) or last;
+
+ # on msdos disklabels we cannot have more than 4 primary partitions
+ last if ( $part_id > 4
+ && $FAI::configs{$FAI::device}{"disklabel"} eq "msdos" );
+
+ # store the latest index found
+ $part_number = $part_id;
+ }
+
+ # the next index available - note that $part_number might have been 0
+ $part_number++;
+
+ # msdos disk labels don't allow for more than 4 primary partitions
+ ( $part_number < 5 || $FAI::configs{$FAI::device}{"disklabel"} ne "msdos" )
+ or die "$part_number are too many primary partitions\n";
+ } else {
+
+ # no further checks for the disk label being msdos have to be performed in
+ # this branch, it has been ensured above
+
+# find the index of the new partition, initialise it to the highest current index
+ foreach my $part_id ( sort keys %{ $FAI::configs{$FAI::device}{"partitions"} } ) {
+
+ # skip primary partitions
+ next if ( $part_id < 5 );
+
+ # break, if the partition has not been created by init_part_config
+ defined( $FAI::configs{$FAI::device}{"partitions"}{$part_id}{"size"}{"extended"} ) or last;
+
+ # store the latest index found
+ $part_number = $part_id;
+ }
+
+ # and use the next one available
+ $part_number++;
+
+ # if this is the first logical partition, the index must be set to 5 and an
+ # extended partition must be created
+ if ( $part_number <= 5 ) {
+ $part_number = 5;
+
+ # the proposed index of the extended partition
+ my $extended = 0;
+
+ # find all previously defined primary partitions
+ foreach my $part_id ( sort keys %{ $FAI::configs{$FAI::device}{"partitions"} } ) {
+
+ # break, if the partition has not been created by init_part_config
+ defined( $FAI::configs{$FAI::device}{"partitions"}{$part_id}{"size"}{"extended"} ) or last;
+
+ # we cannot have more than 4 primary partitions
+ last if ( $part_id > 4 );
+
+ # store the latest index found
+ $extended = $part_id;
+ }
+
+ # the next index available
+ $extended++;
+
+ # msdos disk labels don't allow for more than 4 primary partitions
+ ( $extended < 5 )
+ or die "Too many primary partitions while creating extended\n";
+
+ # mark the entry as an extended partition
+ $FAI::configs{$FAI::device}{"partitions"}{$extended}{"size"}{"extended"} = 1;
+
+ # add the preserve = 0 flag, if it doesn't exist already
+ defined( $FAI::configs{$FAI::device}{"partitions"}{$extended}{"size"}{"preserve"} )
+ or $FAI::configs{$FAI::device}{"partitions"}{$extended}{"size"}{"preserve"} = 0;
+
+ # add the resize = 0 flag, if it doesn't exist already
+ defined( $FAI::configs{$FAI::device}{"partitions"}{$extended}{"size"}{"resize"} )
+ or $FAI::configs{$FAI::device}{"partitions"}{$extended}{"size"}{"resize"} = 0;
+ }
+ }
+
+ # initialise the hash for the partitions, if it doesn't exist already
+ # note that it might exists due to options, such as preserve:x,y
+ # the initialisation is required for the reference defined next
+ defined( $FAI::configs{$FAI::device}{"partitions"}{$part_number} )
+ or $FAI::configs{$FAI::device}{"partitions"}{$part_number} = {};
+
+ # set the reference to the current partition
+ # the reference is used by all further processing of this config line
+ $FAI::partition_pointer =
+ ( \%FAI::configs )->{$FAI::device}->{"partitions"}->{$part_number};
+
+ # as we can't compute the index from the reference, we need to store the
+ # $part_number explicitly
+ $FAI::partition_pointer->{"number"} = $part_number;
+
+ # the partition is not an extended one
+ $FAI::partition_pointer->{"size"}->{"extended"} = 0;
+
+ # add the preserve = 0 flag, if it doesn't exist already
+ defined( $FAI::partition_pointer->{"size"}->{"preserve"} )
+ or $FAI::partition_pointer->{"size"}->{"preserve"} = 0;
+
+ # add the resize = 0 flag, if it doesn't exist already
+ defined( $FAI::partition_pointer->{"size"}->{"resize"} )
+ or $FAI::partition_pointer->{"size"}->{"resize"} = 0;
+}
+
+################################################################################
+#
+# @brief This function converts different sizes to Mbyte
+#
+# @param $val is the number with its unit
+#
+################################################################################
+sub convert_unit
+{
+ my ($val) = @_;
+ ( $val =~ /^(\d+)([kMGTP%]?)(B)?\s*$/ ) or die "INTERNAL ERROR (convert_unit)\n";
+ $val = $1 * ( 1 / 1024 ) if ( $2 eq "k" );
+ $val = $1 if ( $2 eq "M" );
+ $val = $1 * 1024 if ( $2 eq "G" );
+ $val = $1 * ( 1024 * 1024 ) if ( $2 eq "T" );
+ $val = $1 * ( 1024 * 1024 * 1024 ) if ( $2 eq "P" );
+ # % is returned as is
+ return $val;
+}
+
+# have RecDescent do proper error reporting
+$::RD_HINT = 1;
+
+################################################################################
+#
+# @brief The effective implementation of the parser is instantiated here
+#
+################################################################################
+$FAI::Parser = Parse::RecDescent->new(
+ q{
+ file: line(s?) /\Z/
+ {
+ $return = 1;
+ }
+ | <error>
+
+ line: <skip: qr/[ \t]*/> "\\n"
+ | <skip: qr/[ \t]*/> comment "\\n"
+ | <skip: qr/[ \t]*/> config "\\n"
+
+ comment: /^\s*#.*/
+
+ config: 'disk_config' disk_config_arg
+ | volume
+
+ disk_config_arg: 'raid'
+ {
+ # check, whether raid tools are available
+ ( &FAI::in_path( "mdadm" ) == 1 ) or die "mdadm not found in PATH\n";
+ $FAI::device = "RAID";
+ }
+ | /^lvm/
+ {
+
+ # check, whether lvm tools are available
+ ( &FAI::in_path( "lvcreate" ) == 1 ) or die "LVM tools not found in PATH\n";
+ # initialise $FAI::device to inform the following lines about the LVM
+ # being configured
+ $FAI::device = "VG_";
+ }
+ | 'end'
+ {
+ # exit config mode
+ $FAI::device = "";
+ }
+ | /^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 ] );
+ }
+ option(s?)
+
+ option: /^preserve:(\d+(,\d+)*)/
+ {
+ # set the preserve flag for all ids
+ $FAI::configs{ $FAI::device }{ "partitions" }{ $_ }{ "size" }{ "preserve" } = 1 foreach ( split( ",", $1 ) );
+ }
+ | /^resize:(\d+(,\d+)*)/
+ {
+ # set the resize flag for all ids
+ $FAI::configs{ $FAI::device }{ "partitions" }{ $_ }{ "size" }{ "resize" } = 1 foreach ( split( ",", $1 ) );
+ }
+ | /^disklabel:(msdos|gpt)/
+ {
+ # set the disk label - actually not only the above, but all types
+ # 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
+ $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\s+/ name devices
+ | /^raid([0156])\s+/
+ {
+ # make sure that this is a RAID configuration
+ ( $FAI::device eq "RAID" ) or die "RAID entry invalid in this context\n";
+ # initialise RAID entry, if it doesn't exist already
+ defined( $FAI::configs{"RAID"} ) or $FAI::configs{"RAID"}{"volumes"} = {};
+ # compute the next available index - the size of the entry
+ my $vol_id = scalar( keys %{ $FAI::configs{"RAID"}{"volumes"} } );
+ # set the RAID type of this volume
+ $FAI::configs{"RAID"}{"volumes"}{$vol_id}{"mode"} = $1;
+ # initialise the hash of devices
+ $FAI::configs{"RAID"}{"volumes"}{$vol_id}{"devices"} = {};
+ # set the reference to the current volume
+ # the reference is used by all further processing of this config line
+ $FAI::partition_pointer = ( \%FAI::configs )->{"RAID"}->{"volumes"}->{$vol_id};
+ }
+ mountpoint devices filesystem mount_options fs_options
+ | type mountpoint size filesystem mount_options fs_options
+
+ type: 'primary'
+ {
+ # initialise a primary partition
+ &FAI::init_part_config( $item[ 1 ] );
+ }
+ | 'logical'
+ {
+ # initialise a logical partition
+ &FAI::init_part_config( $item[ 1 ] );
+ }
+ | m{^([^/\s\-]+)-([^/\s\-]+)\s+}
+ {
+ # set $FAI::device to VG_$1
+ $FAI::device = "VG_$1";
+ # make sure, the volume group $1 has been defined before
+ defined( $FAI::configs{$FAI::device} ) or die "Volume group $1 has not been declared yet.\n";
+ # make sure, $2 has not been defined already
+ defined( $FAI::configs{$FAI::device}{"volumes"}{$2} ) and 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};
+ }
+
+ mountpoint: '-'
+ {
+ # this partition should not be mounted
+ $FAI::partition_pointer->{ "mountpoint" } = "-";
+ }
+ | 'swap'
+ {
+ # this partition is swap space, not mounted
+ $FAI::partition_pointer->{ "mountpoint" } = "none";
+ }
+ | m{^/\S*}
+ {
+ # set the mount point
+ $FAI::partition_pointer->{ "mountpoint" } = $item[ 1 ];
+ # if the mount point is / or /boot and we are currently doing a
+ # physical device, the variables should be set, unless they are
+ # already
+ if ( $FAI::configs{$FAI::device}{"bootable"} == -1 &&
+ $FAI::device =~ /^PHY_(.+)$/ &&
+ ( $item[ 1 ] eq "/boot" || ( $item[ 1 ] eq "/" &&
+ !defined( $FAI::disk_var{ "BOOT_DEVICE" } ) ) ) ) {
+ # set the BOOT_DEVICE and BOOT_PARTITION variables
+ $FAI::disk_var{ "BOOT_DEVICE" } = $1;
+ $FAI::disk_var{ "BOOT_PARTITION" } = $1 .
+ $FAI::partition_pointer->{"number"};
+ }
+ }
+
+ name: m{^([^/\s\-]+)}
+ {
+ # 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+[kMGTP%]?(-(\d+[kMGTP%]?)?)?)(:resize)?\s+/
+ {
+ # complete the size specification to be a range in all cases
+ my $range = $1;
+ # the size is fixed
+ if( ! defined( $2 ) ) {
+ # make it a range of the form x-x
+ $range = "$range-$1";
+ } elsif( ! defined( $3 ) ) {
+ # range has no upper limit, assume the whole disk
+ $range = $range . "100%";
+ }
+
+ # convert the units, if necessary
+ my ($min, $max) = split(/-/, $range);
+ $min = &FAI::convert_unit($min);
+ $max = &FAI::convert_unit($max);
+ $range = "$min-$max";
+ # enter the range into the hash
+ $FAI::partition_pointer->{ "size" }->{ "range" } = $range;
+ # set the resize flag, if required
+ defined( $4 ) and $FAI::partition_pointer->{ "size" }->{ "resize" } = 1;
+ }
+ | /^(-\d+[kMGTP%]?)(:resize)?\s+/
+ {
+ # complete the range by assuming 0 as the lower limit
+ my $range = "0$1";
+ # convert the units, if necessary
+ my ($min, $max) = split(/-/, $range);
+ $min = &FAI::convert_unit($min);
+ $max = &FAI::convert_unit($max);
+ $range = "$min-$max";
+ # enter the range into the hash
+ $FAI::partition_pointer->{ "size" }->{ "range" } = $range;
+ # set the resize flag, if required
+ defined( $2 ) and $FAI::partition_pointer->{ "size" }->{ "resize" } = 1;
+ }
+ | <error: invalid partition size near "$text">
+
+ devices: /^([^\d,:\s\-][^,:\s]*(:(spare|missing))*(,[^,:\s]+(:(spare|missing))*)*)/
+ {
+ # split the device list by ,
+ foreach my $dev ( split( ",", $1 ) )
+ {
+ # match the substrings
+ ( $dev =~ /^([^\d,:\s\-][^,:\s]*)(:(spare|missing))*$/ ) or die "INTERNAL PARSER ERROR\n";
+ # redefine the device string
+ $dev = $1;
+ # make $dev a full path name; can't validate device name yet as it
+ # might be created later on
+ unless ( $dev =~ m{^/} ) {
+ if ( $dev =~ m/^disk(\d+)\.(\d+)/ ) {
+ my $short_dev = $FAI::disks[ $1 - 1 ];
+ $dev = "/dev/$short_dev$2";
+ }
+ else {
+ $dev = "/dev/$dev";
+ }
+ }
+ # options are only valid for RAID
+ defined( $2 ) and ( $FAI::device ne "RAID" ) and die "Option $2 invalid in a non-RAID context\n";
+ if( $FAI::device eq "RAID" ) {
+ # parse all options
+ my $spare = 0;
+ my $missing = 0;
+ if( defined( $2 ) ) {
+ ( $2 =~ /spare/ ) and $spare = 1;
+ ( $2 =~ /missing/ ) and $missing = 1;
+ }
+ # each device may only appear once
+ defined( $FAI::partition_pointer->{"devices"}->{$dev} ) and
+ die "$dev is already part of the RAID volume\n";
+ # set the options
+ $FAI::partition_pointer->{"devices"}->{$dev}->{"options"} = {
+ "spare" => $spare,
+ "missing" => $missing
+ };
+ } else {
+ # create an empty hash for each device
+ $FAI::configs{$FAI::device}{"devices"}{$dev} = {};
+ }
+ }
+ 1;
+ }
+ | <error: invalid device spec "$text">
+
+ mount_options: /\S+/
+ {
+ $FAI::partition_pointer->{ "mount_options" } = $item[ 1 ];
+ }
+
+ filesystem: '-'
+ {
+ $FAI::partition_pointer->{ "filesystem" } = $item[ 1 ];
+ }
+ | 'swap'
+ {
+ $FAI::partition_pointer->{ "filesystem" } = $item[ 1 ];
+ }
+ | /^\S+/
+ {
+ ( &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: /[^;\n]*/
+ {
+ $FAI::partition_pointer->{ "fs_options" } = $item[ 1 ];
+ }
+}
+);
+
+################################################################################
+#
+# @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 <$IN> to a single string (not a list), thus $/ has to be unset
+ my $ifs = $/;
+ undef $/;
+ my $input = <$IN>;
+ $/ = $ifs;
+
+ # 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";
+}
+
+use POSIX qw(ceil floor);
+
+################################################################################
+#
+# @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 estimate_size
+{
+ my ($dev) = @_;
+
+ # 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";
+
+ # 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 = "";
+
+ # 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);
+
+ # 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 );
+ }
+
+ 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" );
+ }
+ else
+ {
+
+ # 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";
+ }
+}
+
+################################################################################
+#
+# @brief Compute the desired sizes of logical volumes
+#
+################################################################################
+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;
+
+ # the volumes that require redistribution of free space
+ my @redist_list = ();
+
+ # 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;
+
+ # start may be given in percents of the size, rewrite it to megabytes
+ $start = POSIX::floor( $vg_size * $1 / 100 ) if ( $start =~ /^(\d+)%$/ );
+
+ # end may be given in percents of the size, rewrite it to megabytes
+ $end = POSIX::ceil( $vg_size * $1 / 100 ) if ( $end =~ /^(\d+)%$/ );
+
+ # make sure that $end >= $start
+ ( $end >= $start ) or die "INTERNAL ERROR: end < start\n";
+
+ # 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 )
+ {
+
+ # write the size back to the configuration
+ $FAI::configs{$config}{"volumes"}{$lv}{"size"}{"eff_size"} = $start;
+ }
+ else
+ {
+
+ # add this volume to the redistribution list
+ push @redist_list, $lv;
+ }
+ }
+
+ # 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";
+
+ # the extension factor
+ my $redist_factor = 0;
+ $redist_factor = ( $vg_size - $min_space ) / ( $max_space - $min_space )
+ if ( $max_space > $min_space );
+
+ # update all sizes that are still ranges
+ foreach my $lv (@redist_list)
+ {
+
+ # 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;
+
+ # write the final size
+ $FAI::configs{$config}{"volumes"}{$lv}{"size"}{"eff_size"} =
+ $start + ( ( $end - $start ) * $redist_factor );
+ }
+ }
+}
+
+################################################################################
+#
+# @brief Compute the desired sizes of the partitions and test feasibility
+# thereof.
+#
+################################################################################
+sub compute_partition_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" );
+
+ # don't configure the sizes of logical volumes here
+ next if ( $config =~ /^VG_(.+)$/ );
+
+ # 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 );
+
+ # 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;
+ }
+ }
+
+ # the space required on the disk
+ my $min_req_total_space = 0;
+
+ # 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"};
+
+ # the MBR requires space, too
+ $min_req_total_space +=
+ $FAI::current_config{$disk}{"bios_sectors_per_track"} *
+ $FAI::current_config{$disk}{"sector_size"};
+ }
+
+ # 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"};
+ }
+
+ # the list of partitions that we need to find start and end bytes for
+ my @worklist = ( sort keys %{ $FAI::configs{$config}{"partitions"} } );
+
+ while ( scalar(@worklist) > 0 )
+ {
+
+ # work on the first entry of the list
+ my $part_id = $worklist[0];
+
+ # the partition $part_id must be preserved
+ if ( $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"preserve"} ==
+ 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";
+
+ ( $next_start >
+ $FAI::current_config{$disk}{"partitions"}{$part_id}{"begin_byte"} )
+ and die
+"Previous partitions overflow begin of preserved partition $part_id\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"};
+
+ # 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"};
+
+ # 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 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 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";
+
+ # 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 );
+
+ # 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"};
+
+ # set the next start to the start of the extended partition
+ $next_start =
+ $FAI::configs{$config}{"partitions"}{$part_id}{"start_byte"};
+ }
+
+ }
+
+ # 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";
+ }
+
+ # 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";
+
+ # make sure that there is only one extended partition
+ ( $extended == -1 || 1 == scalar(@worklist) )
+ or die "INTERNAL ERROR: More than 1 extended partition\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 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 );
+
+ 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"};
+ }
+
+ ( $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;
+
+ # start may be given in percents of the size
+ if ( $start =~ /^(\d+)%$/ )
+ {
+
+ # rewrite it to bytes
+ $start =
+ POSIX::floor( $FAI::current_config{$disk}{"size"} * $1 / 100 );
+ }
+ else
+ {
+
+ # it is given in megabytes, make it bytes
+ $start = $start * 1024.0 * 1024.0;
+ }
+
+ # end may be given in percents of the size
+ if ( $end =~ /^(\d+)%$/ )
+ {
+
+ # rewrite it to bytes
+ $end = POSIX::ceil( $FAI::current_config{$disk}{"size"} * $1 / 100 );
+ }
+ else
+ {
+
+ # 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";
+
+ # 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;
+
+ # 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;
+
+ # maximum useful space
+ my $max_space = 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;
+
+ # start may be given in percents of the size
+ if ( $min_size =~ /^(\d+)%$/ )
+ {
+
+ # rewrite it to bytes
+ $min_size =
+ POSIX::floor(
+ $FAI::current_config{$disk}{"size"} * $1 / 100 );
+ }
+ else
+ {
+
+ # 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+)%$/ )
+ {
+
+ # rewrite it to bytes
+ $max_size =
+ POSIX::ceil( $FAI::current_config{$disk}{"size"} * $1 / 100 );
+ }
+ else
+ {
+
+ # it is given in megabytes, make it bytes
+ $max_size *= 1024.0 * 1024.0;
+ }
+
+ # 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"};
+ }
+
+ $min_req_space += $min_size;
+ $max_space += $max_size;
+ }
+ }
+
+ # 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 );
+
+ my $available_space = $end_of_range - $next_start + 1;
+
+ # 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"};
+ }
+
+ # partition starts at where we currently are
+ $FAI::configs{$config}{"partitions"}{$part_id}{"start_byte"} =
+ $next_start;
+
+ # the end may need some alignment, depending on the disk label
+ my $end_byte = $next_start + $start - 1;
+
+ # 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"} );
+ }
+
+ # 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"};
+ }
+
+ # 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;
+
+ # then set eff_size to a proper value
+ $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"eff_size"} =
+ $start;
+
+ # 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 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}{"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";
+
+ # 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";
+ }
+
+ }
+}
+
+
+################################################################################
+#
+# @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
+# built to create any RAID devices
+#
+# The list is @FAI::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_(.+)$/ );
+
+ # 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"} };
+
+ # set proper partition types for RAID
+ foreach my $d (@devs)
+ {
+ # skip devices marked missing
+ next if( 1 ==
+ $FAI::configs{$config}{"volumes"}{$id}{"devices"}{$d}{"missing"} );
+ # only match physical partitions (this string of matchings is hopefully complete)
+ next unless( $d =~
+ m{^/dev/(cciss/c\dd\dp|ida/c\dd\dp|rd/c\dd\dp|ataraid/d\dp|sd[a-t]|hd[a-t])(\d+)$} );
+ my $disk = "/dev/$1";
+ my $part_no = $2;
+ # in case the name was /dev/cciss/c0d1p or the like, remove the trailing
+ # p to get the disk name
+ $disk =~ s/(\d)p$/$1/;
+ # make sure this device really exists (we can't check for the partition
+ # as that may be created later on
+ ( -b $disk ) or die "Specified disk $disk does not exist in this system!\n";
+ # set the raid flag
+ push @FAI::commands, "parted -s $disk set $part_no raid on";
+ }
+ # wait for udev to set up all devices
+ push @FAI::commands, "udevsettle --timeout=10";
+
+ # 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} } );
+ }
+ }
+}
+
+################################################################################
+#
+# @brief Erase the LVM signature from a list of devices that should be prestine
+# in order to avoid confusion of the lvm tools
+#
+# The list is @FAI::commands
+#
+################################################################################
+sub erase_lvm_signature
+{
+ my( $devices_aref ) = @_;
+ # first remove the dm_mod module to prevent ghost lvm volumes
+ # from existing
+# push @FAI::commands, "modprobe -r dm_mod";
+ # zero out (broken?) lvm signatures
+# push @FAI::commands, "dd if=/dev/zero of=$_ bs=1 count=1"
+# foreach ( @{$devices_aref} );
+ my $device_list = join(" ", (@{$devices_aref}) );
+ ( $FAI::debug > 0 ) and print "list of erased devices: $device_list\n";
+ push @FAI::commands, "pvremove -ff -y $device_list";
+
+ # reload module
+# push @FAI::commands, "modprobe dm_mod";
+
+}
+
+################################################################################
+#
+# @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";
+ }
+
+ # set proper partition types for LVM
+ foreach my $d (keys %{ $FAI::configs{$config}{"devices"} })
+ {
+ # only match physical partitions (this string of matchings is hopefully complete)
+ next unless( $d =~
+ m{^/dev/(cciss/c\dd\dp|ida/c\dd\dp|rd/c\dd\dp|ataraid/d\dp|sd[a-t]|hd[a-t])(\d+)$} );
+ my $disk = "/dev/$1";
+ my $part_no = $2;
+ # in case the name was /dev/cciss/c0d1p or the like, remove the trailing
+ # p to get the disk name
+ $disk =~ s/(\d)p$/$1/;
+ # make sure this device really exists (we can't check for the partition
+ # as that may be created later on
+ ( -b $disk ) or die "Specified disk $disk does not exist in this system!\n";
+ # set the lvm flag
+ push @FAI::commands, "parted -s $disk set $part_no lvm on";
+ }
+ # wait for udev to set up all devices
+ push @FAI::commands, "udevsettle --timeout=10";
+
+ # create the volume group, if it doesn't exist already
+ if ( !defined( $FAI::current_lvm_config{$vg} ) )
+ {
+
+ # create all the devices
+ my @devices = keys %{ $FAI::configs{$config}{"devices"} };
+ &FAI::erase_lvm_signature(\@devices);
+ push @FAI::commands, "pvcreate $_"
+ foreach ( @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"} } } = ();
+
+ my @new_devices = keys %new_devs;
+
+ erase_lvm_signature( \@new_devices );
+
+ # create all the devices
+ push @FAI::commands, "pvcreate $_"
+ foreach ( @new_devices );
+
+ # 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";
+ }
+
+ # 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} } );
+ }
+ }
+
+ }
+}
+
+################################################################################
+#
+# @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
+{
+
+ # loop through all configs
+ foreach my $config ( keys %FAI::configs )
+ {
+
+ # no RAID devices here
+ next if ( $config eq "RAID" );
+
+ # no LVM here
+ next if ( $config =~ /^VG_(.+)$/ );
+
+ # configure a physical device
+ ( $config =~ /^PHY_(.+)$/ ) or die "INTERNAL ERROR: Invalid config\n";
+
+ # 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"} } )
+ {
+
+ next
+ unless (
+ $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"preserve"} ==
+ 1
+ || $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"resize"} ==
+ 1 );
+
+ # 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;
+
+ }
+
+ # sort the list of preserved partitions
+ @to_preserve = sort { $a <=> $b } @to_preserve;
+
+ # 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;
+ }
+ }
+
+ # 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 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";
+ }
+
+ # 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, "parted -s $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 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 )
+ {
+ $part_type = "extended";
+ }
+ elsif ( $part_id > 4 )
+ {
+ $part_type = "logical";
+ $part_nr = 4 if ( $part_nr < 4 );
+ }
+ }
+
+ # 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,
+ "parted -s $disk mkpart $part_type ${start}B ${end}B";
+ }
+
+ # 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 );
+
+ # 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"}{"eff_size"} >
+ $FAI::current_config{$disk}{"partitions"}{$mapped_id}{"count_byte"} )
+ {
+ 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"};
+
+ # build an appropriate command
+ push @FAI::commands,
+ "parted -s $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"};
+
+ # build an appropriate command
+ push @FAI::commands,
+ "parted -s $disk resize $p ${start}B ${end}B";
+ }
+
+ # write the disklabel again to drop the partition table
+ push @FAI::commands, "parted -s $disk mklabel " . $FAI::configs{$config}{'disklabel'};
+
+ # generate the commands for creating all partitions
+ foreach
+ my $part_id ( sort keys %{ $FAI::configs{$config}{"partitions"} } )
+ {
+
+ # 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"};
+
+ # 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 )
+ {
+ $part_type = "extended";
+ }
+ elsif ( $part_id > 4 )
+ {
+ $part_type = "logical";
+ }
+ }
+
+ # build a parted command to create the partition
+ push @FAI::commands,
+ "parted -s $disk mkpart $part_type ${start}B ${end}B";
+ }
+
+ # set the bootable flag, if requested at all
+ push @FAI::commands,
+ "parted -s $disk set " . $FAI::configs{$config}{"bootable"}
+ . " boot on"
+ if ( $FAI::configs{$config}{"bootable"} > -1 );
+
+ # wait for udev to set up all devices
+ push @FAI::commands, "udevsettle --timeout=10";
+ }
+
+ # generate the commands for creating all filesystems
+ foreach my $part_id ( sort keys %{ $FAI::configs{$config}{"partitions"} } )
+ {
+
+ # 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( "parted -s $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( "parted -s $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";
+}
+
+################################################################################
+#
+# @brief this function generates the fstab file from our representation of the
+# partitions to be created.
+#
+# @reference config Reference to our representation of the partitions to be
+# created
+#
+# @return list of fstab lines
+#
+################################################################################
+sub generate_fstab
+{
+
+ # config structure is the only input
+ my ($config) = @_;
+
+ # the file to be returned, a list of lines
+ my @fstab = ();
+
+ # walk through all configured parts
+ # the order of entries is most likely wrong, it is fixed at the end
+ foreach my $c ( keys %$config )
+ {
+
+ # 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 "/" );
+
+ # 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"} = $fstab_line[0]
+ 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 = ();
+
+ # resolve the symlink to the real device
+ # and write it as the first entry
+ &execute_command_std(
+ "readlink -f /dev/$device/$l", \@fstab_line, 0 );
+
+ # remove the newline
+ chomp( $fstab_line[0] );
+
+ # make sure we got back a real device
+ ( $FAI::no_dry_run == 0 || -b $fstab_line[0] )
+ or die "Failed to resolve /dev/$device/$l\n";
+
+ # 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 "/" );
+
+ # 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"} = $fstab_line[0]
+ if ( $l_ref->{"mountpoint"} eq "/" );
+
+ # add to the swaplist, if the filesystem is swap
+ $FAI::disk_var{"SWAPLIST"} .= " " . $fstab_line[0]
+ if ( $l_ref->{"filesystem"} eq "swap" );
+ }
+ }
+ elsif ( $c eq "RAID" )
+ {
+
+ # create a line in the output file for each device
+ foreach my $r ( sort keys %{ $config->{$c}->{"volumes"} } )
+ {
+
+ # 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 "/" );
+
+ # 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" );
+ }
+ }
+ else
+ {
+ die "INTERNAL ERROR: Unexpected key $c\n";
+ }
+ }
+
+ # cleanup the swaplist (remove leading space)
+ $FAI::disk_var{"SWAPLIST"} =~ s/^\s+//;
+
+ # quote the entries of SWAPLIST
+ $FAI::disk_var{"SWAPLIST"} = '"' . $FAI::disk_var{"SWAPLIST"} . '"';
+
+ # 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" );
+
+ 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;
+}
+
+use File::Temp;
+
+################################################################################
+#
+# @brief hash, defined: errors, descriptions, actions on error
+#
+# @scalar error error
+# @scalar message our errormessage
+# @scalar stderr_regex regex to recognize the error message on stderr output of the bash
+# @scalar stdout_regex regex to recognize the error message on stdout output of the bash
+# @scalar program the program this error message can come from
+# @scalar response default action on this error.
+#
+################################################################################
+$FAI::error_codes = [
+ {
+ error => "parted_1",
+ message => "Parted produced error. Couldn't remove partition\n",
+ stderr_regex =>
+ ".*Error: Could not stat device rm - No such file or directory.*",
+ stdout_regex => "",
+ program => "parted",
+ response => "die",
+ },
+ {
+ error => "parted_2",
+ message => "Parted produced error. Could not read disk label.\n",
+ stderr_regex => ".*Error: Unable to open .* - unrecognised disk label.*",
+ stdout_regex => "",
+ program => "parted",
+ response => "warn",
+ },
+ {
+ error => "parted_3",
+ message => "Parted produced error. Could not open disk\n",
+ stderr_regex =>
+ ".*Error: Could not stat device .* - No such file or directory.*",
+ stdout_regex => "",
+ program => "parted",
+ response => "die"
+ },
+ {
+ error => "parted_4",
+ message => "parted not found\n",
+ stderr_regex =>
+ ".*(parted: command not found|/sbin/parted: No such file or directory)",
+ stdout_regex => "",
+ 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",
+ },
+];
+
+################################################################################
+#
+# @brief returns the error message associated with an error
+#
+# @param error identifier of an error
+#
+# @return our interpretation of the error as string
+#
+################################################################################
+sub get_error_message
+{
+ my ($error) = @_;
+ my @treffer = grep { $_->{error} eq "$error" } @$FAI::error_codes;
+
+ # returns the first found error message.
+ return $treffer[0]->{'message'};
+}
+
+################################################################################
+#
+# @brief gets any part of the error struct associated with an error
+#
+# @param error identifier of an error
+# @param field field of the error struct as string, example: "stderr_regex"
+#
+# @return the associated value
+#
+################################################################################
+sub get_error
+{
+ my ( $error, $field ) = @_;
+ my @treffer = grep { $_->{error} eq "$error" } @$FAI::error_codes;
+
+ # returns the first found error message.
+ return $treffer[0]->{$field};
+}
+################################################################################
+#
+# @brief execute a /bin/bash command, given as string. also catch stderr and
+# stdout, to be passed to the caller function, and also used for error
+# recognition. This execute function does execute the in the error struct
+# defined action, when an error occurs.
+#
+# @param command bash command to be executed as string
+# @reference stdout_ref reference to a list, that should contain the standard
+# output of the bash command
+#
+# @reference stderr_ref reference to a list, that should contain the standard
+# errer output of the bash command
+#
+# @return the identifier of the error
+#
+################################################################################
+sub execute_command_std #execute command with standard error handling
+{
+ my ( $command, $stdout_ref, $stderr_ref ) = @_;
+ my $err = &execute_command( $command, $stdout_ref, $stderr_ref );
+ if ( $err ne "" )
+ {
+ my $response = &get_error( $err, "response" );
+ my $message = &get_error( $err, "message" );
+
+ $response->() if ( ref($response) );
+
+ die $message if ( $response eq "die" );
+
+ warn $message if ( $response eq "warn" );
+
+ return $err;
+ }
+ return "";
+}
+
+################################################################################
+#
+# @brief execute a /bin/bash command, given as string. also catch stderr and
+# stdout, to be passed to the caller function, and also used for error
+# recognition. This caller function must handle the error.
+#
+# @param command bash command to be executed as string
+# @reference stdout_ref reference to a list, that should contain the standard
+# output of the bash command
+#
+# @reference stderr_ref reference to a list, that should contain the standard
+# error output of the bash command
+#
+# @return the identifier of the error
+#
+################################################################################
+sub execute_command
+{
+ my ( $command, $stdout_ref, $stderr_ref ) = @_;
+
+ my @stderr = ();
+ my @stdout = ();
+ my $stderr_line = "";
+ my $stdout_line = "";
+
+ #make tempfile, get perl filehandle and filename of the file
+ ( 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 ($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
+ {
+ print "would run command $command; to have them executed, use -X \n";
+ }
+
+ # read the tempfile into lists, each element of the list one line
+ @stderr = <$stderr_fh>;
+ @stdout = <$stdout_fh>;
+
+ #when closing the files, the tempfiles are removed too
+ 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
+ $stderr_line = $stderr[0] if ( scalar(@stderr) > 0 );
+
+ #see last comment
+ $stdout_line = $stdout[0] if ( scalar(@stdout) > 0 );
+
+ #if an array is passed to the function, it is filled with the stdout
+ @$stdout_ref = @stdout if ( 'ARRAY' eq ref($stdout_ref) );
+
+ #see above
+ @$stderr_ref = @stderr if ( 'ARRAY' eq ref($stderr_ref) );
+
+ #get the error, if there was any
+ foreach my $err (@$FAI::error_codes)
+ {
+ if (
+ (
+ $err->{'stdout_regex'} eq "" || $stdout_line =~ /$err->{'stdout_regex'}/
+ )
+ && ( $err->{'stderr_regex'} eq ""
+ || $stderr_line =~ /$err->{'stderr_regex'}/ )
+ && ( $err->{'program'} eq "" || $command =~ /.*$err->{'program'}.*/ )
+ )
+ {
+
+ return $err->{'error'};
+ }
+ }
+
+}
+
+# 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($config_file);
+
+# read the sizes and partition tables of all disks listed in $FAI::disks
+&FAI::get_current_disks;
+
+# 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;
+
+# 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;
+
+# for debugging purposes to print the hash structures
+use Data::Dumper;
+
+# debugging only: print the current contents of $FAI::current_config
+if ($FAI::debug)
+{
+ 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;
+}
+
+# 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 );
+
+# 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;
+}
+
Property changes on: people/michael/features/setup_harddisks_2/oo-rewrite-that-may-happen-or-not/storage-magic
___________________________________________________________________
Name: svn:executable
+ *
Copied: people/michael/features/setup_harddisks_2/setup-storage.8 (from rev 4857, trunk/man/fcopy.8)
===================================================================
--- people/michael/features/setup_harddisks_2/setup-storage.8 (rev 0)
+++ people/michael/features/setup_harddisks_2/setup-storage.8 2008-04-12 13:39:28 UTC (rev 4868)
@@ -0,0 +1,253 @@
+.\" Hey, EMACS: -*- nroff -*-
+.\" .TH setup-storage 8 "11 april 2008" "FAI 3.3"
+.\" Please adjust this date whenever revising the manpage.
+.\"
+.\" Some roff macros, for reference:
+.\" .nh disable hyphenation
+.\" .hy enable hyphenation
+.\" .ad l left justify
+.\" .ad b justify to both left and right margins
+.\" .nf disable filling
+.\" .fi enable filling
+.\" .br insert line break
+.\" .sp <n> insert n+1 empty lines
+.\" for manpage-specific macros, see man(7)
+.Dd April 11, 2008
+.Dt SETUP-STORAGE 8 SMM
+.Os Debian/GNU Linux
+.Sh NAME
+.Nm setup-storage
+.Nd automatically prepare storage devices
+.Sh SYNOPSIS
+.Nm
+.Op Fl X
+.Op Fl f Ar filename
+.Sh DESCRIPTION
+Using FAI disk_config files,
+.Nm
+computes effective partition and volume sizes and executes the necessary
+commands to configure storage devices. It manages disk drives as well as RAID
+and LVM volumes. It handles all file systems supported by
+.Xr parted 8
+as well as ntfs, but is flexible enough to be extended to further types as well.
+Once the storage devices are prepared, an appropriate
+.Xr fstab 5
+file is generated.
+.Pp
+Without the
+.Fl X
+parameter,
+.Nm
+runs in test-only mode and does not execute commands other than writing disk
+labels to a blank disk.
+.Pp
+The exit code of
+.Nm
+is 0 if all operations were performed successfully, and non-zero if an error
+occurs.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.Pp
+.It Fl X
+Really write the configuration to disk. Otherwise,
+.Nm
+runs in test-only mode.
+.Pp
+.It Fl f Ar filename
+Normally,
+.Nm
+selects an appropriate configuration from
+.Sm off
+.Ev FAI
+/disk_config/
+.Sm on
+by picking the first class from
+.EV classes
+that has an existing file.
+If, however,
+.Fl f
+is given, the configuration in
+.Ar filename
+is used.
+.El
+.Sh ENVIRONMENT
+.Nm
+will uses the following environment variables:
+.Bl -tag -width "disklist"
+.It Ev disklist
+The
+.Ev disklist
+variable must contain a newline separated list of disk drives available in the
+system. Their order matters as they may be referred to as disk1, etc. in
+disk_config.
+.It Ev debug
+If
+.Ev debug
+is set to a non-zero value, all actions and details to track the operation of
+.Nm
+are printed to stderr.
+.It Ev FAI
+The location of the config space to find the disk_config directory.
+.It Ev classes
+The list of FAI classes to determine the appropriate configuration to choose.
+.It Ev LOGDIR
+.Nm
+generates disk_var.sh and fstab (see below) in this directory.
+.El
+.Sh FILES
+If
+.Nm
+executes successfully, an
+.Xr fstab 5
+file matching the specified configuration is generated as
+.Sm off
+.Ev LOGDIR
+/fstab.
+.Sm on
+Further,
+.Sm off
+.Ev LOGDIR
+/disk_var.sh
+.Sm on
+is generated and may be sourced to get the variables
+.Ev SWAPLIST, ROOT_PARTITION, BOOT_PARTITION
+and
+.Ev BOOT_DEVICE.
+The latter two will only be set in case they
+reside on a disk drive.
+.Sh SYNTAX
+This section describes the syntax of disk_config files
+.Pp
+file ::= <lines> EOF
+.Pp
+lines ::= EOL
+ /* empty lines or whitespace only */
+ | <comment> EOL
+ | <config> EOL
+.Pp
+comment ::= #.*
+.Pp
+config ::= disk_config lvm
+ | disk_config raid
+ | disk_config end
+ | disk_config disk[[:digit:]]+( <option>)*
+ | disk_config [^[:space:]]+( <option>)*
+ /* fully qualified device-path or short form, like hda, whereby full
+ * path is assumed to be /dev/hda */
+ | <volume>
+.Pp
+option ::= /* empty */
+ | preserve_always:[[:digit:]]+(,[[:digit:]]+)*
+ /* preserve partitions -- always */
+ | preserve_reinstall:[[:digit:]]+(,[[:digit:]]+)*
+ /* preserve partitions -- unless the system is installed for the
+ first time */
+ | resize:[[:digit:]]+(,[[:digit:]]+)*
+ /* attempt to resize partitions */
+ | 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
+ */
+.Pp
+volume ::= <type> <mountpoint> <size> <filesystem> <mount_options> <fs_options>
+ | vg <name> <size>
+ /* lvm vg */
+.Pp
+type ::= primary
+ /* for physical disks only */
+ | logical
+ /* for physical disks only */
+ | raid[0156]
+ /* raid level */
+ | [^/[:space:]]+-[^/[:space:]]+
+ /* lvm logical volume: vg name and lv name*/
+.Pp
+mountpoint ::= -
+ /* do not mount */
+ | swap
+ /* swap space */
+ | /[^[:space:]]*
+ /* fully qualified path */
+.Pp
+name ::= [^/[:space:]]+
+ /* lvm volume group name */
+.Pp
+size ::= [[:digit:]]+[kMGTP%]?(-([[:digit:]]+[kMGTP%]?)?)?(:resize)?
+ /* size in kilo, mega (default), giga, tera or petabytes or %,
+ * possibly given as a range; physical
+ * partitions or lvm logical volumes only; */
+ | -[[:digit:]]+[kMGTP%]?(:resize)?
+ /* size in kilo, mega (default), giga, tera or petabytes or %,
+ * given as upper limit; physical partitions
+ * or lvm logical volumes only */
+ | [^,:[:space:]]+(:(spare|missing))*(,[^,:[:space:]]+(:(spare|missing))*)*
+ /* devices and options for a raid or lvm vg */
+
+mount_options ::= [^[:space:]]+
+.Pp
+filesystem ::= -
+ | swap
+ | [^[:space:]]
+ /* mkfs.xxx must exist */
+.Pp
+fs_options ::= .*
+ /* options appended to mkfs.xxx call */
+.Sh EXAMPLES
+# Configure the device /dev/hda
+disk_config hda preserve:6,7 disklabel:msdos bootable:3
+# preserve the 6th and the 7th partition. The disklabel is msdos, which is the default
+# for x86. Furthermore the 3rd partition is made bootable.
+primary /boot 20-100 ext3 rw
+# create a primary partition /dev/hda1 with a size between 20 and 100 MB and mount it
+# read-write as /boot; it is formatted using ext3 filesystem
+primary swap 1000 swap sw
+# /dev/hda2 will be a swap space of 1000 MB
+primary / 12000 ext3 rw -b 2048
+# /dev/hda3 should be formatted using ext3 filesystem; when calling mkfs.ext3
+# the option "-b 2048" is appended.
+logical /tmp 1000 ext3 rw,nosuid
+# create the logical partition /dev/hda5
+logical /usr preserve6 ext3 rw
+logical /var 10%- ext3 rw
+# make /dev/hda7 at least 10% of the disk size
+logical /nobackup 0- xfs rw
+# use mkfs.xfs to format the partition
+.Pp
+# Create a softRAID
+disk_config raid
+raid1 / sda1,sdd1 ext2 rw,errors=remount-ro
+# create a RAID-1 on /dev/sda1 and /dev/sdd1, format using mkfs.ext2 and mount
+# it as /
+raid0 - disk2.2,sdc1,sde1:spare:missing ext2 default
+# create a RAID-0 on the second partition of the second disk, /dev/sdc1, and
+# /dev/sde1 as a spare partition, which (may?) me missing
+.Pp
+# Simple LVM example
+disk_config sda bootable:1
+primary /boot 500 ext3 rw
+primary - 4096- - -
+
+disk_config lvm
+vg my_pv sda2
+my_pv-_swap swap 2048 swap sw
+my_pv-_root / 2048 ext3 rw
+.Sh SEE ALSO
+This program is part of FAI (Fully Automatic Installation).
+The FAI homepage is http://www.informatik.uni-koeln.de/fai.
+.Pp
+As
+.Nm
+is still beta-software being actively developed, its documentation is maintained
+in a wiki page at http://faiwiki.debian.net/index.php/Setup-storage.
+.Sh AUTHOR
+FAI is courtesy of Thomas Lange <lange at informatik.uni-koeln.de>. Michael
+Tautschnig <mt at debian.org> contributed the initial version of
+.Nm
+to replace the previous setup-harddisks, with the help of Christian Kern,
+Andreas Schuldei and Sam Vilain.
More information about the Fai-commit
mailing list