File Coverage

blib/lib/Linux/Bootloader.pm
Criterion Covered Total %
statement 115 298 38.5
branch 45 182 24.7
condition 13 66 19.7
subroutine 13 18 72.2
pod 11 11 100.0
total 197 575 34.2


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Linux::Bootloader - Base class interacting with Linux bootloaders
4              
5             =head1 SYNOPSIS
6              
7             use Linux::Bootloader;
8            
9             my $bootloader = new Linux::Bootloader();
10             my $config_file='/boot/grub/menu.lst';
11            
12             $bootloader->read($config_file);
13             $bootloader->print_info('all');
14             $bootloader->add(%hash);
15             $bootloader->update(%hash);
16             $bootloader->remove(2);
17             $bootloader->get_default();
18             $bootloader->set_default(2);
19             %hash = $bootloader->read_entry(0);
20             $bootloader->write($config_file);
21              
22            
23             =head1 DESCRIPTION
24              
25             This module provides base functions for working with bootloader configuration files.
26              
27             =head1 FUNCTIONS
28              
29             =head2 new()
30              
31             Creates a new Linux::Bootloader object.
32              
33             =head2 read()
34              
35             Reads configuration file into an array.
36             Takes: string.
37             Returns: undef on error.
38              
39             =head2 write()
40              
41             Writes configuration file.
42             Takes: string.
43             Returns: undef on error.
44              
45             =head2 print_info()
46              
47             Prints information from config.
48             Takes: string.
49             Returns: undef on error.
50              
51             =head2 _info()
52              
53             Parse config into array of hashes.
54             Takes: nothing.
55             Returns: array of hashes.
56              
57             =head2 get_default()
58              
59             Determine current default kernel.
60             Takes: nothing.
61             Returns: integer, undef on error.
62              
63             =head2 set_default()
64              
65             Set new default kernel.
66             Takes: integer.
67             Returns: undef on error.
68              
69             =head2 add()
70              
71             Add new kernel to config.
72             Takes: hash.
73             Returns: undef on error.
74              
75             =head2 update()
76              
77             Update args of an existing kernel entry.
78             Takes: hash.
79             Returns: undef on error.
80              
81             =head2 remove()
82              
83             Remove kernel from config.
84             Takes: string.
85             Returns: undef on error.
86              
87             =head2 read_entry()
88              
89             Read an existing entry into a hash suitable to add or update from.
90             Takes: integer or title
91             Returns: undef or hash
92              
93             =head2 debug($level)
94              
95             Sets or gets the current debug level, 0-5.
96             Returns: Debug level
97              
98             =head2 _check_config()
99              
100             Conducts a basic check for kernel validity
101             Returns: true if checks out okay,
102             false if not okay,
103             undef on error
104              
105             =head2 _lookup()
106              
107             Converts title into position.
108             Takes: string.
109             Returns: integer,
110             undef on error
111              
112             =cut
113              
114              
115             package Linux::Bootloader;
116              
117 2     2   25351 use Linux::Bootloader::Detect;
  2         5  
  2         58  
118 2     2   13 use strict;
  2         3  
  2         61  
119 2     2   10 use warnings;
  2         3  
  2         55  
120              
121 2     2   20 use vars qw( $VERSION );
  2         3  
  2         17547  
122             our $VERSION = '1.2';
123              
124              
125             sub new {
126 1     1 1 3 my $this = shift;
127 1   33     6 my $class = ref($this) || $this;
128 1 50 33     12 if ( defined $class and $class eq 'Linux::Bootloader' ){
129 0         0 my $detected_bootloader = Linux::Bootloader::Detect::detect_bootloader();
130 0 0       0 unless (defined $detected_bootloader) { return undef; }
  0         0  
131 0         0 $class = "Linux::Bootloader::" . "\u$detected_bootloader";
132 0         0 eval" require $class; ";
133             }
134 1         4 my $self = bless ({}, $class);
135 1         10 $self->{config_file} = shift;
136 1 50       302 unless (defined $self->{'config_file'}){
137 1         7 $self->_set_config_file();
138             }
139              
140 1         3 $self->{config} = [];
141 1         3 $self->{debug} = 0;
142 1         3 $self->{'entry'} = {};
143              
144 1         36 return $self;
145             }
146              
147              
148             ### Generic Functions ###
149              
150             # Read config file into array
151              
152             sub read {
153 3     3 1 500 my $self=shift;
154 3   33     14 my $config_file=shift || $self->{config_file};
155 3 50       21 print ("Reading $config_file.\n") if $self->debug()>1;
156              
157 3 100 50     267 open(CONFIG, "$config_file")
158             || warn ("ERROR: Can't open $config_file.\n") && return undef;
159 2         100 @{$self->{config}}=;
  2         41  
160 2         38 close(CONFIG);
161              
162 2 50       7 print ("Current config:\n @{$self->{config}}") if $self->debug()>4;
  0         0  
163 2 50       7 print ("Closed $config_file.\n") if $self->debug()>2;
164 2         15 return 1;
165             }
166              
167              
168             # Write new config
169              
170             sub write {
171 3     3 1 2564 my $self=shift;
172 3   33     17 my $config_file=shift || $self->{config_file};
173 3         9 my @config=@{$self->{config}};
  3         37  
174              
175 3 50       15 return undef unless $self->_check_config();
176              
177 3 50       13 print ("Writing $config_file.\n") if $self->debug()>1;
178 3 50       9 print join("",@config) if $self->debug() > 4;
179              
180 3 50       135 if (-w $config_file) {
181 3         39062 system("cp","$config_file","$config_file.bak.boottool");
182 3 50       139 if ($? != 0) {
183 0         0 warn "ERROR: Cannot backup $config_file.\n";
184 0         0 return undef;
185             } else {
186 3         1372 print "Backed up config to $config_file.bak.boottool.\n";
187             }
188              
189 3 50 0     850 open(CONFIG, ">$config_file")
190             || warn ("ERROR: Can't open config file.\n") && return undef;
191 3         489 print CONFIG join("",@config);
192 3         172 close(CONFIG);
193 3         257 return 0;
194             } else {
195 0 0       0 print join("",@config) if $self->debug() > 2;
196 0         0 warn "WARNING: You do not have write access to $config_file.\n";
197 0         0 return 1;
198             }
199             }
200              
201              
202             # Parse config into array of hashes
203              
204             sub _info {
205 0     0   0 my $self=shift;
206              
207 0 0       0 return undef unless $self->_check_config();
208 0         0 my @config=@{$self->{config}};
  0         0  
209              
210             # remove garbarge - comments, blank lines
211 0         0 @config=grep(!/^#|^\n/, @config);
212              
213 0         0 my %matches = ( default => '^\s*default[\s+\=]+(\S+)',
214             timeout => '^\s*timeout[\s+\=]+(\S+)',
215             title => '^\s*label[\s+\=]+(\S+)',
216             root => '^\s*root[\s+\=]+(\S+)',
217             args => '^\s*append[\s+\=]+(.*)',
218             initrd => '^\s*initrd[\s+\=]+(\S+)',
219             );
220              
221 0         0 my @sections;
222 0         0 my $index=0;
223 0         0 foreach (@config) {
224 0 0       0 if ($_ =~ /^\s*(image|other)[\s+\=]+(\S+)/i) {
225 0         0 $index++;
226 0         0 $sections[$index]{'kernel'} = $2;
227             }
228 0         0 foreach my $key (keys %matches) {
229 0 0       0 if ($_ =~ /$matches{$key}/i) {
230 0         0 $sections[$index]{$key} = $1;
231 0 0       0 $sections[$index]{$key} =~ s/\"|\'//g if ($key eq 'args');
232             }
233             }
234             }
235              
236             # sometimes config doesn't have a default, so goes to first
237 0 0       0 if (!(defined $sections[0]{'default'})) {
    0          
238 0         0 $sections[0]{'default'} = '0';
239              
240             # if default is label name, we need position
241             } elsif ($sections[0]{'default'} !~ m/^\d+$/) {
242 0         0 foreach my $index (1..$#sections) {
243 0 0       0 if ($sections[$index]{'title'} eq $sections[0]{'default'}) {
244 0         0 $sections[0]{'default'} = $index-1;
245 0         0 last;
246             }
247             }
248             }
249              
250             # if still no valid default, set to first
251 0 0       0 if ( $sections[0]{'default'} !~ m/^\d+$/ ) {
252 0         0 $sections[0]{'default'} = 0;
253             }
254              
255             # return array of hashes
256 0         0 return @sections;
257             }
258              
259              
260             # Determine current default kernel
261              
262             sub get_default {
263 3     3 1 11 my $self = shift;
264              
265 3 50       22 print ("Getting default.\n") if $self->debug()>1;
266 3 50       26 return undef unless $self->_check_config();
267              
268 3         23 my @sections = $self->_info();
269 3         23 my $default = $sections[0]{'default'};
270 3 50       39 if ($default =~ /^\d+$/) {
271 3         45 return 0+$default;
272             }
273              
274             }
275              
276              
277             # Set new default kernel
278              
279             sub set_default {
280 0     0 1 0 my $self=shift;
281 0         0 my $newdefault=shift;
282              
283 0 0       0 print ("Setting default.\n") if $self->debug()>1;
284              
285 0 0       0 return undef unless defined $newdefault;
286 0 0       0 return undef unless $self->_check_config();
287              
288 0         0 my @config=@{$self->{config}};
  0         0  
289 0         0 my @sections=$self->_info();
290              
291             # if not a number, do title lookup
292 0 0       0 if ($newdefault !~ /^\d+$/) {
293 0         0 $newdefault = $self->_lookup($newdefault);
294             }
295              
296 0         0 my $kcount = $#sections-1;
297 0 0 0     0 if ((!defined $newdefault) || ($newdefault < 0) || ($newdefault > $kcount)) {
      0        
298 0         0 warn "ERROR: Enter a default between 0 and $kcount.\n";
299 0         0 return undef;
300             }
301              
302             # convert position to title
303 0         0 $newdefault = $sections[++$newdefault]{title};
304            
305 0         0 foreach my $index (0..$#config) {
306 0 0       0 if ($config[$index] =~ /^\s*default/i) {
307 0         0 $config[$index] = "default=$newdefault # set by $0\n";
308 0         0 last;
309             }
310             }
311 0         0 @{$self->{config}} = @config;
  0         0  
312             }
313              
314              
315             # Add new kernel to config
316              
317             sub add {
318 0     0 1 0 my $self=shift;
319 0         0 my %param=@_;
320              
321 0 0       0 print ("Adding kernel.\n") if $self->debug()>1;
322              
323 0 0 0     0 if (!defined $param{'add-kernel'} && defined $param{'kernel'}) {
    0 0        
    0 0        
    0          
324 0         0 $param{'add-kernel'} = $param{'kernel'};
325             } elsif (!defined $param{'add-kernel'} || !defined $param{'title'}) {
326 0         0 warn "ERROR: kernel path (--add-kernel), title (--title) required.\n";
327 0         0 return undef;
328             } elsif (!(-f "$param{'add-kernel'}")) {
329 0         0 warn "ERROR: kernel $param{'add-kernel'} not found!\n";
330 0         0 return undef;
331             } elsif (defined $param{'initrd'} && !(-f "$param{'initrd'}")) {
332 0         0 warn "ERROR: initrd $param{'initrd'} not found!\n";
333 0         0 return undef;
334             }
335              
336 0 0       0 return undef unless $self->_check_config();
337              
338             # remove title spaces and truncate if more than 15 chars
339 0         0 $param{title} =~ s/\s+//g;
340 0 0       0 $param{title} = substr($param{title}, 0, 15) if length($param{title}) > 15;
341              
342 0         0 my @sections=$self->_info();
343              
344             # check if title already exists
345 0 0       0 if (defined $self->_lookup($param{title})) {
346 0         0 warn ("WARNING: Title already exists.\n");
347 0 0       0 if (defined $param{force}) {
348 0         0 $self->remove($param{title});
349             } else {
350 0         0 return undef;
351             }
352             }
353              
354 0         0 my @config = @{$self->{config}};
  0         0  
355 0         0 @sections=$self->_info();
356            
357             # Use default kernel to fill in missing info
358 0         0 my $default=$self->get_default();
359 0         0 $default++;
360              
361 0         0 foreach my $p ('args', 'root') {
362 0 0       0 if (! defined $param{$p}) {
363 0         0 $param{$p} = $sections[$default]{$p};
364             }
365             }
366              
367             # use default entry to determine if path (/boot) should be removed
368 0 0       0 if ($sections[$default]{'kernel'} !~ /^\/boot/) {
369 0         0 $param{'add-kernel'} =~ s/^\/boot//;
370 0 0       0 $param{'initrd'} =~ s/^\/boot// unless (!defined $param{'initrd'});
371             }
372              
373 0         0 my @newkernel;
374 0         0 push (@newkernel, "image=$param{'add-kernel'}\n", "\tlabel=$param{title}\n");
375 0 0       0 push (@newkernel, "\tappend=\"$param{args}\"\n") if defined $param{args};
376 0 0       0 push (@newkernel, "\tinitrd=$param{initrd}\n") if defined $param{initrd};
377 0 0       0 push (@newkernel, "\troot=$param{root}\n") if defined $param{root};
378 0         0 push (@newkernel, "\tread-only\n\n");
379              
380 0 0 0     0 if (!defined $param{position} || $param{position} !~ /end|\d+/) {
381 0         0 $param{position}=0;
382             }
383              
384 0         0 my @newconfig;
385 0 0 0     0 if ($param{position}=~/end/ || $param{position} >= $#sections) {
386 0         0 $param{position}=$#sections;
387 0         0 push (@newconfig,@config);
388 0 0       0 if ($newconfig[$#newconfig] =~ /\S/) {
389 0         0 push (@newconfig, "\n");
390             }
391 0         0 push (@newconfig,@newkernel);
392             } else {
393 0         0 my $index=0;
394 0         0 foreach (@config) {
395 0 0       0 if ($_ =~ /^\s*(image|other)/i) {
396 0 0       0 if ($index==$param{position}) {
397 0         0 push (@newconfig, @newkernel);
398             }
399 0         0 $index++;
400             }
401 0         0 push (@newconfig, $_);
402             }
403             }
404              
405 0         0 @{$self->{config}} = @newconfig;
  0         0  
406              
407 0 0       0 if (defined $param{'make-default'}) {
408 0         0 $self->set_default($param{position});
409             }
410             }
411              
412              
413             # Update kernel args
414              
415             sub update {
416 0     0 1 0 my $self=shift;
417 0         0 my %params=@_;
418              
419 0 0       0 print ("Updating kernel.\n") if $self->debug()>1;
420              
421 0 0 0     0 if (!defined $params{'update-kernel'} || (!defined $params{'args'} && !defined $params{'remove-args'})) {
      0        
422 0         0 warn "ERROR: kernel position or title (--update-kernel) and args (--args or --remove-args) required.\n";
423 0         0 return undef;
424             }
425              
426 0 0       0 return undef unless $self->_check_config();
427              
428 0         0 my @config = @{$self->{config}};
  0         0  
429 0         0 my @sections=$self->_info();
430              
431             # if not a number, do title lookup
432 0 0       0 if ($params{'update-kernel'} !~ /^\d+$/) {
433 0         0 $params{'update-kernel'} = $self->_lookup($params{'update-kernel'});
434             }
435              
436 0         0 my $kcount = $#sections-1;
437 0 0 0     0 if ($params{'update-kernel'} !~ /^\d+$/ || $params{'update-kernel'} < 0 || $params{'update-kernel'} > $kcount) {
      0        
438 0         0 warn "ERROR: Enter a default between 0 and $kcount.\n";
439 0         0 return undef;
440             }
441              
442 0         0 my $index=-1;
443 0         0 foreach (@config) {
444 0 0       0 if ($_ =~ /^\s*(image|other)/i) {
445 0         0 $index++;
446             }
447 0 0       0 if ($index==$params{'update-kernel'}) {
448 0 0       0 if ($_ =~ /(^\s*append[\s\=]+)(.*)\n/i) {
449 0         0 my $append = $1;
450 0         0 my $args = $2;
451 0         0 $args =~ s/\"|\'//g;
452 0 0       0 $args =~ s/\s*$params{'remove-args'}\=*\S*//ig if defined $params{'remove-args'};
453 0 0       0 $args = $args . " ". $params{'args'} if defined $params{'args'};
454 0 0       0 if ($_ eq "$append\"$args\"\n") {
455 0         0 warn "WARNING: No change made to args.\n";
456 0         0 return undef;
457             } else {
458 0         0 $_ = "$append\"$args\"\n";
459             }
460 0         0 next;
461             }
462             }
463             }
464 0         0 @{$self->{config}} = @config;
  0         0  
465             }
466              
467              
468             # Remove kernel from config
469              
470             sub remove {
471 3     3 1 11 my $self=shift;
472 3         8 my $position=shift;
473 3         6 my @newconfig;
474              
475 3 50       21 return undef unless defined $position;
476 3 50       25 return undef unless $self->_check_config();
477              
478 3         6 my @config=@{$self->{config}};
  3         38  
479 3         1236 my @sections=$self->_info();
480              
481 3 50       24 if ($position=~/^end$/i) {
    50          
482 0         0 $position=$#sections-1;
483             } elsif ($position=~/^start$/i) {
484 0         0 $position=0;
485             }
486              
487 3 50       23 print ("Removing kernel $position.\n") if $self->debug()>1;
488              
489             # remove based on title
490 3 100       42 if ($position !~ /^\d+$/) {
    50          
491 1         7 my $removed=0;
492 1         6 for (my $index=$#sections; $index > 0; $index--) {
493 3 50 33     30 if (defined $sections[$index]{title} && $position eq $sections[$index]{title}) {
494 0 0       0 $removed++ if $self->remove($index-1);
495             }
496             }
497 1 50       14 if (! $removed) {
498 1         164 warn "ERROR: No kernel with specified title.\n";
499 1         23 return undef;
500             }
501              
502             # remove based on position
503             } elsif ($position =~ /^\d+$/) {
504              
505 2 100 66     26 if ($position < 0 || $position > $#sections) {
506 1         149 warn "ERROR: Enter a position between 0 and $#sections.\n";
507 1         23 return undef;
508             }
509              
510 1         6 my $index=-1;
511 1         3 foreach (@config) {
512 19 100       61 if ($_ =~ /^\s*(image|other|title)/i) {
513 3         5 $index++
514             }
515             # add everything to newconfig, except removed kernel (keep comments)
516 19 100 66     59 if ($index != $position || $_ =~ /^#/) {
517 15         25 push (@newconfig, $_)
518             }
519             }
520 1         6 @{$self->{config}} = @newconfig;
  1         9  
521              
522              
523             # if we removed the default, set new default to first
524 1 50       14 $self->set_default(0) if $position == $sections[0]{'default'};
525              
526 1         2156 print "Removed kernel $position.\n";
527 1         44 return 1;
528              
529             } else {
530 0         0 warn "WARNING: problem removing entered position.\n";
531 0         0 return undef;
532             }
533              
534             }
535              
536              
537             # Print info from config
538              
539             sub print_info {
540 3     3 1 6 my $self=shift;
541 3         6 my $info=shift;
542              
543 3 50       10 return undef unless defined $info;
544 3 50       14 return undef unless $self->_check_config();
545              
546 3 50       7 print ("Printing config info.\n") if $self->debug()>1;
547              
548 3         5 my @config=@{$self->{config}};
  3         170  
549 3         12 my @sections=$self->_info();
550              
551 3         7 my ($start,$end);
552 3 50       23 if ($info =~ /default/i) {
    50          
    50          
553 0         0 $start=$end=$self->get_default()
554             } elsif ($info =~ /all/i) {
555 0         0 $start=0; $end=$#sections-1
  0         0  
556             } elsif ($info =~ /^\d+/) {
557 0         0 $start=$end=$info
558             } else {
559 3         307 warn "ERROR: input should be: #, default, or all.\n";
560 3         47 return undef;
561             }
562              
563 0 0 0     0 if ($start < 0 || $end > $#sections-1) {
564 0         0 warn "ERROR: No kernels with that index.\n";
565 0         0 return undef;
566             }
567              
568 0         0 for my $index ($start..$end) {
569 0         0 print "\nindex\t: $index\n";
570 0         0 $index++;
571 0         0 foreach ( sort keys(%{$sections[$index]}) ) {
  0         0  
572 0         0 print "$_\t: $sections[$index]{$_}\n";
573             }
574             }
575             }
576              
577              
578             # Set/get debug level
579              
580             sub debug {
581 69     69 1 99 my $self=shift;
582 69 50       173 if (@_) {
583 0         0 $self->{debug} = shift;
584             }
585 69   50     1692 return $self->{debug} || 0;
586             }
587              
588             # Get a bootloader entry as a hash to edit or update.
589             sub read_entry {
590 0     0 1 0 my $self=shift;
591 0         0 my $entry=shift;
592              
593 0 0       0 if ($entry !~ /^\d+$/) {
594 0         0 $entry = $self->_lookup($entry);
595             }
596 0         0 my @sections=$self->_info();
597              
598 0         0 my $index = $entry + 1;
599 0 0       0 if ((defined $sections[$index]{'title'})) {
600 0         0 $self->{'entry'}->{'index'} = $index;
601 0         0 foreach my $key ( keys %{$sections[$index]} ){
  0         0  
602 0         0 $self->{'entry'}->{'data'}->{ $key } = $sections[$index]{$key};
603             }
604 0         0 return $self->{'entry'}->{'data'};
605             } else {
606 0         0 return undef;
607             }
608             }
609              
610             # Basic check for valid config
611              
612             sub _check_config {
613 41     41   88 my $self=shift;
614              
615 41 50       110 print ("Verifying config.\n") if $self->debug()>3;
616              
617 41 50       71 if ($#{$self->{config}} < 5) {
  41         364  
618 0         0 warn "ERROR: you must read a valid config file first.\n";
619 0         0 return undef;
620             }
621 41         215 return 1;
622             }
623              
624              
625             # lookup position using title
626              
627             sub _lookup {
628 4     4   14 my $self=shift;
629 4         7 my $title=shift;
630            
631 4 50       14 unless ( defined $title ){ return undef; }
  0         0  
632              
633 4         42 my @sections=$self->_info();
634              
635 4         24 for my $index (1..$#sections) {
636 10         21 my $tmp = $sections[$index]{title};
637 10 100 66     66 if (defined $tmp and $title eq $tmp) {
638 1         14 return $index-1;
639             }
640             }
641 3         25 return undef;
642             }
643              
644              
645             =head1 AUTHOR
646              
647             Jason N., Open Source Development Labs, Engineering Department
648              
649             =head1 COPYRIGHT
650              
651             Copyright (C) 2006 Open Source Development Labs
652             All Rights Reserved.
653              
654             This script is free software; you can redistribute it and/or modify it
655             under the same terms as Perl itself.
656              
657             =head1 SEE ALSO
658              
659             L, L, L,
660             L, L
661              
662             =cut
663              
664              
665             1;