#!/usr/bin/perl -w #********************************************************************* # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # A copy of the GNU General Public License is available as # `/usr/share/common-licences/GPL' in the Debian GNU/Linux distribution # or on the World Wide Web at http://www.gnu.org/copyleft/gpl.html. You # can also obtain it by writing to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #********************************************************************* use strict; ################################################################################ # # @file shdd2-sizes # # @brief A set of functions to obtain the current partition table and to # compute the size of the partitions to be created # # $Id$ # # @author Christian Kern, Michael Tautschnig # @date Sun Jul 23 16:09:36 CEST 2006 # ################################################################################ use POSIX qw(ceil floor); package FAI; ################################################################################ # # @brief Collect the current partition information from all disks listed both # in $FAI::disks and $FAI::configs{PHY_} # ################################################################################ sub get_current_disks { # backup value of $ENV{"NO_DRY_RUN"} my $no_dry_run = ""; defined( $ENV{"NO_DRY_RUN"} ) and $no_dry_run = $ENV{"NO_DRY_RUN"}; # obtain the current state of all disks foreach my $disk (@FAI::disks) { # create full paths ( $disk =~ m{^/} ) or $disk = "/dev/$disk"; # make sure, $disk is a proper block device ( -b $disk ) or die "$disk is not a block special device!\n"; # skip this disk, if it is not listed in $FAI::configs defined( $FAI::configs{"PHY_$disk"} ) or next; # initialise the hash $FAI::current_config{$disk}{"partitions"} = {}; # the list to hold the output of parted commands as parsed below my @parted_print = (); # set NO_DRY_RUN to perform read-only commands always $ENV{"NO_DRY_RUN"} = "1"; # try to obtain the partition table for $disk # it might fail with parted_2 in case the disk has no partition table my $error = &FAI::execute_command( $FAI::system_commands{"parted"} . " $disk unit TiB print", \@parted_print, 0 ); # reset NO_DRY_RUN $ENV{"NO_DRY_RUN"} = $no_dry_run; # parted_2 happens when the disk has no disk label, because parted then # provides no information about the disk if ( $error eq "parted_2" ) { # write the disk label as configured $error = &FAI::execute_command( $FAI::system_commands{"parted"} . " $disk mklabel " . $FAI::configs{$disk}{"disklabel"} ); # set NO_DRY_RUN to perform read-only commands always $ENV{"NO_DRY_RUN"} = "1"; # retry partition-table print $error = &FAI::execute_command( $FAI::system_commands{"parted"} . " $disk unit TiB print", \@parted_print, 0 ); # reset NO_DRY_RUN $ENV{"NO_DRY_RUN"} = $no_dry_run; } # 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 # 6 14715058176B 30449986559B 15734928384B logical ext3 # 7 30450018816B 32547432959B 2097414144B logical ext3 # 8 32547465216B 82343278079B 49795812864B logical ext3 # # As shown above, the file system might be blank, if its type is not known # to parted. Thus the exact columns of "File system" have to be extracted # These two variables keep the indices my $parted_fs_before = 0; my $parted_fs_len = 0; # Parse the output line by line foreach my $line (@parted_print) { # print the line read - for debugging purposes only ( $FAI::debug > 0 ) and print "$line"; # now we test line by line - some of them may be ignored if ( $line =~ /^Disk / || $line =~ /^\s*$/ || $line =~ /^WARNING: You are not superuser/ || $line =~ /^Sector / ) { next; } # read and store the current disk label elsif ( $line =~ /^Partition Table: (.*)$/ ) { $FAI::current_config{$disk}{"disklabel"} = $1; } # the line containing the table headers elsif ( $line =~ /^(Number.*\s+)(File system\s+)\S+/ ) { # the number of characters before File system $parted_fs_before = length($1) + 1; # the length of the File system column $parted_fs_len = length($2); } # one of the partitions else { # we must have seen the header, otherwise probably the format has # changed ( $parted_fs_len > 0 ) or die "INTERNAL ERROR: Table header not seen yet\n"; # get the partition number $line =~ /^\s*(\d+)/; my $id = $1; # extract the set of characters $line =~ /^.{$parted_fs_before}(.{$parted_fs_len})/; my $fs = $1; # remove any trailing space $fs =~ s/\s*$//g; # store the information in the hash $FAI::current_config{$disk}{"partitions"}{$id}{"filesystem"} = $fs; } } # set NO_DRY_RUN to perform read-only commands always $ENV{"NO_DRY_RUN"} = "1"; # reset the output list @parted_print = (); # obtain the partition table using bytes as units # TODO: when to use _std, when should one use execute_command my $error = &FAI::execute_command_std( "$FAI::system_commands{'parted'}} $disk unit B print", \@parted_print, 0 ); # reset NO_DRY_RUN $ENV{"NO_DRY_RUN"} = $no_dry_run; # check, whether an error has occured # TODO: is this necessary? if ( $error ne "" ) { my $response = &FAI::get_error( $error, "response" ); ( $response eq "die" ) and die &FAI::get_error( $error, "message" ); ( $response eq "warn" ) and warn &FAI::get_error( $error, "message" ); } # Parse the output of the byte-wise partition table foreach my $line (@parted_print) { # The size of the disk if ( $line =~ /^Disk .*: (\d+)B$/i ) { $FAI::current_config{$disk}{"begin_byte"} = 0; $FAI::current_config{$disk}{"end_byte"} = $1; } # One of the partition lines, see above example next unless ( $line =~ /^\s*(\d+)*\s+(\d+)B\s+(\d+)B\s+(\d+)B(\s+(primary|logical|extended))?/i ); # set the corresponding entries $FAI::current_config{$disk}{"partitions"}{$1}{"begin_byte"} = $2; $FAI::current_config{$disk}{"partitions"}{$1}{"end_byte"} = $3; $FAI::current_config{$disk}{"partitions"}{$1}{"count_byte"} = $4; # is_extended defaults to false/0 $FAI::current_config{$disk}{"partitions"}{$1}{"is_extended"} = 0; # but may be true/1 on msdos disk labels ( ( $FAI::current_config{$disk}{"disklabel"} eq "msdos" ) && ( $6 eq "extended" ) ) and $FAI::current_config{$disk}{"partitions"}{$1}{"is_extended"} = 1; } # set NO_DRY_RUN to perform read-only commands always $ENV{"NO_DRY_RUN"} = "1"; # reset the output list @parted_print = (); # obtain the partition table using bytes as units # TODO: when to use _std, when should one use execute_command my $error = &FAI::execute_command_std( "$FAI::system_commands{'parted'}} $disk unit chs print", \@parted_print, 0 ); # reset NO_DRY_RUN $ENV{"NO_DRY_RUN"} = $no_dry_run; # check, whether an error has occured # TODO: is this necessary? if ( $error ne "" ) { my $response = &FAI::get_error( $error, "response" ); ( $response eq "die" ) and die &FAI::get_error( $error, "message" ); ( $response eq "warn" ) and warn &FAI::get_error( $error, "message" ); } # Parse the output of the CHS partition table foreach my $line (@parted_print) { # The partition geometry if ( $line =~ /^\s*(\d+)\s+(\d+),(\d+),(\d+)\s+(\d+),(\d+),(\d+)/i ) { $FAI::current_config{$disk}{"partitions"}{$1}{"begin_cylinder"} = $2; $FAI::current_config{$disk}{"partitions"}{$1}{"begin_head"} = $3; $FAI::current_config{$disk}{"partitions"}{$1}{"begin_sector"} = $4; $FAI::current_config{$disk}{"partitions"}{$1}{"end_cylinder"} = $5; $FAI::current_config{$disk}{"partitions"}{$1}{"end_head"} = $6; $FAI::current_config{$disk}{"partitions"}{$1}{"end_sector"} = $7; } # The disk geometry if ( $line =~ /^Disk .*: (\d+),(\d+),(\d+)$/i ) { $FAI::current_config{$disk}{"begin_cylinder"} = 0; $FAI::current_config{$disk}{"begin_head"} = 0; $FAI::current_config{$disk}{"begin_sector"} = 0; $FAI::current_config{$disk}{"end_cylinder"} = $1; $FAI::current_config{$disk}{"end_head"} = $2; $FAI::current_config{$disk}{"end_sector"} = $3; } } } } ################################################################################ # # @brief Compute the desired sizes of the partitions and test feasibility # thereof. # ################################################################################ sub compute_sizes { # TODO MT CONT here # loop through all device configurations foreach my $config ( keys %FAI::configs ) { if ( $config eq "RAID" || $config =~ /^VG_/ ) { # TODO compute the sizes of lvms, RAIDs next; } # device is an effective disk elsif ( $config =~ /^PHY_(.*)$/ ) { # initialise variables # the id of the extended partition to be created, if required my $extended = -1; # the device name of the disk my $disk = $1; # remaining free space to be shared by those partitions whose size is a # range my $redist_space = 0; # minimum space required by all partitions, i.e., the lower ends of the # ranges TODO ??? my $min_req_space = 0; # TODO ??? my $min_req_total_space = 0; # TODO ??? my @redist_list = (); # the multiplier for the amount added to the lower bounds of the ranges my $redist_factor = 0; # TODO ??? my $range_start = 0; # loop through all configured partitions in a sorted manner foreach my $part_id ( sort keys %{ $FAI::configs{$config}{"partitions"} } ) { # find/handle the extended partition, if any if ( $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"extended"} == 1 ) { # make sure that there is only one extended partition ( $extended == -1 ) 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; # initialise the size of the extended partition to 0 $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"eff_size"} = 0; } # the partition $pard_id need not be preserved # this is the main part of this function elsif ( $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"preserve"} == 0 ) { # make the size specification is a range (even though it might be # something like x-x) ( $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"range"} =~ /^(\d+%?)-(\d+%?)$/ ) or die "INTERNAL ERROR: Invalid range\n"; my $start = $1; my $end = $2; if ( $start =~ /^(\d+)%$/ ) { $start = POSIX::floor( $FAI::current_config{$disk}{"end_byte"} * $1 / 100 ); } else { $start = $start * 1024.0 * 1024.0; } if ( $end =~ /^(\d+)%$/ ) { $end = POSIX::ceil( $FAI::current_config{$disk}{"end_byte"} * $1 / 100 ); } else { $end = $end * 1024.0 * 1024.0; } $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"range"} = $start . "-" . $end; if ( $end == $start ) { $FAI::configs{$config}{"partitions"}{$part_id}{"size"} {"eff_size"} = $start; } else { $FAI::configs{$config}{"partitions"}{$part_id}{"size"} {"eff_size"} = -1; $redist_space += $end - $start; } $min_req_space += $start; $min_req_total_space += $start; } else { if ( !defined( $FAI::current_config{$disk}{"partitions"}{$part_id} ) ) { die "$part_id can't be preserved, it does not exist.\n"; } $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"eff_size"} = $FAI::current_config{$disk}{"partitions"}{$part_id}{"count_byte"}; $min_req_total_space += $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"eff_size"}; if ( scalar(@redist_list) > 0 ) { my $redist_factor = ( $FAI::current_config{$disk}{"partitions"}{$part_id} {"begin_byte"} - 1 - $range_start - $min_req_space ) / $redist_space; print "redist factor is $redist_factor\n"; foreach my $part_id (@redist_list) { ( $FAI::configs{$config}{"partitions"}{$part_id}{"size"} {"eff_size"} == -1 ) or die "internal error\n"; if ( $FAI::configs{$config}{"partitions"}{$part_id}{"size"} {"range"} =~ /^(\d+%?)-(\d+%?)$/ ) { my $start = $1; my $end = $2; $FAI::configs{$config}{"partitions"}{$part_id}{"size"} {"eff_size"} = POSIX::floor( $start + ( ( $end - $start ) * $redist_factor ) ); } else { die "invalid range\n"; } } } @redist_list = (); $range_start = $FAI::current_config{$disk}{"partitions"}{$part_id}{"end_byte"} + 1; $redist_space = 0; $min_req_space = 0; } } if ( scalar(@redist_list) > 0 ) { my $redist_factor = ( $FAI::current_config{$disk}{"end_byte"} - 1 - $range_start - $min_req_space ) / $redist_space; print "redist factor is $redist_factor\n"; foreach my $part_id (@redist_list) { ( $FAI::configs{$config}{"partitions"}{$part_id}{"size"} {"eff_size"} == -1 ) or die "internal error\n"; if ( $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"range"} =~ /^(\d+%?)-(\d+%?)$/ ) { my $start = $1; my $end = $2; $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"eff_size"} = POSIX::floor( $start + ( ( $end - $start ) * $redist_factor ) ); } else { die "invalid range\n"; } } } if ( $min_req_total_space > $FAI::current_config{$disk}{"end_byte"} ) { die "Disk is too small - at least $min_req_space is required\n"; } if ( $FAI::configs{$config}{"disklabel"} ne "msdos" && $extended > -1 ) { die "extended partitions are not supported by this disklabel\n"; } if ( $FAI::configs{$config}{"disklabel"} eq "msdos" && $extended > -1 ) { my $extended_size = 0; foreach my $part_id ( sort keys %{ $FAI::configs{$config}{"partitions"} } ) { next if ( $part_id <= 4 ); $extended_size += $FAI::configs{$config}{"partitions"}{$part_id}{"size"}{"eff_size"}; } $FAI::configs{$config}{"partitions"}{$extended}{"size"}{"eff_size"} = $extended_size; } } else { die "Internal error (invalid config entry $config).\n"; } } } 1;