| 1 |
#!/usr/bin/perl -w |
#!/usr/bin/perl -w |
| 2 |
|
|
| 3 |
|
#********************************************************************* |
| 4 |
|
# This program is free software; you can redistribute it and/or modify |
| 5 |
|
# it under the terms of the GNU General Public License as published by |
| 6 |
|
# the Free Software Foundation; either version 2 of the License, or |
| 7 |
|
# (at your option) any later version. |
| 8 |
|
# |
| 9 |
|
# This program is distributed in the hope that it will be useful, but |
| 10 |
|
# WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 |
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 12 |
|
# General Public License for more details. |
| 13 |
|
# |
| 14 |
|
# A copy of the GNU General Public License is available as |
| 15 |
|
# `/usr/share/common-licences/GPL' in the Debian GNU/Linux distribution |
| 16 |
|
# or on the World Wide Web at http://www.gnu.org/copyleft/gpl.html. You |
| 17 |
|
# can also obtain it by writing to the Free Software Foundation, Inc., |
| 18 |
|
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA |
| 19 |
|
#********************************************************************* |
| 20 |
|
|
| 21 |
use strict; |
use strict; |
| 22 |
|
|
| 23 |
|
################################################################################ |
| 24 |
|
# |
| 25 |
|
# @file sizes.pm |
| 26 |
|
# |
| 27 |
|
# @brief Compute the size of the partitions and volumes to be created |
| 28 |
|
# |
| 29 |
|
# $Id$ |
| 30 |
|
# |
| 31 |
|
# @author Christian Kern, Michael Tautschnig |
| 32 |
|
# @date Sun Jul 23 16:09:36 CEST 2006 |
| 33 |
|
# |
| 34 |
|
################################################################################ |
| 35 |
|
|
| 36 |
package FAI; |
package FAI; |
| 37 |
|
|
| 38 |
%FAI::current_config = (); |
################################################################################ |
| 39 |
|
# |
| 40 |
|
# @brief Build an array $start,$end from ($start-$end) |
| 41 |
|
# |
| 42 |
|
# @param $rstr Range string |
| 43 |
|
# @param $size Size and unit |
| 44 |
|
# |
| 45 |
|
# @return ($start,$end) in bytes |
| 46 |
|
# |
| 47 |
|
################################################################################ |
| 48 |
|
sub make_range { |
| 49 |
|
|
| 50 |
|
use POSIX qw(ceil floor); |
| 51 |
|
|
| 52 |
|
my ($rstr, $size) = @_; |
| 53 |
|
# convert size to Bytes |
| 54 |
|
my $size_b = &FAI::convert_unit($size) * 1024.0 * 1024.0; |
| 55 |
|
# check the format of the string |
| 56 |
|
($rstr =~ /^(\d+(\.\d+)?%?)-(\d+(\.\d+)?%?)$/) or &FAI::internal_error("Invalid range"); |
| 57 |
|
my ($start, $end) = ($1, $3); |
| 58 |
|
# start may be given in percents of the size |
| 59 |
|
if ($start =~ /^(\d+(\.\d+)?)%$/) { |
| 60 |
|
# rewrite it to bytes |
| 61 |
|
$start = POSIX::floor($size_b * $1 / 100); |
| 62 |
|
} else { |
| 63 |
|
# it is given in megabytes, make it bytes |
| 64 |
|
$start = $start * 1024.0 * 1024.0; |
| 65 |
|
} |
| 66 |
|
|
| 67 |
foreach my $disk ( @FAI::disks ) |
# end may be given in percents of the size |
| 68 |
{ |
if ( $end =~ /^(\d+(\.\d+)?)%$/ ) { |
| 69 |
if( ! ( $disk =~ m{^/} ) ) |
# rewrite it to bytes |
| 70 |
{ |
$end = POSIX::ceil($size_b * $1 / 100); |
| 71 |
$disk = "/dev/" . $disk; |
} else { |
| 72 |
} |
# it is given in megabytes, make it bytes |
| 73 |
$FAI::current_config{ $disk } = { |
$end = $end * 1024.0 * 1024.0; |
| 74 |
"partitions" => {} |
} |
| 75 |
}; |
|
| 76 |
|
# make sure that $end >= $start |
| 77 |
my @parted_print = split( "\n", `/sbin/parted -s $disk unit TB print` ); |
($end >= $start) or &FAI::internal_error("end < start"); |
| 78 |
my $parted_fs_start = 0; |
|
| 79 |
my $parted_fs_end = 0; |
return ($start, $end); |
| 80 |
|
} |
| 81 |
foreach my $line ( @parted_print ) |
|
| 82 |
{ |
################################################################################ |
| 83 |
if( $line =~ /^Disk geometry/ ) |
# |
| 84 |
{ |
# @brief Estimate the size of the device $dev |
| 85 |
next; |
# |
| 86 |
|
# @param $dev Device the size of which should be determined. This may be a |
| 87 |
|
# a partition, a RAID device or an entire disk. |
| 88 |
|
# |
| 89 |
|
# @return the size of the device in megabytes |
| 90 |
|
# |
| 91 |
|
################################################################################ |
| 92 |
|
sub estimate_size { |
| 93 |
|
my ($dev) = @_; |
| 94 |
|
|
| 95 |
|
# try the entire disk first; we then use the data from the current |
| 96 |
|
# configuration; this matches in fact for than the allowable strings, but |
| 97 |
|
# this should be caught later on |
| 98 |
|
if ($dev =~ m{^/dev/(i2o/hd[a-t]|cciss/c\dd\d|ida/c\dd\d|rd/c\dd\d|ataraid/d\d|sd[a-t]|hd[a-t])$}) { |
| 99 |
|
defined ($FAI::current_config{$dev}{end_byte}) |
| 100 |
|
or die "$dev is not a valid block device\n"; |
| 101 |
|
|
| 102 |
|
# the size is known, return it |
| 103 |
|
return ($FAI::current_config{$dev}{end_byte} - |
| 104 |
|
$FAI::current_config{$dev}{begin_byte}) / (1024 * 1024); |
| 105 |
|
} |
| 106 |
|
|
| 107 |
|
# try a partition |
| 108 |
|
elsif ($dev =~ m{^(/dev/(i2o/hd[a-t]|cciss/c\dd\d|ida/c\dd\d|rd/c\dd\d|ataraid/d\d|sd[a-t]|hd[a-t]))p?(\d+)$}) { |
| 109 |
|
|
| 110 |
|
# the size is configured, return it |
| 111 |
|
defined ($FAI::configs{"PHY_$1"}{partitions}{$3}{size}{eff_size}) |
| 112 |
|
and return $FAI::configs{"PHY_$1"}{partitions}{$3}{size}{eff_size} / |
| 113 |
|
(1024 * 1024); |
| 114 |
|
|
| 115 |
|
# the size is known from the current configuration on disk, return it |
| 116 |
|
defined ($FAI::current_config{$1}{partitions}{$3}{count_byte}) |
| 117 |
|
and return $FAI::current_config{$1}{partitions}{$3}{count_byte} / |
| 118 |
|
(1024 * 1024); |
| 119 |
|
|
| 120 |
|
# the size is not known (yet?) |
| 121 |
|
die "Cannot determine size of $dev\n"; |
| 122 |
|
} |
| 123 |
|
|
| 124 |
|
# try RAID; estimations here are very limited and possible imprecise |
| 125 |
|
elsif ($dev =~ /^\/dev\/md(\d+)$/) { |
| 126 |
|
|
| 127 |
|
# the list of underlying devices |
| 128 |
|
my @devs = (); |
| 129 |
|
|
| 130 |
|
# the raid level, like raid0, raid5, linear, etc. |
| 131 |
|
my $level = ""; |
| 132 |
|
|
| 133 |
|
# let's see, whether there is a configuration of this volume |
| 134 |
|
if (defined ($FAI::configs{RAID}{volumes}{$1}{devices})) { |
| 135 |
|
@devs = keys %{ $FAI::configs{RAID}{volumes}{$1}{devices} }; |
| 136 |
|
$level = $FAI::configs{RAID}{volumes}{$1}{mode}; |
| 137 |
|
} elsif (defined ($FAI::current_raid_config{$1}{devices})) { |
| 138 |
|
@devs = $FAI::current_raid_config{$1}{devices}; |
| 139 |
|
$level = $FAI::current_raid_config{$1}{mode}; |
| 140 |
|
} else { |
| 141 |
|
die "$dev is not a known RAID device\n"; |
| 142 |
} |
} |
| 143 |
elsif( $line =~ /^Disk label type: (.*)$/ ) |
|
| 144 |
{ |
# prepend "raid", if the mode is numeric-only |
| 145 |
$FAI::current_config{ $disk } = { |
$level = "raid$level" if ($level =~ /^\d+$/); |
| 146 |
"disklabel" => $1 |
|
| 147 |
}; |
# the number of devices in the volume |
| 148 |
next; |
my $dev_count = scalar (@devs); |
| 149 |
|
|
| 150 |
|
# now do the mode-specific size estimations |
| 151 |
|
if ($level =~ /^raid[015]$/) { |
| 152 |
|
my $min_size = &estimate_size(shift @devs); |
| 153 |
|
foreach (@devs) { |
| 154 |
|
my $s = &FAI::estimate_size($_); |
| 155 |
|
$min_size = $s if ($s < $min_size); |
| 156 |
|
} |
| 157 |
|
|
| 158 |
|
return $min_size * POSIX::floor($dev_count / 2) |
| 159 |
|
if ($level eq "raid1"); |
| 160 |
|
return $min_size * $dev_count if ($level eq "raid0"); |
| 161 |
|
return $min_size * ($dev_count - 1) if ($level eq "raid5"); |
| 162 |
|
} else { |
| 163 |
|
|
| 164 |
|
# probably some more should be implemented |
| 165 |
|
die "Don't know how to estimate the size of a $level device\n"; |
| 166 |
} |
} |
| 167 |
elsif( $line =~ /^Number/ ) |
} |
| 168 |
{ |
|
| 169 |
$parted_fs_start = 0; |
# otherwise we are clueless |
| 170 |
$parted_fs_end = 0; |
else { |
| 171 |
my @chars = split( "", $line ); |
die "Cannot determine size of $dev - scheme unknown\n"; |
| 172 |
foreach my $char ( @chars ) |
} |
| 173 |
{ |
} |
| 174 |
$parted_fs_end++; |
|
| 175 |
if( $char eq "F" ) |
################################################################################ |
| 176 |
{ |
# |
| 177 |
$parted_fs_start = $parted_fs_end; |
# @brief Compute the desired sizes of logical volumes |
| 178 |
} |
# |
| 179 |
elsif( $char eq "m" && $parted_fs_start > 0 ) |
################################################################################ |
| 180 |
{ |
sub compute_lv_sizes { |
| 181 |
last; |
|
| 182 |
} |
# loop through all device configurations |
| 183 |
|
foreach my $config (keys %FAI::configs) { |
| 184 |
|
|
| 185 |
|
# for RAID or physical disks there is nothing to be done here |
| 186 |
|
next if ($config eq "RAID" || $config =~ /^PHY_./); |
| 187 |
|
($config =~ /^VG_(.+)$/) or &FAI::internal_error("invalid config entry $config"); |
| 188 |
|
my $vg = $1; # the volume group name |
| 189 |
|
|
| 190 |
|
# compute the size of the volume group; this is not exact, but should at |
| 191 |
|
# least give a rough estimation, we assume 1 % of overhead; the value is |
| 192 |
|
# stored in megabytes |
| 193 |
|
my $vg_size = 0; |
| 194 |
|
foreach my $dev (keys %{ $FAI::configs{$config}{devices} }) { |
| 195 |
|
|
| 196 |
|
# $dev may be a partition, an entire disk or a RAID device; otherwise we |
| 197 |
|
# cannot deal with it |
| 198 |
|
$vg_size += &FAI::estimate_size($dev); |
| 199 |
|
} |
| 200 |
|
|
| 201 |
|
# now subtract 1% of overhead |
| 202 |
|
$vg_size *= 0.99; |
| 203 |
|
|
| 204 |
|
# the volumes that require redistribution of free space |
| 205 |
|
my @redist_list = (); |
| 206 |
|
|
| 207 |
|
# the minimum and maximum space required in this volume group |
| 208 |
|
my $min_space = 0; |
| 209 |
|
my $max_space = 0; |
| 210 |
|
|
| 211 |
|
# set effective sizes where available |
| 212 |
|
foreach my $lv (keys %{ $FAI::configs{$config}{volumes} }) { |
| 213 |
|
# reference to the size of the current logical volume |
| 214 |
|
my $lv_size = (\%FAI::configs)->{$config}->{volumes}->{$lv}->{size}; |
| 215 |
|
# get the effective sizes (in Bytes) from the range |
| 216 |
|
my ($start, $end) = &FAI::make_range($lv_size->{range}, "${vg_size}MB"); |
| 217 |
|
# make them MB |
| 218 |
|
$start /= 1024.0 * 1024.0; |
| 219 |
|
$end /= 1024.0 * 1024.0; |
| 220 |
|
|
| 221 |
|
# increase the used space |
| 222 |
|
$min_space += $start; |
| 223 |
|
$max_space += $end; |
| 224 |
|
|
| 225 |
|
# write back the range in MB |
| 226 |
|
$lv_size->{range} = "$start-$end"; |
| 227 |
|
|
| 228 |
|
# the size is fixed |
| 229 |
|
if ($start == $end) { |
| 230 |
|
# write the size back to the configuration |
| 231 |
|
$lv_size->{eff_size} = $start; |
| 232 |
|
} else { |
| 233 |
|
|
| 234 |
|
# add this volume to the redistribution list |
| 235 |
|
push @redist_list, $lv; |
| 236 |
} |
} |
|
$parted_fs_start--; |
|
|
$parted_fs_end -= $parted_fs_start; |
|
| 237 |
} |
} |
| 238 |
else |
|
| 239 |
{ |
# test, whether the configuration fits on the volume group at all |
| 240 |
$line =~ /^(\d+)/; |
($min_space < $vg_size) |
| 241 |
my $id = $1; |
or die "Volume group $vg requires $min_space MB, but available space was estimated to be $vg_size\n"; |
| 242 |
$line =~ /^.{$parted_fs_start}(.{$parted_fs_end})/; |
|
| 243 |
my $fs = $1; |
# the extension factor |
| 244 |
$FAI::current_config{ $disk }{ "partitions" }{ $id } = { |
my $redist_factor = 0; |
| 245 |
"filesystem" => $fs |
$redist_factor = ($vg_size - $min_space) / ($max_space - $min_space) |
| 246 |
}; |
if ($max_space > $min_space); |
| 247 |
|
|
| 248 |
|
# update all sizes that are still ranges |
| 249 |
|
foreach my $lv (@redist_list) { |
| 250 |
|
|
| 251 |
|
# get the range again |
| 252 |
|
my ($start, $end) = |
| 253 |
|
&FAI::make_range($FAI::configs{$config}{volumes}{$lv}{size}{range}, "${vg_size}MB"); |
| 254 |
|
# make them MB |
| 255 |
|
$start /= 1024.0 * 1024.0; |
| 256 |
|
$end /= 1024.0 * 1024.0; |
| 257 |
|
|
| 258 |
|
# write the final size |
| 259 |
|
$FAI::configs{$config}{volumes}{$lv}{size}{eff_size} = |
| 260 |
|
$start + (($end - $start) * $redist_factor); |
| 261 |
} |
} |
| 262 |
} |
} |
| 263 |
|
} |
| 264 |
|
|
| 265 |
|
################################################################################ |
| 266 |
|
# |
| 267 |
|
# @brief Handle preserved partitions while computing the size of partitions |
| 268 |
|
# |
| 269 |
|
# @param $part_id Partition id within $config |
| 270 |
|
# @param $config Disk config |
| 271 |
|
# @param $current_disk Current config of this disk |
| 272 |
|
# @param $next_start Start of the next partition |
| 273 |
|
# @param $min_req_total_space Minimum space required on disk |
| 274 |
|
# |
| 275 |
|
# @return Updated values of ($next_start, $min_req_total_space) |
| 276 |
|
# |
| 277 |
|
################################################################################ |
| 278 |
|
sub do_partition_preserve { |
| 279 |
|
|
| 280 |
|
my ($part_id, $config, $current_disk, $next_start, $min_req_total_space) = @_; |
| 281 |
|
|
| 282 |
|
# reference to the current partition |
| 283 |
|
my $part = (\%FAI::configs)->{$config}->{partitions}->{$part_id}; |
| 284 |
|
|
| 285 |
|
# a partition that should be preserved must exist already |
| 286 |
|
defined($current_disk->{partitions}->{$part_id}) |
| 287 |
|
or die "$part_id can't be preserved, it does not exist.\n"; |
| 288 |
|
|
| 289 |
@parted_print = split( "\n", `/sbin/parted -s $disk unit B print` ); |
my $curr_part = $current_disk->{partitions}->{$part_id}; |
|
{ |
|
|
foreach my $line ( @parted_print ) |
|
|
{ |
|
|
if( $line =~ /^(\d+)*\s+(\d+)B\s+(\d+)B\s+(\d+)B/i ) |
|
|
{ |
|
| 290 |
|
|
| 291 |
$FAI::current_config{ $disk }{ "partitions" }{ $1 } = { |
($next_start > $curr_part->{begin_byte}) |
| 292 |
"begin_byte" => $2; |
and die "Previous partitions overflow begin of preserved partition $part_id\n"; |
|
"end_byte" => $3; |
|
|
"count_byte" => $4; |
|
|
}; |
|
|
} |
|
| 293 |
|
|
| 294 |
if( $line =~ /^Disk geometry for.*(\d+)B - (\d+)B/i ) |
# set the effective size to the value known already |
| 295 |
{ |
$part->{size}->{eff_size} = $curr_part->{count_byte}; |
|
$FAI::current_config{ $disk } = { |
|
|
"begin_byte" => $1; |
|
|
"end_byte" => $2; |
|
|
} |
|
|
} |
|
| 296 |
|
|
| 297 |
# if( $line =~ /^Disk label type:\s*(\w*)\s*/i ) |
# copy the start_byte and end_byte information |
| 298 |
# { |
$part->{start_byte} = $curr_part->{begin_byte}; |
| 299 |
# |
$part->{end_byte} = $curr_part->{end_byte}; |
| 300 |
# printf $1; |
|
| 301 |
# } |
# and add it to the total disk space required by this config |
| 302 |
|
$min_req_total_space += $part->{size}->{eff_size}; |
| 303 |
|
|
| 304 |
|
# set the next start |
| 305 |
|
$next_start = $part->{end_byte} + 1; |
| 306 |
|
|
| 307 |
|
# several msdos specific parts |
| 308 |
|
if ($FAI::configs{$config}{disklabel} eq "msdos") { |
| 309 |
|
|
| 310 |
|
# make sure the partition ends at a cylinder boundary |
| 311 |
|
(0 == ($curr_part->{end_byte} + 1) |
| 312 |
|
% ($current_disk->{sector_size} * |
| 313 |
|
$current_disk->{bios_sectors_per_track} * |
| 314 |
|
$current_disk->{bios_heads})) or |
| 315 |
|
die "Preserved partition $part_id does not end at a cylinder boundary\n"; |
| 316 |
|
|
| 317 |
|
# add one head of disk usage if this is a logical partition |
| 318 |
|
$min_req_total_space += $current_disk->{bios_sectors_per_track} * |
| 319 |
|
$current_disk->{sector_size} if ($part_id > 4); |
| 320 |
|
|
| 321 |
|
# extended partitions consume no space |
| 322 |
|
if ($part->{size}->{extended}) { |
| 323 |
|
|
| 324 |
|
# revert the addition of the size |
| 325 |
|
$min_req_total_space -= $part->{size}->{eff_size}; |
| 326 |
|
|
| 327 |
|
# set the next start to the start of the extended partition |
| 328 |
|
$next_start = $part->{start_byte}; |
| 329 |
} |
} |
| 330 |
} |
} |
| 331 |
|
|
| 332 |
|
# on gpt, ensure that the partition ends at a sector boundary |
| 333 |
|
if ($FAI::configs{$config}{disklabel} eq "gpt") { |
| 334 |
|
(0 == ($current_disk->{partitions}{$part_id}{end_byte} + 1) |
| 335 |
|
% $current_disk->{sector_size}) |
| 336 |
|
or die "Preserved partition $part_id does not end at a sector boundary\n"; |
| 337 |
|
} |
| 338 |
|
|
| 339 |
|
return ($next_start, $min_req_total_space); |
| 340 |
|
} |
| 341 |
|
|
| 342 |
@parted_print = split( "\n", `/sbin/parted -s $disk unit chs print` ); |
################################################################################ |
| 343 |
{ |
# |
| 344 |
foreach my $line ( @parted_print ) |
# @brief Handle extended partitions while computing the size of partitions |
| 345 |
{ |
# |
| 346 |
|
# @param $part_id Partition id within $config |
| 347 |
|
# @param $config Disk config |
| 348 |
|
# @param $current_disk Current config of this disk |
| 349 |
|
# |
| 350 |
|
################################################################################ |
| 351 |
|
sub do_partition_extended { |
| 352 |
|
|
| 353 |
if( $line =~ /^(\d+)\s+(\d+),(\d+),(\d+)\s+(\d+),(\d+),(\d+)/i ) |
my ($part_id, $config, $current_disk) = @_; |
|
{ |
|
|
$FAI::current_config{ $disk }{ "partitions" }{ $1 } = { |
|
|
"begin_cylinder" => $2; |
|
|
"begin_head" => $3; |
|
|
"begin_sector" => $4; |
|
|
"end_cylinder" => $5; |
|
|
"end_head" => $6; |
|
|
"end_sector" => $7; |
|
|
}; |
|
| 354 |
|
|
| 355 |
} |
# reference to the current partition |
| 356 |
|
my $part = (\%FAI::configs)->{$config}->{partitions}->{$part_id}; |
| 357 |
|
|
| 358 |
|
($FAI::configs{$config}{disklabel} eq "msdos") |
| 359 |
|
or die "found an extended partition on a non-msdos disklabel\n"; |
| 360 |
|
|
| 361 |
|
# ensure that it is a primary partition |
| 362 |
|
($part_id <= 4) or |
| 363 |
|
&FAI::internal_error("Extended partition wouldn't be a primary one"); |
| 364 |
|
|
| 365 |
|
my $epbr_size = $current_disk->{bios_sectors_per_track} * |
| 366 |
|
$current_disk->{sector_size}; |
| 367 |
|
|
| 368 |
|
# initialise the size and the start byte |
| 369 |
|
$part->{size}->{eff_size} = 0; |
| 370 |
|
$part->{start_byte} = -1; |
| 371 |
|
|
| 372 |
if( $line =~ /^Disk geometry for.*(\d+),(\d+),(\d+) - (\d+),(\d+),(\d+)/i ) |
foreach my $p (sort { $a <=> $b } keys %{ $FAI::configs{$config}{partitions} }) { |
| 373 |
{ |
next if ($p < 5); |
| 374 |
|
|
| 375 |
|
$part->{start_byte} = $FAI::configs{$config}{partitions}{$p}{start_byte} - |
| 376 |
|
$epbr_size if (-1 == $part->{start_byte}); |
| 377 |
|
|
| 378 |
|
$part->{size}->{eff_size} += $FAI::configs{$config}{partitions}{$p}{size}{eff_size} + |
| 379 |
|
$epbr_size; |
| 380 |
|
|
| 381 |
|
$part->{end_byte} = $FAI::configs{$config}{partitions}{$p}{end_byte}; |
| 382 |
|
} |
| 383 |
|
|
| 384 |
|
($part->{size}->{eff_size} > 0) |
| 385 |
|
or die "Extended partition has a size of 0\n"; |
| 386 |
|
} |
| 387 |
|
|
| 388 |
|
################################################################################ |
| 389 |
|
# |
| 390 |
|
# @brief Handle all other partitions while computing the size of partitions |
| 391 |
|
# |
| 392 |
|
# @param $part_id Partition id within $config |
| 393 |
|
# @param $config Disk config |
| 394 |
|
# @param $current_disk Current config of this disk |
| 395 |
|
# @param $next_start Start of the next partition |
| 396 |
|
# @param $min_req_total_space Minimum space required on disk |
| 397 |
|
# @param $worklist Reference to the remaining partitions |
| 398 |
|
# |
| 399 |
|
# @return Updated values of ($next_start, $min_req_total_space) |
| 400 |
|
# |
| 401 |
|
################################################################################ |
| 402 |
|
sub do_partition_real { |
| 403 |
|
|
| 404 |
|
my ($part_id, $config, $current_disk, $next_start, $min_req_total_space, |
| 405 |
|
$worklist) = @_; |
| 406 |
|
|
| 407 |
|
# reference to the current partition |
| 408 |
|
my $part = (\%FAI::configs)->{$config}->{partitions}->{$part_id}; |
| 409 |
|
|
| 410 |
$FAI::current_config{ $disk } = { |
my ($start, $end) = &FAI::make_range($part->{size}->{range}, |
| 411 |
"begin_cylinder" => $1; |
$current_disk->{size} . "B"); |
| 412 |
"begin_head" => $2; |
|
| 413 |
"begin_sector" => $3; |
# check, whether the size is fixed |
| 414 |
"end_cylinder" => $4; |
if ($end != $start) { |
| 415 |
"end_head" => $5; |
|
| 416 |
"end_sector" => $6; |
# the end of the current range (may be the end of the disk or some |
| 417 |
|
# preserved partition or an ntfs volume to be resized) |
| 418 |
|
my $end_of_range = -1; |
| 419 |
|
|
| 420 |
|
# minimum space required by all partitions, i.e., the lower ends of the |
| 421 |
|
# ranges |
| 422 |
|
# $min_req_space counts up to the next preserved partition or the |
| 423 |
|
# end of the disk |
| 424 |
|
my $min_req_space = 0; |
| 425 |
|
|
| 426 |
|
# maximum useful space |
| 427 |
|
my $max_space = 0; |
| 428 |
|
|
| 429 |
|
# inspect all remaining entries in the worklist |
| 430 |
|
foreach my $p (@{$worklist}) { |
| 431 |
|
|
| 432 |
|
# we have found the delimiter |
| 433 |
|
if ($FAI::configs{$config}{partitions}{$p}{size}{preserve} || |
| 434 |
|
($FAI::configs{$config}{partitions}{$p}{size}{resize} && |
| 435 |
|
($current_disk->{partitions}->{$p}->{filesystem} eq "ntfs"))) { |
| 436 |
|
$end_of_range = $current_disk->{partitions}->{$p}->{begin_byte}; |
| 437 |
|
|
| 438 |
|
# logical partitions require the space for the EPBR to be left |
| 439 |
|
# out |
| 440 |
|
if (($FAI::configs{$config}{disklabel} eq "msdos") |
| 441 |
|
&& ($p > 4)) { |
| 442 |
|
$end_of_range -= $current_disk->{bios_sectors_per_track} * |
| 443 |
|
$current_disk->{sector_size}; |
| 444 |
|
} |
| 445 |
|
last; |
| 446 |
|
} elsif ($FAI::configs{$config}{partitions}{$p}{size}{extended}) { |
| 447 |
|
next; |
| 448 |
|
} else { |
| 449 |
|
my ($min_size, $max_size) = &FAI::make_range( |
| 450 |
|
$FAI::configs{$config}{partitions}{$p}{size}{range}, |
| 451 |
|
$current_disk->{size} . "B"); |
| 452 |
|
|
| 453 |
|
# logical partitions require the space for the EPBR to be left |
| 454 |
|
# out |
| 455 |
|
if (($FAI::configs{$config}{disklabel} eq "msdos") |
| 456 |
|
&& ($p > 4)) { |
| 457 |
|
$min_size += $current_disk->{bios_sectors_per_track} * |
| 458 |
|
$current_disk->{sector_size}; |
| 459 |
|
$max_size += $current_disk->{bios_sectors_per_track} * |
| 460 |
|
$current_disk->{sector_size}; |
| 461 |
} |
} |
| 462 |
|
|
| 463 |
|
$min_req_space += $min_size; |
| 464 |
|
$max_space += $max_size; |
| 465 |
|
} |
| 466 |
} |
} |
| 467 |
|
|
| 468 |
|
# set the end if we have reached the end of the disk |
| 469 |
|
$end_of_range = $current_disk->{end_byte} if (-1 == $end_of_range); |
| 470 |
|
|
| 471 |
|
my $available_space = $end_of_range - $next_start + 1; |
| 472 |
|
|
| 473 |
|
# the next boundary is closer than the minimal space that we need |
| 474 |
|
($available_space < $min_req_space) |
| 475 |
|
and die "Insufficient space available for partition $part_id\n"; |
| 476 |
|
|
| 477 |
|
# the new size |
| 478 |
|
my $scaled_size = $end; |
| 479 |
|
$scaled_size = POSIX::floor(($end - $start) * |
| 480 |
|
(($available_space - $min_req_space) / |
| 481 |
|
($max_space - $min_req_space))) + $start |
| 482 |
|
if ($max_space > $available_space); |
| 483 |
|
|
| 484 |
|
($scaled_size >= $start) |
| 485 |
|
or &FAI::internal_error("scaled size is smaller than the desired minimum"); |
| 486 |
|
|
| 487 |
|
$start = $scaled_size; |
| 488 |
|
$end = $start; |
| 489 |
} |
} |
|
} |
|
| 490 |
|
|
| 491 |
|
# now we compute the effective locations on the disk |
| 492 |
|
# msdos specific offset for logical partitions |
| 493 |
|
if (($FAI::configs{$config}{disklabel} eq "msdos") |
| 494 |
|
&& ($part_id > 4)) { |
| 495 |
|
|
| 496 |
|
# add one head of disk usage if this is a logical partition |
| 497 |
|
$min_req_total_space += $current_disk->{bios_sectors_per_track} * |
| 498 |
|
$current_disk->{sector_size}; |
| 499 |
|
|
| 500 |
|
# move the start byte as well |
| 501 |
|
$next_start += $current_disk->{bios_sectors_per_track} * |
| 502 |
|
$current_disk->{sector_size}; |
| 503 |
|
} |
| 504 |
|
|
| 505 |
|
# partition starts at where we currently are, or remains fixed in case of |
| 506 |
|
# resized ntfs |
| 507 |
|
if ($FAI::configs{$config}{partitions}{$part_id}{size}{resize} && |
| 508 |
|
($current_disk->{partitions}->{$part_id}->{filesystem} eq "ntfs")) { |
| 509 |
|
($next_start <= $current_disk->{partitions}->{$part_id}->{begin_byte}) |
| 510 |
|
or die "Cannot preserve start byte of ntfs volume on partition $part_id, space before it is too small\n"; |
| 511 |
|
$next_start = $current_disk->{partitions}->{$part_id}->{begin_byte}; |
| 512 |
|
} |
| 513 |
|
$FAI::configs{$config}{partitions}{$part_id}{start_byte} = |
| 514 |
|
$next_start; |
| 515 |
|
|
| 516 |
|
# the end may need some alignment, depending on the disk label |
| 517 |
|
my $end_byte = $next_start + $start - 1; |
| 518 |
|
|
| 519 |
|
# on msdos, ensure that the partition ends at a cylinder boundary |
| 520 |
|
if ($FAI::configs{$config}{disklabel} eq "msdos") { |
| 521 |
|
$end_byte -= |
| 522 |
|
($end_byte + 1) % ($current_disk->{sector_size} * |
| 523 |
|
$current_disk->{bios_sectors_per_track} * |
| 524 |
|
$current_disk->{bios_heads}); |
| 525 |
|
} |
| 526 |
|
|
| 527 |
|
# on gpt, ensure that the partition ends at a sector boundary |
| 528 |
|
if ($FAI::configs{$config}{disklabel} eq "gpt") { |
| 529 |
|
$end_byte -= |
| 530 |
|
($end_byte + 1) % $current_disk->{sector_size}; |
| 531 |
|
} |
| 532 |
|
|
| 533 |
|
# set $start and $end to the effective values |
| 534 |
|
$start = $end_byte - $next_start + 1; |
| 535 |
|
$end = $start; |
| 536 |
|
|
| 537 |
|
# write back the size spec in bytes |
| 538 |
|
$part->{size}->{range} = $start . "-" . $end; |
| 539 |
|
|
| 540 |
|
# then set eff_size to a proper value |
| 541 |
|
$part->{size}->{eff_size} = $start; |
| 542 |
|
|
| 543 |
|
# write the end byte to the configuration |
| 544 |
|
$part->{end_byte} = $end_byte; |
| 545 |
|
|
| 546 |
foreach my $config ( keys %FAI::configs ) |
# and add it to the total disk space required by this config |
| 547 |
|
$min_req_total_space += $part->{size}->{eff_size}; |
| 548 |
|
|
| 549 |
|
# set the next start |
| 550 |
|
$next_start = $part->{end_byte} + 1; |
| 551 |
|
|
| 552 |
|
return ($next_start, $min_req_total_space); |
| 553 |
|
} |
| 554 |
|
|
| 555 |
|
################################################################################ |
| 556 |
|
# |
| 557 |
|
# @brief Compute the desired sizes of the partitions and test feasibility |
| 558 |
|
# thereof. |
| 559 |
|
# |
| 560 |
|
################################################################################ |
| 561 |
|
sub compute_partition_sizes |
| 562 |
{ |
{ |
| 563 |
if( $config eq "RAID" || $config =~ /^VG_/ ) |
|
| 564 |
{ |
# loop through all device configurations |
| 565 |
next; |
foreach my $config (keys %FAI::configs) { |
| 566 |
} |
|
| 567 |
elsif( $config =~ /^PHY_(.*)$/ ) |
# for RAID or LVM, there is nothing to be done here |
| 568 |
{ |
next if ($config eq "RAID" || $config =~ /^VG_./); |
| 569 |
foreach my $part_id ( keys $FAI::configs{ $config }{ "partitions" } ) |
($config =~ /^PHY_(.+)$/) or &FAI::internal_error("invalid config entry $config"); |
| 570 |
{ |
# nothing to be done, if this is a configuration for a virtual disk |
| 571 |
print "$part_id configured\n"; |
next if $FAI::configs{$config}{virtual}; |
| 572 |
} |
my $disk = $1; # the device name of the disk |
| 573 |
} |
# test, whether $disk is a block special device |
| 574 |
else |
(-b $disk) or die "$disk is not a valid device name\n"; |
| 575 |
{ |
# reference to the current disk config |
| 576 |
die "Internal error (invalid config entry).\n"; |
my $current_disk = $FAI::current_config{$disk}; |
| 577 |
} |
|
| 578 |
|
# at various points the following code highly depends on the desired disk label! |
| 579 |
|
# initialise variables |
| 580 |
|
# the id of the extended partition to be created, if required |
| 581 |
|
my $extended = -1; |
| 582 |
|
|
| 583 |
|
# the id of the current extended partition, if any; this setup only caters |
| 584 |
|
# for a single existing extended partition! |
| 585 |
|
my $current_extended = -1; |
| 586 |
|
|
| 587 |
|
# find the first existing extended partition |
| 588 |
|
foreach my $part_id (sort { $a <=> $b } keys %{ $current_disk->{partitions} }) { |
| 589 |
|
if ($current_disk->{partitions}->{$part_id}->{is_extended}) { |
| 590 |
|
$current_extended = $part_id; |
| 591 |
|
last; |
| 592 |
|
} |
| 593 |
|
} |
| 594 |
|
|
| 595 |
|
# the space required on the disk |
| 596 |
|
my $min_req_total_space = 0; |
| 597 |
|
|
| 598 |
|
# the start byte for the next partition |
| 599 |
|
my $next_start = 0; |
| 600 |
|
|
| 601 |
|
# on msdos disk labels, the first partitions starts at head #1 |
| 602 |
|
if ($FAI::configs{$config}{disklabel} eq "msdos") { |
| 603 |
|
$next_start = $current_disk->{bios_sectors_per_track} * |
| 604 |
|
$current_disk->{sector_size}; |
| 605 |
|
|
| 606 |
|
# the MBR requires space, too |
| 607 |
|
$min_req_total_space += $current_disk->{bios_sectors_per_track} * |
| 608 |
|
$current_disk->{sector_size}; |
| 609 |
|
} |
| 610 |
|
|
| 611 |
|
# on GPT disk labels the first 34 and last 34 sectors must be left alone |
| 612 |
|
if ($FAI::configs{$config}{disklabel} eq "gpt") { |
| 613 |
|
$next_start = 34 * $current_disk->{sector_size}; |
| 614 |
|
|
| 615 |
|
# modify the disk to claim the space for the second partition table |
| 616 |
|
$current_disk->{end_byte} -= 34 * $current_disk->{sector_size}; |
| 617 |
|
|
| 618 |
|
# the space required by the GPTs |
| 619 |
|
$min_req_total_space += 2 * 34 * $current_disk->{sector_size}; |
| 620 |
|
} |
| 621 |
|
|
| 622 |
|
# the list of partitions that we need to find start and end bytes for |
| 623 |
|
my @worklist = (sort { $a <=> $b } keys %{ $FAI::configs{$config}{partitions} }); |
| 624 |
|
|
| 625 |
|
while (scalar (@worklist)) |
| 626 |
|
{ |
| 627 |
|
|
| 628 |
|
# work on the first entry of the list |
| 629 |
|
my $part_id = $worklist[0]; |
| 630 |
|
# reference to the current partition |
| 631 |
|
my $part = (\%FAI::configs)->{$config}->{partitions}->{$part_id}; |
| 632 |
|
|
| 633 |
|
# the partition $part_id must be preserved |
| 634 |
|
if ($part->{size}->{preserve}) { |
| 635 |
|
($next_start, $min_req_total_space) = &FAI::do_partition_preserve($part_id, |
| 636 |
|
$config, $current_disk, $next_start, $min_req_total_space); |
| 637 |
|
|
| 638 |
|
# partition done |
| 639 |
|
shift @worklist; |
| 640 |
|
} |
| 641 |
|
|
| 642 |
|
# msdos specific: deal with extended partitions |
| 643 |
|
elsif ($part->{size}->{extended}) { |
| 644 |
|
# make sure that there is only one extended partition |
| 645 |
|
($extended == -1 || 1 == scalar (@worklist)) |
| 646 |
|
or &FAI::internal_error("More than 1 extended partition"); |
| 647 |
|
|
| 648 |
|
# set the local variable to this id |
| 649 |
|
$extended = $part_id; |
| 650 |
|
|
| 651 |
|
# the size cannot be determined now, push it to the end of the |
| 652 |
|
# worklist; the check against $extended being == -1 ensures that |
| 653 |
|
# there is no indefinite loop |
| 654 |
|
if (scalar (@worklist) > 1) { |
| 655 |
|
push @worklist, shift @worklist; |
| 656 |
|
next; |
| 657 |
|
} |
| 658 |
|
|
| 659 |
|
# determine the size of the extended partition |
| 660 |
|
&FAI::do_partition_extended($part_id, $config, $current_disk); |
| 661 |
|
|
| 662 |
|
# partition done |
| 663 |
|
shift @worklist; |
| 664 |
|
} else { |
| 665 |
|
($next_start, $min_req_total_space) = &FAI::do_partition_real($part_id, |
| 666 |
|
$config, $current_disk, $next_start, $min_req_total_space, \@worklist); |
| 667 |
|
|
| 668 |
|
# partition done |
| 669 |
|
shift @worklist; |
| 670 |
|
} |
| 671 |
|
} |
| 672 |
|
|
| 673 |
|
# check, whether there is sufficient space on the disk |
| 674 |
|
($min_req_total_space > $current_disk->{size}) |
| 675 |
|
and die "Disk $disk is too small - at least $min_req_total_space bytes are required\n"; |
| 676 |
|
|
| 677 |
|
# make sure, extended partitions are only created on msdos disklabels |
| 678 |
|
($FAI::configs{$config}{disklabel} ne "msdos" && $extended > -1) |
| 679 |
|
and &FAI::internal_error("extended partitions are not supported by this disklabel"); |
| 680 |
|
|
| 681 |
|
# ensure that we have done our work |
| 682 |
|
(defined ($FAI::configs{$config}{partitions}{$_}{start_byte}) |
| 683 |
|
&& defined ($FAI::configs{$config}{partitions}{$_}{end_byte})) |
| 684 |
|
or &FAI::internal_error("start or end of partition $_ not set") |
| 685 |
|
foreach (sort { $a <=> $b } keys %{ $FAI::configs{$config}{partitions} }); |
| 686 |
|
} |
| 687 |
} |
} |
| 688 |
|
|
| 689 |
1; |
1; |