File Coverage

blib/lib/Linux/Bootloader/Grub.pm
Criterion Covered Total %
statement 118 261 45.2
branch 51 150 34.0
condition 13 48 27.0
subroutine 10 15 66.6
pod 7 7 100.0
total 199 481 41.3


line stmt bran cond sub pod time code
1             package Linux::Bootloader::Grub;
2              
3             =head1 NAME
4              
5             Linux::Bootloader::Grub - Parse and modify GRUB configuration files.
6              
7             =head1 SYNOPSIS
8              
9             use Linux::Bootloader;
10             use Linux::Bootloader::Grub;
11              
12             my $config_file='/boot/grub/menu.lst';
13             $bootloader = Linux::Bootloader::Grub->new($config_file);
14              
15             $bootloader->read();
16              
17             # add a kernel
18             $bootloader->add(%hash)
19              
20             # remove a kernel
21             $bootloader->remove(2)
22              
23             # print config info
24             $bootloader->print_info('all')
25              
26             # set new default
27             $bootloader->set_default(1)
28              
29             $bootloader->write();
30              
31              
32             =head1 DESCRIPTION
33              
34             This module provides functions for working with GRUB configuration files.
35              
36             Adding a kernel:
37             - add kernel at start, end, or any index position.
38             - kernel path and title are required.
39             - root, kernel args, initrd, savedefault, module are optional.
40             - any options not specified are copied from default.
41             - remove any conflicting kernels first if force is specified.
42            
43             Removing a kernel:
44             - remove by index position
45             - or by title/label
46              
47              
48             =head1 FUNCTIONS
49              
50             Also see L for functions available from the base class.
51              
52             =head2 new()
53              
54             Creates a new Linux::Bootloader::Grub object.
55              
56             =head2 _info()
57              
58             Parse config into array of hashes.
59             Takes: nothing.
60             Returns: array of hashes containing config file options and boot entries,
61             undef on error.
62              
63             =head2 set_default()
64              
65             Set new default kernel.
66             Takes: integer or string, boot menu position or title.
67             Returns: undef on error.
68              
69             =head2 add()
70              
71             Add new kernel to config.
72             Takes: hash containing kernel path, title, etc.
73             Returns: undef on error.
74              
75             =head2 update()
76              
77             Update args of an existing kernel entry.
78             Takes: hash containing args and entry to update.
79             Returns: undef on error.
80              
81             =head2 install()
82              
83             Prints message on how to re-install grub.
84             Takes: nothing.
85             Returns: nothing.
86              
87             =head2 update_main_options()
88              
89             This updates or adds a general line anywhere before the first 'title' line.
90             it is called with the 'update' and 'option' options, when no 'update-kernel'
91             is specified.
92              
93             =head2 boot_once()
94              
95             This is a special case of using 'fallback'. This function makes the current
96             default the fallback kernel and sets the passed argument to be the default
97             kernel which saves to the fallback kernel after booting. The file
98             '/boot/grub/default' is created if it does not exist.
99              
100             This only works with grub versions 0.97 or better.
101              
102             =head2 _get_bootloader_version()
103              
104             Prints detected grub version.
105             Takes: nothing.
106             Returns: nothing.
107              
108             =cut
109              
110 1     1   82705 use strict;
  1         3  
  1         54  
111 1     1   7 use warnings;
  1         2  
  1         46  
112             use Linux::Bootloader
113              
114 1     1   679 @Linux::Bootloader::Grub::ISA = qw(Linux::Bootloader);
  1         3  
  1         56  
115 1     1   13 use base 'Linux::Bootloader';
  1         3  
  1         71  
116              
117              
118 1     1   12 use vars qw( $VERSION );
  1         2  
  1         3578  
119             our $VERSION = '1.2';
120              
121              
122             sub new {
123 1     1 1 1021 my $this = shift;
124 1   33     8 my $class = ref($this) || $this;
125 1         4 my $self = bless({}, $class);
126             #my $self = fields::new($class);
127              
128 1         14 $self->SUPER::new();
129              
130 1         3 return $self;
131             }
132              
133             sub _set_config_file {
134 1     1   3 my $self=shift;
135 1         5 $self->{'config_file'}='/boot/grub/menu.lst';
136             }
137              
138              
139             ### GRUB functions ###
140              
141             # Parse config into array of hashes
142              
143             sub _info {
144 22     22   42 my $self=shift;
145              
146 22 50       1188 return undef unless $self->_check_config();
147              
148 22         45 my @config=@{$self->{config}};
  22         226  
149 22         14459 @config=grep(!/^#|^\n/, @config);
150              
151 22         18708 my %matches = ( default => '^\s*default\s*\=*\s*(\S+)',
152             timeout => '^\s*timeout\s*\=*\s*(\S+)',
153             fallback => '^\s*fallback\s*\=*\s*(\S+)',
154             kernel => '^\s*kernel\s+(\S+)',
155             root => '^\s*kernel\s+.*\s+.*root=(\S+)',
156             args => '^\s*kernel\s+\S+\s+(.*)\n',
157             boot => '^\s*root\s+(.*)',
158             initrd => '^\s*initrd\s+(.*)',
159             savedefault => '^\s*savedefault\s+(.*)',
160             module => '^\s*module\s+(.+)',
161             );
162              
163 22         32 my @sections;
164 22         43 my $index=0;
165 22         49 foreach (@config) {
166 305 100       1083 if ($_ =~ /^\s*title\s+(.*)/i) {
167 65         89 $index++;
168 65         331 $sections[$index]{title} = $1;
169             }
170 305         11112 foreach my $key (keys %matches) {
171 3050 100       130791 if ($_ =~ /$matches{$key}/i) {
172 326 50       854 $key .= '2' if exists $sections[$index]{$key};
173 326         1024 $sections[$index]{$key} = $1;
174 326 100       1018 if ($key eq 'args') {
175 65         12545 $sections[$index]{$key} =~ s/root=\S+\s*//i;
176 65 100       410 delete $sections[$index]{$key} if ($sections[$index]{$key} !~ /\S/);
177             }
178             }
179             }
180             }
181              
182             # sometimes config doesn't have a default, so goes to first
183 22 50       163 if (!(defined $sections[0]{'default'})) {
    50          
184 0         0 $sections[0]{'default'} = '0';
185              
186             # if default is 'saved', read from grub default file
187             } elsif ($sections[0]{'default'} =~ m/^saved$/i) {
188 0 0 0     0 open(DEFAULT_FILE, '/boot/grub/default')
189             || warn ("ERROR: cannot read grub default file.\n") && return undef;
190 0         0 my @default_config = ;
191 0         0 close(DEFAULT_FILE);
192 0         0 $default_config[0] =~ /^(\d+)/;
193 0         0 $sections[0]{'default'} = $1;
194             }
195              
196             # return array of hashes
197 22         230 return @sections;
198             }
199              
200              
201             # Set new default kernel
202              
203             sub set_default {
204 5     5 1 20 my $self=shift;
205 5         13 my $newdefault=shift;
206              
207 5 50       27 return undef unless defined $newdefault;
208 5 50       25 return undef unless $self->_check_config();
209              
210 5         9 my @config=@{$self->{config}};
  5         57  
211 5         19 my @sections=$self->_info();
212              
213             # if not a number, do title lookup
214 5 50 66     66 if ($newdefault !~ /^\d+$/ && $newdefault !~ m/^saved$/) {
215 2         32 $newdefault = $self->_lookup($newdefault);
216 2 50       45 return undef unless (defined $newdefault);
217             }
218              
219 3         14 my $kcount = $#sections-1;
220 3 50       24 if ($newdefault !~ m/saved/) {
221 3 100 66     41 if (($newdefault < 0) || ($newdefault > $kcount)) {
222 1         136 warn "ERROR: Enter a default between 0 and $kcount.\n";
223 1         26 return undef;
224             }
225             }
226              
227 2         21 foreach my $index (0..$#config) {
228              
229 4 100       39 if ($config[$index] =~ /(^\s*default\s*\=*\s*)\d+/i) {
    50          
230 2         22 $config[$index] = "$1$newdefault # set by $0\n";
231 2         7 last;
232             } elsif ($config[$index] =~ /^\s*default\s*\=*\s*saved/i) {
233 0         0 my @default_config;
234 0         0 my $default_config_file='/boot/grub/default';
235              
236 0 0 0     0 open(DEFAULT_FILE, $default_config_file)
237             || warn ("ERROR: cannot open default file.\n") && return undef;
238 0         0 @default_config = ;
239 0         0 close(DEFAULT_FILE);
240              
241 0 0       0 if ($newdefault eq 'saved') {
242 0         0 warn "WARNING: Setting new default to '0'\n";
243 0         0 $newdefault = 0;
244             }
245              
246 0         0 $default_config[0] = "$newdefault\n";
247              
248 0 0 0     0 open(DEFAULT_FILE, ">$default_config_file")
249             || warn ("ERROR: cannot open default file.\n") && return undef;
250 0         0 print DEFAULT_FILE join("",@default_config);
251 0         0 close(DEFAULT_FILE);
252 0         0 last;
253             }
254             }
255 2         5 @{$self->{config}} = @config;
  2         56  
256             }
257              
258              
259             # Add new kernel to config
260              
261             sub add {
262 6     6 1 623 my $self=shift;
263 6         23 my %param=@_;
264              
265 6 50       110 print ("Adding kernel.\n") if $self->debug()>1;
266              
267 6 100 100     177 if (!defined $param{'add-kernel'} || !defined $param{'title'}) {
    50 66        
    100          
268 3         268 warn "ERROR: kernel path (--add-kernel), title (--title) required.\n";
269 3         25 return undef;
270             } elsif (!(-f "$param{'add-kernel'}")) {
271 0         0 warn "ERROR: kernel $param{'add-kernel'} not found!\n";
272 0         0 return undef;
273             } elsif (defined $param{'initrd'} && !(-f "$param{'initrd'}")) {
274 1         85 warn "ERROR: initrd $param{'initrd'} not found!\n";
275 1         10 return undef;
276             }
277              
278 2 50       14 return undef unless $self->_check_config();
279              
280 2         8 my @sections=$self->_info();
281              
282             # check if title already exists
283 2 100       17 if (defined $self->_lookup($param{title})) {
284 1         113 warn ("WARNING: Title already exists.\n");
285 1 50       9 if (defined $param{force}) {
286 0         0 $self->remove($param{title});
287             } else {
288 1         20 return undef;
289             }
290             }
291              
292 1         2 my @config = @{$self->{config}};
  1         7  
293 1         5 @sections=$self->_info();
294              
295             # Use default kernel to fill in missing info
296 1         14 my $default=$self->get_default();
297 1         3 $default++;
298              
299 1         4 foreach my $p ('args', 'root', 'boot', 'savedefault') {
300 4 100       11 if (! defined $param{$p}) {
301 1         7 $param{$p} = $sections[$default]{$p};
302             }
303             }
304              
305             # use default entry to determine if path (/boot) should be removed
306 1 50       6 if ($sections[$default]{'kernel'} !~ /^\/boot/) {
307 1         6 $param{'add-kernel'} =~ s/^\/boot//;
308 1 50       4 $param{'initrd'} =~ s/^\/boot// unless !defined $param{'initrd'};
309             }
310              
311 1         3 my @newkernel;
312 1 50       7 push(@newkernel, "title\t$param{title}\n") if defined $param{title};
313 1 50       5 push(@newkernel, "\troot $param{boot}\n") if defined $param{boot};
314              
315 1         2 my $line;
316 1 50       4 if ( defined $param{xen} ) {
317 0         0 $line = "\tkernel $sections[$default]{kernel}";
318 0 0       0 $line .= " $sections[$default]{root}" if defined $sections[$default]{root};
319 0 0       0 $line .= " $sections[$default]{args}" if defined $sections[$default]{args};
320 0         0 push( @newkernel, "$line\n" );
321 0 0       0 push( @newkernel, "\tinitrd $sections[$default]{'initrd'}\n" ) if defined $sections[$default]{'initrd'};
322 0 0       0 $line = "\tmodule $param{'add-kernel'}" if defined $param{'add-kernel'};
323 0 0       0 $line .= " root=$param{root}" if defined $param{root};
324 0 0       0 $line .= " $param{args}" if defined $param{args};
325 0         0 push( @newkernel, "$line\n" );
326 0 0       0 push( @newkernel, "\tmodule $param{initrd}\n" ) if defined $param{initrd};
327             } else {
328 1 50       6 $line = "\tkernel $param{'add-kernel'}" if defined $param{'add-kernel'};
329 1 50       12 $line .= " root=$param{root}" if defined $param{root};
330 1 50       6 $line .= " $param{args}" if defined $param{args};
331 1         3 push( @newkernel, "$line\n" );
332 1 50       4 push( @newkernel, "\tinitrd $param{initrd}\n" ) if defined $param{initrd};
333             }
334              
335 1 50       5 push(@newkernel, "\tsavedefault $param{savedefault}\n") if defined $param{savedefault};
336              
337 1         2 foreach my $module (@{$param{'module'}}) {
  1         6  
338 0         0 push(@newkernel, "\tmodule " . $module . "\n");
339             }
340              
341 1         6 push(@newkernel, "\n");
342              
343 1 50 33     6 if (!defined $param{position} || $param{position} !~ /end|\d+/) {
344 1         3 $param{position}=0
345             }
346              
347 1         3 my @newconfig;
348 1 50 33     12 if ($param{position}=~/end/ || $param{position} >= $#sections) {
349 0         0 $param{position}=$#sections;
350 0         0 push (@newconfig,@config);
351 0 0       0 if ($newconfig[$#newconfig] =~ /\S/) {
352 0         0 push (@newconfig, "\n");
353             }
354 0         0 push (@newconfig,@newkernel);
355             } else {
356 1         3 my $index=0;
357 1         12 foreach (@config) {
358 19 100       40 if ($_ =~ /^\s*title/i) {
359 3 100       8 if ($index==$param{position}) {
360 1         4 push (@newconfig, @newkernel);
361             }
362 3         4 $index++;
363             }
364 19         32 push (@newconfig, $_);
365             }
366             }
367              
368 1         3 @{$self->{config}} = @newconfig;
  1         483  
369              
370 1 50 33     28 if (defined $param{'make-default'} || defined $param{'boot-once'}) {
371 0         0 $self->set_default($param{position});
372             }
373 1         312 print "Added: $param{'title'}.\n";
374             }
375              
376              
377             # Update kernel args
378              
379             sub update {
380 0     0 1   my $self=shift;
381 0           my %params=@_;
382              
383 0 0         print ("Updating kernel.\n") if $self->debug()>1;
384              
385 0 0 0       if (defined $params{'option'} && !defined $params{'update-kernel'}) {
    0 0        
386 0           return $self->update_main_options(%params);
387             } elsif (!defined $params{'update-kernel'} || (!defined $params{'args'} && !defined $params{'remove-args'} && !defined $params{'option'})) {
388 0           warn "ERROR: kernel position or title (--update-kernel) and args (--args or --remove-args) required.\n";
389 0           return undef;
390             }
391              
392 0 0         return undef unless $self->_check_config();
393              
394             # my @config = @{$self->{config}};
395 0           my @sections=$self->_info();
396              
397             # if not a number, do title lookup
398 0 0 0       if (defined $params{'update-kernel'} and $params{'update-kernel'} !~ /^\d+$/) {
399 0           $params{'update-kernel'} = $self->_lookup($params{'update-kernel'});
400             }
401              
402 0           my $kcount = $#sections-1;
403 0 0 0       if ($params{'update-kernel'} !~ /^\d+$/ || $params{'update-kernel'} < 0 || $params{'update-kernel'} > $kcount) {
      0        
404 0           warn "ERROR: Enter a default between 0 and $kcount.\n";
405 0           return undef;
406             }
407              
408 0           my $kregex = '(^\s*kernel\s+\S+)(.*)';
409 0 0         $kregex = '(^\s*module\s+\S+vmlinuz\S+)(.*)' if defined $params{'xen'};
410              
411 0           my $index=-1;
412 0           my $config_line = -1;
413 0           my $line = '';
414 0           foreach $line (@{$self->{config}}) {
  0            
415 0           $config_line = $config_line + 1;
416 0 0         if ($line =~ /^\s*title/i) {
417 0           $index++;
418             }
419 0 0         if ($index==$params{'update-kernel'}) {
    0          
420 0 0 0       if (defined $params{'args'} or defined $params{'remove-args'}){
    0          
421 0 0         if ( $line =~ /$kregex/i ) {
422 0           my $kernel = $1;
423 0           my $args = $2;
424 0 0         $args =~ s/\s+$params{'remove-args'}(\=\S+|\s+|$)/ /ig if defined $params{'remove-args'};
425 0 0         if ( defined $params{'args'} ) {
426 0           my $base_arg = $params{'args'};
427 0           $base_arg =~ s/\=.*//;
428 0           $args =~ s/\s+$base_arg(\=\S+|\s+|$)/ /ig;
429 0           $args = $args . " " . $params{'args'};
430             }
431 0 0         if ($line eq $kernel . $args . "\n") {
432 0           warn "WARNING: No change made to args.\n";
433 0           return undef;
434             } else {
435 0           $line = $kernel . $args . "\n";
436             }
437 0           next;
438             }
439             } elsif (defined $params{'option'}){
440 0           foreach my $val ( keys %params){
441 0 0         if ($line =~ m/^\s*$val.*/i) {
442 0           splice @{$self->{config}},$config_line,1,"$val $params{$val}\n";
  0            
443 0           delete $params{$val};
444 0           $config_line += 1;
445             }
446             }
447             }
448             } elsif ($index > $params{'update-kernel'}){
449 0           last;
450             }
451             }
452             # Add any leftover parameters
453 0           delete $params{'update-kernel'};
454 0 0         if (defined $params{'option'}){
455 0           delete $params{'option'};
456 0           $config_line -= 1;
457 0           foreach my $val ( keys %params){
458 0           splice @{$self->{config}},$config_line,0,"$val $params{$val}\n";
  0            
459 0           $config_line += 1;
460             }
461             }
462             }
463              
464              
465             # Run command to install bootloader
466              
467             sub install {
468 0     0 1   my $self=shift;
469 0           my $device;
470              
471 0           warn "Re-installing grub is currently unsupported.\n";
472 0           warn "If you really need to re-install grub, use 'grub-install '.\n";
473 0           return undef;
474              
475             #system("grub-install $device");
476             #if ($? != 0) {
477             # warn ("ERROR: Failed to run grub-install.\n") && return undef;
478             #}
479             #return 1;
480             }
481              
482              
483             sub update_main_options{
484 0     0 1   my $self=shift;
485 0           my %params=@_;
486 0           delete $params{'option'};
487 0           foreach my $val (keys %params){
488 0           my $x=0;
489 0           foreach my $line ( @{$self->{config}} ) {
  0            
490             # Replace
491 0 0         if ($line =~ m/^\s*$val/) {
492 0           splice (@{$self->{config}},$x,1,"$val $params{$val}\n");
  0            
493 0           last;
494             }
495             # Add
496 0 0         if ($line =~ /^\s*title/i) {
497             # This is a new option, add it before here
498 0           print "Your option is not in current configuration. Adding.\n";
499 0           splice @{$self->{config}},$x,0,"$val $params{$val}\n";
  0            
500 0           last;
501             }
502 0           $x+=1;
503             }
504             }
505             }
506              
507              
508             sub boot_once {
509 0     0 1   my $self=shift;
510 0           my $entry_to_boot_once = shift;
511              
512 0 0         unless ( $entry_to_boot_once ) { print "No kernel\n"; return undef;}
  0            
  0            
513 0           $self->read();
514 0           my $default=$self->get_default();
515              
516 0 0         if ( $default == $self->_lookup($entry_to_boot_once)){
517 0           warn "The default and once-boot kernels are the same. No action taken. \nSet default to something else, then re-try.\n";
518 0           return undef;
519             }
520 0 0         if ( $self->_get_bootloader_version() < 0.97 ){
521 0           warn "This function works for grub version 0.97 and up. No action taken. \nUpgrade, then re-try.\n";
522 0           return undef;
523             }
524              
525 0           $self->set_default('saved');
526 0 0         if ( ! -f '/boot/grub/default' ){
527 0           open FH, '>/boot/grub/default';
528 0           my $file_contents="default
529             #
530             #
531             #
532             #
533             #
534             #
535             #
536             #
537             #
538             #
539             # WARNING: If you want to edit this file directly, do not remove any line
540             # from this file, including this warning. Using `grub-set-default\' is
541             # strongly recommended.
542             ";
543 0           print FH $file_contents;
544 0           close FH;
545             }
546 0           $self->set_default( "$entry_to_boot_once" );
547 0           $self->update( 'option'=>'','fallback' => $default );
548 0           $self->update( 'update-kernel'=>"$entry_to_boot_once",'option'=>'','savedefault' => 'fallback' );
549 0           $self->update( 'update-kernel'=>"$default",'option'=>'', 'savedefault' => '' );
550 0           $self->write();
551            
552             }
553              
554             sub _get_bootloader_version {
555 0     0     my $self = shift;
556 0           return `grub --version | sed 's/grub (GNU GRUB //' | sed 's/)//'`;
557             }
558              
559              
560             1;
561              
562              
563             =head1 AUTHOR
564              
565             Open Source Development Labs, Engineering Department
566              
567             =head1 COPYRIGHT
568              
569             Copyright (C) 2006 Open Source Development Labs
570             All Rights Reserved.
571              
572             This script is free software; you can redistribute it and/or modify it
573             under the same terms as Perl itself.
574              
575             =head1 SEE ALSO
576              
577             L
578              
579             =cut
580