/[fai]/people/michael/features/setup_harddisks_2/implementation/lib/sizes.pm
ViewVC logotype

Contents of /people/michael/features/setup_harddisks_2/implementation/lib/sizes.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 4859 - (show annotations) (download)
Tue Apr 8 15:30:31 2008 UTC (5 years, 1 month ago) by mt
File size: 23428 byte(s)
bug fixing:
- sort partition ids _numerically_ - always.
- when creating or rebuilding partitions, set a proper partition id as well
1 #!/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;
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;
37
38 ################################################################################
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 # end may be given in percents of the size
68 if ( $end =~ /^(\d+(\.\d+)?)%$/ ) {
69 # rewrite it to bytes
70 $end = POSIX::ceil($size_b * $1 / 100);
71 } else {
72 # it is given in megabytes, make it bytes
73 $end = $end * 1024.0 * 1024.0;
74 }
75
76 # make sure that $end >= $start
77 ($end >= $start) or &FAI::internal_error("end < start");
78
79 return ($start, $end);
80 }
81
82 ################################################################################
83 #
84 # @brief Estimate the size of the device $dev
85 #
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 =~ /^\/dev\/[sh]d[a-z]$/) {
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 =~ /^(\/dev\/[sh]d[a-z])(\d+)$/) {
109
110 # the size is configured, return it
111 defined ($FAI::configs{"PHY_$1"}{partitions}{$2}{size}{eff_size})
112 and return $FAI::configs{"PHY_$1"}{partitions}{$2}{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}{$2}{count_byte})
117 and return $FAI::current_config{$1}{partitions}{$2}{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
144 # prepend "raid", if the mode is numeric-only
145 $level = "raid$level" if ($level =~ /^\d+$/);
146
147 # the number of devices in the volume
148 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 }
168
169 # otherwise we are clueless
170 else {
171 die "Cannot determine size of $dev\n";
172 }
173 }
174
175 ################################################################################
176 #
177 # @brief Compute the desired sizes of logical volumes
178 #
179 ################################################################################
180 sub compute_lv_sizes {
181
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 }
237 }
238
239 # test, whether the configuration fits on the volume group at all
240 ($min_space < $vg_size)
241 or die "Volume group $vg requires $min_space MB, but available space was estimated to be $vg_size\n";
242
243 # the extension factor
244 my $redist_factor = 0;
245 $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 my $curr_part = $current_disk->{partitions}->{$part_id};
290
291 ($next_start > $curr_part->{begin_byte})
292 and die "Previous partitions overflow begin of preserved partition $part_id\n";
293
294 # set the effective size to the value known already
295 $part->{size}->{eff_size} = $curr_part->{count_byte};
296
297 # 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
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 ################################################################################
343 #
344 # @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 my ($part_id, $config, $current_disk) = @_;
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 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 my ($start, $end) = &FAI::make_range($part->{size}->{range},
411 $current_disk->{size} . "B");
412
413 # check, whether the size is fixed
414 if ($end != $start) {
415
416 # the end of the current range (may be the end of the disk or some
417 # preserved partition
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 $end_of_range = $current_disk->{partitions}->{$p}->{begin_byte};
435
436 # logical partitions require the space for the EPBR to be left
437 # out
438 if (($FAI::configs{$config}{disklabel} eq "msdos")
439 && ($p > 4)) {
440 $end_of_range -= $current_disk->{bios_sectors_per_track} *
441 $current_disk->{sector_size};
442 }
443 last;
444 } elsif ($FAI::configs{$config}{partitions}{$p}{size}{extended}) {
445 next;
446 } else {
447 my ($min_size, $max_size) = &FAI::make_range(
448 $FAI::configs{$config}{partitions}{$p}{size}{range},
449 $current_disk->{size} . "B");
450
451 # logical partitions require the space for the EPBR to be left
452 # out
453 if (($FAI::configs{$config}{disklabel} eq "msdos")
454 && ($p > 4)) {
455 $min_size += $current_disk->{bios_sectors_per_track} *
456 $current_disk->{sector_size};
457 $max_size += $current_disk->{bios_sectors_per_track} *
458 $current_disk->{sector_size};
459 }
460
461 $min_req_space += $min_size;
462 $max_space += $max_size;
463 }
464 }
465
466 # set the end if we have reached the end of the disk
467 $end_of_range = $current_disk->{end_byte} if (-1 == $end_of_range);
468
469 my $available_space = $end_of_range - $next_start + 1;
470
471 # the next boundary is closer than the minimal space that we need
472 ($available_space < $min_req_space)
473 and die "Insufficient space available for partition $part_id\n";
474
475 # the new size
476 my $scaled_size = $end;
477 $scaled_size = POSIX::floor(($end - $start) *
478 (($available_space - $min_req_space) /
479 ($max_space - $min_req_space))) + $start
480 if ($max_space > $available_space);
481
482 ($scaled_size >= $start)
483 or &FAI::internal_error("scaled size is smaller than the desired minimum");
484
485 $start = $scaled_size;
486 $end = $start;
487 }
488
489 # now we compute the effective locations on the disk
490 # msdos specific offset for logical partitions
491 if (($FAI::configs{$config}{disklabel} eq "msdos")
492 && ($part_id > 4)) {
493
494 # add one head of disk usage if this is a logical partition
495 $min_req_total_space += $current_disk->{bios_sectors_per_track} *
496 $current_disk->{sector_size};
497
498 # move the start byte as well
499 $next_start += $current_disk->{bios_sectors_per_track} *
500 $current_disk->{sector_size};
501 }
502
503 # partition starts at where we currently are
504 $FAI::configs{$config}{partitions}{$part_id}{start_byte} =
505 $next_start;
506
507 # the end may need some alignment, depending on the disk label
508 my $end_byte = $next_start + $start - 1;
509
510 # on msdos, ensure that the partition ends at a cylinder boundary
511 if ($FAI::configs{$config}{disklabel} eq "msdos") {
512 $end_byte -=
513 ($end_byte + 1) % ($current_disk->{sector_size} *
514 $current_disk->{bios_sectors_per_track} *
515 $current_disk->{bios_heads});
516 }
517
518 # on gpt, ensure that the partition ends at a sector boundary
519 if ($FAI::configs{$config}{disklabel} eq "gpt") {
520 $end_byte -=
521 ($end_byte + 1) % $current_disk->{sector_size};
522 }
523
524 # set $start and $end to the effective values
525 $start = $end_byte - $next_start + 1;
526 $end = $start;
527
528 # write back the size spec in bytes
529 $part->{size}->{range} = $start . "-" . $end;
530
531 # then set eff_size to a proper value
532 $part->{size}->{eff_size} = $start;
533
534 # write the end byte to the configuration
535 $part->{end_byte} = $end_byte;
536
537 # and add it to the total disk space required by this config
538 $min_req_total_space += $part->{size}->{eff_size};
539
540 # set the next start
541 $next_start = $part->{end_byte} + 1;
542
543 return ($next_start, $min_req_total_space);
544 }
545
546 ################################################################################
547 #
548 # @brief Compute the desired sizes of the partitions and test feasibility
549 # thereof.
550 #
551 ################################################################################
552 sub compute_partition_sizes
553 {
554
555 # loop through all device configurations
556 foreach my $config (keys %FAI::configs) {
557
558 # for RAID or LVM, there is nothing to be done here
559 next if ($config eq "RAID" || $config =~ /^VG_./);
560 ($config =~ /^PHY_(.+)$/) or &FAI::internal_error("invalid config entry $config");
561 # nothing to be done, if this is a configuration for a virtual disk
562 next if $FAI::configs{$config}{virtual};
563 my $disk = $1; # the device name of the disk
564 # test, whether $disk is a block special device
565 (-b $disk) or die "$disk is not a valid device name\n";
566 # reference to the current disk config
567 my $current_disk = $FAI::current_config{$disk};
568
569 # at various points the following code highly depends on the desired disk label!
570 # initialise variables
571 # the id of the extended partition to be created, if required
572 my $extended = -1;
573
574 # the id of the current extended partition, if any; this setup only caters
575 # for a single existing extended partition!
576 my $current_extended = -1;
577
578 # find the first existing extended partition
579 foreach my $part_id (sort { $a <=> $b } keys %{ $current_disk->{partitions} }) {
580 if ($current_disk->{partitions}->{$part_id}->{is_extended}) {
581 $current_extended = $part_id;
582 last;
583 }
584 }
585
586 # the space required on the disk
587 my $min_req_total_space = 0;
588
589 # the start byte for the next partition
590 my $next_start = 0;
591
592 # on msdos disk labels, the first partitions starts at head #1
593 if ($FAI::configs{$config}{disklabel} eq "msdos") {
594 $next_start = $current_disk->{bios_sectors_per_track} *
595 $current_disk->{sector_size};
596
597 # the MBR requires space, too
598 $min_req_total_space += $current_disk->{bios_sectors_per_track} *
599 $current_disk->{sector_size};
600 }
601
602 # on GPT disk labels the first 34 and last 34 sectors must be left alone
603 if ($FAI::configs{$config}{disklabel} eq "gpt") {
604 $next_start = 34 * $current_disk->{sector_size};
605
606 # modify the disk to claim the space for the second partition table
607 $current_disk->{end_byte} -= 34 * $current_disk->{sector_size};
608
609 # the space required by the GPTs
610 $min_req_total_space += 2 * 34 * $current_disk->{sector_size};
611 }
612
613 # the list of partitions that we need to find start and end bytes for
614 my @worklist = (sort { $a <=> $b } keys %{ $FAI::configs{$config}{partitions} });
615
616 while (scalar (@worklist))
617 {
618
619 # work on the first entry of the list
620 my $part_id = $worklist[0];
621 # reference to the current partition
622 my $part = (\%FAI::configs)->{$config}->{partitions}->{$part_id};
623
624 # the partition $part_id must be preserved
625 if ($part->{size}->{preserve}) {
626 ($next_start, $min_req_total_space) = &FAI::do_partition_preserve($part_id,
627 $config, $current_disk, $next_start, $min_req_total_space);
628
629 # partition done
630 shift @worklist;
631 }
632
633 # msdos specific: deal with extended partitions
634 elsif ($part->{size}->{extended}) {
635 # make sure that there is only one extended partition
636 ($extended == -1 || 1 == scalar (@worklist))
637 or &FAI::internal_error("More than 1 extended partition");
638
639 # set the local variable to this id
640 $extended = $part_id;
641
642 # the size cannot be determined now, push it to the end of the
643 # worklist; the check against $extended being == -1 ensures that
644 # there is no indefinite loop
645 if (scalar (@worklist) > 1) {
646 push @worklist, shift @worklist;
647 next;
648 }
649
650 # determine the size of the extended partition
651 &FAI::do_partition_extended($part_id, $config, $current_disk);
652
653 # partition done
654 shift @worklist;
655 } else {
656 ($next_start, $min_req_total_space) = &FAI::do_partition_real($part_id,
657 $config, $current_disk, $next_start, $min_req_total_space, \@worklist);
658
659 # partition done
660 shift @worklist;
661 }
662 }
663
664 # check, whether there is sufficient space on the disk
665 ($min_req_total_space > $current_disk->{size})
666 and die "Disk $disk is too small - at least $min_req_total_space bytes are required\n";
667
668 # make sure, extended partitions are only created on msdos disklabels
669 ($FAI::configs{$config}{disklabel} ne "msdos" && $extended > -1)
670 and &FAI::internal_error("extended partitions are not supported by this disklabel");
671
672 # ensure that we have done our work
673 (defined ($FAI::configs{$config}{partitions}{$_}{start_byte})
674 && defined ($FAI::configs{$config}{partitions}{$_}{end_byte}))
675 or &FAI::internal_error("start or end of partition $_ not set")
676 foreach (sort { $a <=> $b } keys %{ $FAI::configs{$config}{partitions} });
677 }
678 }
679
680 1;
681

Properties

Name Value
svn:executable *
svn:keywords Id

  ViewVC Help
Powered by ViewVC 1.1.5