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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 4783 - (show annotations) (download)
Wed Nov 21 23:06:51 2007 UTC (5 years, 5 months ago) by michael-guest
File size: 22712 byte(s)
fixed handling of BOOT_DEVICE and BOOT_PARTITION in case they are not on the
same device
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 parser.pm
26 #
27 # @brief A parser for the disk_config files within FAI.
28 #
29 # $Id$
30 #
31 # @author Christian Kern, Michael Tautschnig, Sam Vilain, Andreas Schludei
32 # @date Sun Jul 23 16:09:36 CEST 2006
33 #
34 ################################################################################
35
36 use Parse::RecDescent;
37
38 package FAI;
39
40 ################################################################################
41 #
42 # @brief the name of the device currently being configured, including a prefix
43 # such as PHY_ or VG_ to indicate physical devices or LVM volume groups. For
44 # RAID, the entry is only "RAID"
45 #
46 ################################################################################
47 $FAI::device = "";
48
49 ################################################################################
50 #
51 # @brief Test, whether @ref $cmd is available on the system using $PATH
52 #
53 # @param $cmd Command that is to be found in $PATH
54 #
55 # @return 1, if the command is found, else 0
56 #
57 ################################################################################
58 sub in_path {
59
60 # initialize the parameter
61 my ($cmd) = @_;
62
63 # split $PATH into its components, search all of its components
64 # and test for $cmd being executable
65 ( -x "$_/$cmd" ) and return 1 foreach ( split( ":", $ENV{PATH} ) );
66 # return 0 otherwise
67 return 0;
68 }
69
70 ################################################################################
71 #
72 # @brief Initialise a new entry in @ref $FAI::configs for a physical disk.
73 #
74 # Besides creating the entry in the hash, the fully path of the device is
75 # computed (see @ref $disk) and it is tested, whether this is a block device.
76 # The device name is then used to define @ref $FAI::device.
77 #
78 # @param $disk Either an integer, occurring in the context of, e.g., disk2, or
79 # a device name. The latter may be fully qualified, such as /dev/hda, or a short
80 # name, such as sdb, in which case /dev/ is prepended.
81 #
82 ################################################################################
83 sub init_disk_config {
84
85 # Initialise $disk
86 my ($disk) = @_;
87
88 # test $disk for being numeric
89 if ( $disk =~ /^\d+$/ ) {
90
91 # $disk-1 must be a valid index in the map of all disks in the system
92 ( scalar(@FAI::disks) >= $disk )
93 or die "this system does not have a physical disk $disk\n";
94
95 # fetch the (short) device name
96 $disk = $FAI::disks[ $disk - 1 ];
97 }
98
99 # test, whether the device name starts with a / and prepend /dev/, if
100 # appropriate
101 ( $disk =~ m{^/} ) or $disk = "/dev/$disk";
102
103 # test, whether $disk is a block special device
104 ( -b $disk ) or die "$disk is not a valid device name\n";
105
106 # prepend PHY_
107 $FAI::device = "PHY_$disk";
108
109 # test, whether this is the first disk_config stanza to configure $disk
110 defined( $FAI::configs{$FAI::device} )
111 and die "Duplicate configuration for disk $FAI::disks[ $1-1 ]\n";
112
113 # Initialise the entry in $FAI::configs
114 $FAI::configs{$FAI::device} = {
115 virtual => 0,
116 disklabel => "msdos",
117 bootable => -1,
118 fstabkey => "device",
119 partitions => {}
120 };
121 }
122
123 ################################################################################
124 #
125 # @brief Initialise the entry of a partition in @ref $FAI::configs
126 #
127 # @param $type The type of the partition. It must be either primary or logical.
128 #
129 ################################################################################
130 sub init_part_config {
131
132 # the type of the partition to be created
133 my ($type) = @_;
134
135 # type must either be primary or logical, nothing else may be accepted by the
136 # parser
137 ( $type eq "primary" || $type eq "logical" ) or die "INTERNAL PARSER ERROR\n";
138
139 # check that a physical device is being configured; logical partitions are
140 # only supported on msdos disk labels.
141 (
142 $FAI::device =~ /^PHY_/ && ( $type ne "logical"
143 || $FAI::configs{$FAI::device}{disklabel} eq "msdos" )
144 ) or die "Syntax error: invalid partition type";
145
146 # the index of the new partition
147 my $part_number = 0;
148
149 # create a primary partition
150 if ( $type eq "primary" ) {
151
152 # find all previously defined primary partitions
153 foreach my $part_id ( sort keys %{ $FAI::configs{$FAI::device}{partitions} } ) {
154
155 # break, if the partition has not been created by init_part_config
156 defined( $FAI::configs{$FAI::device}{partitions}{$part_id}{size}{extended} ) or last;
157
158 # on msdos disklabels we cannot have more than 4 primary partitions
159 last if ( $part_id > 4
160 && $FAI::configs{$FAI::device}{disklabel} eq "msdos" );
161
162 # store the latest index found
163 $part_number = $part_id;
164 }
165
166 # the next index available - note that $part_number might have been 0
167 $part_number++;
168
169 # msdos disk labels don't allow for more than 4 primary partitions
170 ( $part_number < 5 || $FAI::configs{$FAI::device}{disklabel} ne "msdos" )
171 or die "$part_number are too many primary partitions\n";
172 } else {
173
174 # no further checks for the disk label being msdos have to be performed in
175 # this branch, it has been ensured above
176
177 # find the index of the new partition, initialise it to the highest current index
178 foreach my $part_id ( sort keys %{ $FAI::configs{$FAI::device}{partitions} } ) {
179
180 # skip primary partitions
181 next if ( $part_id < 5 );
182
183 # break, if the partition has not been created by init_part_config
184 defined( $FAI::configs{$FAI::device}{partitions}{$part_id}{size}{extended} )
185 or last;
186
187 # store the latest index found
188 $part_number = $part_id;
189 }
190
191 # and use the next one available
192 $part_number++;
193
194 # if this is the first logical partition, the index must be set to 5 and an
195 # extended partition must be created
196 if ( $part_number <= 5 ) {
197 $part_number = 5;
198
199 # the proposed index of the extended partition
200 my $extended = 0;
201
202 # find all previously defined primary partitions
203 foreach my $part_id ( sort keys %{ $FAI::configs{$FAI::device}{partitions} } ) {
204
205 # break, if the partition has not been created by init_part_config
206 defined( $FAI::configs{$FAI::device}{partitions}{$part_id}{size}{extended} ) or last;
207
208 # we cannot have more than 4 primary partitions
209 last if ( $part_id > 4 );
210
211 # store the latest index found
212 $extended = $part_id;
213 }
214
215 # the next index available
216 $extended++;
217
218 # msdos disk labels don't allow for more than 4 primary partitions
219 ( $extended < 5 )
220 or die "Too many primary partitions while creating extended\n";
221
222 # mark the entry as an extended partition
223 $FAI::configs{$FAI::device}{partitions}{$extended}{size}{extended} = 1;
224
225 # add the preserve = 0 flag, if it doesn't exist already
226 defined( $FAI::configs{$FAI::device}{partitions}{$extended}{size}{preserve} )
227 or $FAI::configs{$FAI::device}{partitions}{$extended}{size}{preserve} = 0;
228
229 # add the resize = 0 flag, if it doesn't exist already
230 defined(
231 $FAI::configs{$FAI::device}{partitions}{$extended}{size}{resize} ) or
232 $FAI::configs{$FAI::device}{partitions}{$extended}{size}{resize} = 0;
233 }
234 }
235
236 # initialise the hash for the partitions, if it doesn't exist already
237 # note that it might exists due to options, such as preserve:x,y
238 # the initialisation is required for the reference defined next
239 defined( $FAI::configs{$FAI::device}{partitions}{$part_number} )
240 or $FAI::configs{$FAI::device}{partitions}{$part_number} = {};
241
242 # set the reference to the current partition
243 # the reference is used by all further processing of this config line
244 $FAI::partition_pointer =
245 ( \%FAI::configs )->{$FAI::device}->{partitions}->{$part_number};
246
247 # as we can't compute the index from the reference, we need to store the
248 # $part_number explicitly
249 $FAI::partition_pointer->{number} = $part_number;
250
251 # the partition is not an extended one
252 $FAI::partition_pointer->{size}->{extended} = 0;
253
254 # add the preserve = 0 flag, if it doesn't exist already
255 defined( $FAI::partition_pointer->{size}->{preserve} )
256 or $FAI::partition_pointer->{size}->{preserve} = 0;
257
258 # add the resize = 0 flag, if it doesn't exist already
259 defined( $FAI::partition_pointer->{size}->{resize} )
260 or $FAI::partition_pointer->{size}->{resize} = 0;
261 }
262
263 ################################################################################
264 #
265 # @brief This function converts different sizes to Mbyte
266 #
267 # @param $val is the number with its unit
268 #
269 ################################################################################
270 sub convert_unit
271 {
272 my ($val) = @_;
273 ( $val =~ /^(\d+)([kMGTP%]?)(B)?\s*$/ ) or die "INTERNAL ERROR (convert_unit)\n";
274 $val = $1 * ( 1 / 1024 ) if ( $2 eq "k" );
275 $val = $1 if ( $2 eq "M" );
276 $val = $1 * 1024 if ( $2 eq "G" );
277 $val = $1 * ( 1024 * 1024 ) if ( $2 eq "T" );
278 $val = $1 * ( 1024 * 1024 * 1024 ) if ( $2 eq "P" );
279 # % is returned as is
280 return $val;
281 }
282
283 # have RecDescent do proper error reporting
284 $::RD_HINT = 1;
285
286 ################################################################################
287 #
288 # @brief The effective implementation of the parser is instantiated here
289 #
290 ################################################################################
291 $FAI::Parser = Parse::RecDescent->new(
292 q{
293 file: line(s?) /\Z/
294 {
295 $return = 1;
296 }
297 | <error>
298
299 line: <skip: qr/[ \t]*/> "\\n"
300 | <skip: qr/[ \t]*/> comment "\\n"
301 | <skip: qr/[ \t]*/> config "\\n"
302
303 comment: /^\s*#.*/
304
305 config: 'disk_config' disk_config_arg
306 | volume
307
308 disk_config_arg: 'raid'
309 {
310 # check, whether raid tools are available
311 ( &FAI::in_path( "mdadm" ) == 1 ) or
312 die "mdadm not found in PATH\n";
313 $FAI::device = "RAID";
314 }
315 | /^lvm/
316 {
317
318 # check, whether lvm tools are available
319 ( &FAI::in_path( "lvcreate" ) == 1 ) or
320 die "LVM tools not found in PATH\n";
321 # initialise $FAI::device to inform the following lines about the LVM
322 # being configured
323 $FAI::device = "VG_";
324 }
325 | 'end'
326 {
327 # exit config mode
328 $FAI::device = "";
329 }
330 | /^disk(\d+)/
331 {
332 # check, whether parted is available
333 ( &FAI::in_path( "parted" ) == 1 ) or
334 die "parted not found in PATH\n";
335 # initialise the entry of the hash corresponding to disk$1
336 &FAI::init_disk_config( $1 );
337 }
338 option(s?)
339 | /^\S+/
340 {
341 # check, whether parted is available
342 ( &FAI::in_path( "parted" ) == 1 ) or
343 die "parted not found in PATH\n";
344 # initialise the entry of the hash corresponding to $item[1]
345 &FAI::init_disk_config( $item[ 1 ] );
346 }
347 option(s?)
348
349 option: /^preserve_always:(\d+(,\d+)*)/
350 {
351 # set the preserve flag for all ids in all cases
352 $FAI::configs{$FAI::device}{partitions}{$_}{size}{preserve} = 1 foreach ( split( ",", $1 ) );
353 }
354 /^preserve_reinstall:(\d+(,\d+)*)/
355 {
356 # set the preserve flag for all ids if $FAI::reinstall is set
357 if( $FAI::reinstall == 1 ) {
358 $FAI::configs{$FAI::device}{partitions}{$_}{size}{preserve} = 1 foreach ( split( ",", $1 ) );
359 }
360 }
361 | /^resize:(\d+(,\d+)*)/
362 {
363 # set the resize flag for all ids
364 $FAI::configs{$FAI::device}{partitions}{$_}{size}{resize} = 1 foreach ( split( ",", $1 ) );
365 }
366 | /^disklabel:(msdos|gpt)/
367 {
368 # set the disk label - actually not only the above, but all types
369 # supported by parted could be allowed, but others are not implemented
370 # yet
371 $FAI::configs{$FAI::device}{disklabel} = $1;
372 }
373 | /^bootable:(\d+)/
374 {
375 # specify a partition that should get the bootable flag set
376 $FAI::configs{$FAI::device}{bootable} = $1;
377 ( $FAI::device =~ /^PHY_(.+)$/ ) or die
378 "INTERNAL ERROR: unexpected device name\n";
379 $FAI::disk_var{BOOT_DEVICE} = $1;
380 }
381 | 'virtual'
382 {
383 # this is a configuration for a virtual disk
384 $FAI::configs{$FAI::device}{virtual} = 1;
385 }
386 | /^fstabkey:(device|label|uuid)/
387 {
388 # the information preferred for fstab device identifieres
389 $FAI::configs{$FAI::device}{fstabkey} = $1;
390 }
391
392 volume: /^vg\s+/ name devices
393 | /^raid([0156])\s+/
394 {
395 # make sure that this is a RAID configuration
396 ( $FAI::device eq "RAID" ) or die "RAID entry invalid in this context\n";
397 # initialise RAID entry, if it doesn't exist already
398 defined( $FAI::configs{RAID} ) or $FAI::configs{RAID}{volumes} = {};
399 # compute the next available index - the size of the entry
400 my $vol_id = scalar( keys %{ $FAI::configs{RAID}{volumes} } );
401 # set the RAID type of this volume
402 $FAI::configs{RAID}{volumes}{$vol_id}{mode} = $1;
403 # initialise the hash of devices
404 $FAI::configs{RAID}{volumes}{$vol_id}{devices} = {};
405 # set the reference to the current volume
406 # the reference is used by all further processing of this config line
407 $FAI::partition_pointer = ( \%FAI::configs )->{RAID}->{volumes}->{$vol_id};
408 }
409 mountpoint devices filesystem mount_options fs_options
410 | type mountpoint size filesystem mount_options fs_options
411
412 type: 'primary'
413 {
414 # initialise a primary partition
415 &FAI::init_part_config( $item[ 1 ] );
416 }
417 | 'logical'
418 {
419 # initialise a logical partition
420 &FAI::init_part_config( $item[ 1 ] );
421 }
422 | m{^([^/\s\-]+)-([^/\s\-]+)\s+}
423 {
424 # set $FAI::device to VG_$1
425 $FAI::device = "VG_$1";
426 # make sure, the volume group $1 has been defined before
427 defined( $FAI::configs{$FAI::device} ) or
428 die "Volume group $1 has not been declared yet.\n";
429 # make sure, $2 has not been defined already
430 defined( $FAI::configs{$FAI::device}{volumes}{$2} ) and
431 die "Logical volume $2 has been defined already.\n";
432 # initialise the new hash
433 $FAI::configs{$FAI::device}{volumes}{$2} = {};
434 # initialise the preserve and resize flags
435 $FAI::configs{$FAI::device}{volumes}{$2}{size}{preserve} = 0;
436 $FAI::configs{$FAI::device}{volumes}{$2}{size}{resize} = 0;
437 # set the reference to the current volume
438 # the reference is used by all further processing of this config line
439 $FAI::partition_pointer = ( \%FAI::configs )->{$FAI::device}->{volumes}->{$2};
440 }
441
442 mountpoint: '-'
443 {
444 # this partition should not be mounted
445 $FAI::partition_pointer->{mountpoint} = "-";
446 }
447 | 'swap'
448 {
449 # this partition is swap space, not mounted
450 $FAI::partition_pointer->{mountpoint} = "none";
451 }
452 | m{^/\S*}
453 {
454 # set the mount point
455 $FAI::partition_pointer->{mountpoint} = $item[ 1 ];
456 # if the mount point is / or /boot and we are currently doing a
457 # physical device, the variables should be set, unless they are
458 # already
459 if ( $FAI::device =~ /^PHY_(.+)$/ &&
460 ( $item[ 1 ] eq "/boot" || ( $item[ 1 ] eq "/" &&
461 !defined( $FAI::disk_var{BOOT_PARTITION} ) ) ) ) {
462 # set the BOOT_DEVICE and BOOT_PARTITION variables, if necessary
463 $FAI::disk_var{BOOT_PARTITION} = $1 .
464 $FAI::partition_pointer->{number};
465 defined( $FAI::disk_var{BOOT_DEVICE} ) or
466 $FAI::disk_var{BOOT_DEVICE} = $1;
467 }
468 }
469
470 name: m{^([^/\s\-]+)}
471 {
472 # set the device name to VG_ and the name of the volume group
473 $FAI::device = "VG_$1";
474 # make sure, the volume group $1 not has been defined already
475 defined( $FAI::configs{$FAI::device} ) and
476 die "Volume group $1 has been defined already.\n";
477 # make sure this line is part of an LVM configuration
478 ( $FAI::device =~ /^VG_/ ) or
479 die "vg is invalid in a non LVM-context.\n";
480 # initialise the new hash
481 $FAI::configs{$FAI::device}{volumes} = {};
482 # initialise the list of physical devices
483 $FAI::configs{$FAI::device}{devices} = ();
484 # the rule must not return undef
485 1;
486 }
487
488 size: /^(\d+[kMGTP%]?(-(\d+[kMGTP%]?)?)?)(:resize)?\s+/
489 {
490 # complete the size specification to be a range in all cases
491 my $range = $1;
492 # the size is fixed
493 if( ! defined( $2 ) )
494 {
495 # make it a range of the form x-x
496 $range = "$range-$1";
497 }
498 elsif( ! defined( $3 ) )
499 {
500 # range has no upper limit, assume the whole disk
501 $range = $range . "100%";
502 }
503
504 # convert the units, if necessary
505 my ($min, $max) = split(/-/, $range);
506 $min = &FAI::convert_unit($min);
507 $max = &FAI::convert_unit($max);
508 $range = "$min-$max";
509 # enter the range into the hash
510 $FAI::partition_pointer->{size}->{range} = $range;
511 # set the resize flag, if required
512 defined( $4 ) and $FAI::partition_pointer->{size}->{resize} = 1;
513 }
514 | /^(-\d+[kMGTP%]?)(:resize)?\s+/
515 {
516 # complete the range by assuming 0 as the lower limit
517 my $range = "0$1";
518 # convert the units, if necessary
519 my ($min, $max) = split(/-/, $range);
520 $min = &FAI::convert_unit($min);
521 $max = &FAI::convert_unit($max);
522 $range = "$min-$max";
523 # enter the range into the hash
524 $FAI::partition_pointer->{size}->{range} = $range;
525 # set the resize flag, if required
526 defined( $2 ) and $FAI::partition_pointer->{size}->{resize} = 1;
527 }
528 | <error: invalid partition size near "$text">
529
530 devices: /^([^\d,:\s\-][^,:\s]*(:(spare|missing))*(,[^,:\s]+(:(spare|missing))*)*)/
531 {
532 # split the device list by ,
533 foreach my $dev ( split( ",", $1 ) )
534 {
535 # match the substrings
536 ( $dev =~ /^([^\d,:\s\-][^,:\s]*)(:(spare|missing))*$/ ) or die "INTERNAL PARSER ERROR\n";
537 # redefine the device string
538 $dev = $1;
539 # make $dev a full path name; can't validate device name yet as it
540 # might be created later on
541 unless ( $dev =~ m{^/} ) {
542 if ( $dev =~ m/^disk(\d+)\.(\d+)/ ) {
543 my $short_dev = $FAI::disks[ $1 - 1 ];
544 $dev = "/dev/$short_dev$2";
545 }
546 else {
547 $dev = "/dev/$dev";
548 }
549 }
550 # options are only valid for RAID
551 defined( $2 ) and ( $FAI::device ne "RAID" ) and die "Option $2 invalid in a non-RAID context\n";
552 if( $FAI::device eq "RAID" )
553 {
554 # parse all options
555 my $spare = 0;
556 my $missing = 0;
557 if( defined( $2 ) )
558 {
559 ( $2 =~ /spare/ ) and $spare = 1;
560 ( $2 =~ /missing/ ) and $missing = 1;
561 }
562 # each device may only appear once
563 defined( $FAI::partition_pointer->{devices}->{$dev} ) and
564 die "$dev is already part of the RAID volume\n";
565 # set the options
566 $FAI::partition_pointer->{devices}->{$dev}->{options} = {
567 "spare" => $spare,
568 "missing" => $missing
569 };
570 }
571 else
572 {
573 # create an empty hash for each device
574 $FAI::configs{$FAI::device}{devices}{$dev} = {};
575 }
576 }
577 1;
578 }
579 | <error: invalid device spec "$text">
580
581 mount_options: /\S+/
582 {
583 $FAI::partition_pointer->{mount_options} = $item[ 1 ];
584 }
585
586 filesystem: '-'
587 {
588 $FAI::partition_pointer->{filesystem} = $item[ 1 ];
589 }
590 | 'swap'
591 {
592 $FAI::partition_pointer->{filesystem} = $item[ 1 ];
593 }
594 | /^\S+/
595 {
596 ( &FAI::in_path("mkfs.$item[1]") == 1 ) or
597 die "unknown/invalid filesystem type $item[1] (mkfs.$item[1] not found in PATH)\n";
598 $FAI::partition_pointer->{filesystem} = $item[ 1 ];
599 }
600
601 fs_options: /[^;\n]*/
602 {
603 $FAI::partition_pointer->{fs_options} = $item[ 1 ];
604 }
605 }
606 );
607
608 ################################################################################
609 #
610 # @brief Parse the data from <$IN> using @ref $FAI::Parser
611 #
612 # @param IN file handle for input file, may be STDIN
613 #
614 ################################################################################
615 sub run_parser {
616 my ($IN) = @_;
617
618 # read <$IN> to a single string (not a list), thus $/ has to be unset
619 my $ifs = $/;
620 undef $/;
621 my $input = <$IN>;
622 $/ = $ifs;
623
624 # print the contents of <$IN> for debugging purposes
625 ( $FAI::debug > 0 ) and print "Input was:\n" . $input;
626
627 # check for old-style configuration files
628 ( $input =~ m{(^|\n)[^\n#]+;} )
629 and die "Old style configuration files are not supported\n";
630
631 # attempt to parse $input - any error will lead to termination
632 defined $FAI::Parser->file($input) or die "Syntax error\n";
633 }
634
635 1;
636

Properties

Name Value
svn:executable *
svn:keywords Id

  ViewVC Help
Powered by ViewVC 1.1.5