
Here is a list of possible refactoring ideas.  These may or may not
improve the code, but could be worth thinking about...


You've kept a good deal of similarity between the different modules,
which is good.  That'll make it easier to spot places where identical
code can be factored out and moved to the base class.  :-)



In the info() functions, there is a foreach loop with a set of regular
expressions to match portions of the config file in an if-then chain.
An alternate approach would be to construct an array of regular
expressions.  I.e., something like:

my %regex = ( default => '^\s*default[\s+\=]+(\S+)',
              timeout => '^\s*timeout[\s+\=]+(\S+)',
              kernel  => '^\s*(?:image|other)[\s+\=]+(\S+)',
              title   => '^\s*label[\s+\=]+(\S+)',
              etc.
              );

Then later you can do:

foreach (@config) {
    foreach my $key (keys %regex) {
        if ($_ =~ /$regex{$key}/i) {
            $index++ if ($key eq 'kernel');
            $sections[$index]{$key} = $1;
        }
    }
}

Since this snippet of code doesn't have anything bootloader-specific in
it, you could probably even put it in the base Linux::Bootloader class, 
as a helper function to call from within info().



At the end of info() is some code to ensure there is a defined default,
and a bit of code to get the default position instead of the default
title.  You could move this code into Linux::Bootloader::info(), and
then call it at the end of info() like this:

    $self->SUPER::info(@sections);

Or, perhaps it would be better to have a small helper function in the
base class that performs that code, that you could call as in

    @sections = $self->_info_fix_defaults(@sections);



sub get_default() looks like it'll be identical for all the subclasses.
Just move it into Linux::Bootloader and remove it from Elilo.pm,
Lilo.pm, etc.

Also, I notice it's got a line '  my @config=@{$self->{config}}; ', but
@config isn't used anywhere, so that's probably not needed.


set_default() is basically the same in all of the subclasses.  For Lilo,
Elilo, and Yaboot, you can simply move set_default() to
Linux::Bootloader, and remove it from those files.  For Grub, you've got
a biut more to do, but can do that via a little override:


package Linux::Bootloader::Grub;

# ...

sub set_default {
  my $self = shift;

  $self->SUPER::set_default(@_);

  foreach my $line (@{$self->{config}}) {
    if ($line =~ /^\s*default\ssaved/i) {
      my @default_config;

      my $default_config_file='/boot/grub/default';
      open(DEFAULT_FILE, $default_config_file)
        || warn ("ERROR:  cannot open default file.\n") && return undef;
      @default_config = <DEFAULT_FILE>;
      close(DEFAULT_FILE);

      $default_config[0] = "$newdefault\n";

      open(DEFAULT_FILE, ">$default_config_file")
        || warn ("ERROR:  cannot open default file.\n") && return undef;
      print DEFAULT_FILE join("",@default_config);
      close(DEFAULT_FILE);
      last;
    }
  }
}


Okay, add() is very interesting.  I'll bet this is the one you feel is
the most ugly in terms of code duplication.  Looking across all the
bootloader files, this function is roughly identical in all of them,
except for a few areas that are very bootloader specific.

Here is a refactoring technique to use here.  Move the add() function
into the Linux::Bootloader base class, but anyplace where there could be
a bootloader-specific set of code, put a call to another function, that
does nothing (or something generic) in the base class.  Then subclasses
can override some or all of these functions as needed.

For example, Linux::Bootloader::add() might look like this:

sub add {
  my $self  = shift;
  my %param = @_;
  # ....
  } elsif (defined $param{'initrd'} && !(-f "$param{'initrd'}")) {
    warn "ERROR:  initrd $param{'initrd'} not found!\n";
    return undef;
  }

  return undef unless $self->check_config;

  return undef unless $self->on_validate_add_params(\%param);

  my @sections=$self->info();
  # ....
}

You'd create an empty validate_add_params() in Linux::Bootloader, and
leave it undefined for Grub, but for Linux::Bootloader::Lilo, you might
define it like this:

sub on_validate_add_parameters {
  my $self = shift;
  my $params = shift;

  # remove title spaces and truncate if more than 15 chars
  $param->{title} =~ s/\s+//g;
  $param->{title} = substr($param->{title}, 0, 15) if length($param->{title}) > 15;
  return 1;
}

This way, instead of having add() reimplemented in all four modules,
you'd only have one generic version in the base class, and a few of
these little specialty handlers in each subclass, to do custom stuff.



This bit:

  if (!defined $param{args} || $param{args} !~ /\S/) {
    $param{args}=$sections[$default]{'args'}
  }
  if (!defined $param{root} || $param{root} !~ /\S/) {
    $param{root}=$sections[$default]{'root'}
  }
  if (!defined $param{boot} || $param{boot} !~ /\S/) {
    $param{boot}=$sections[$default]{'boot'}
  }
  if (!defined $param{savedefault} || $param{savedefault} !~ /\S/) {
    $param{savedefault}=$sections[$default]{'savedefault'}
  }

could be done a bit more concisely with a little loop:

  foreach my $p ('args', 'root', 'boot', 'savedefault') {
    if (! defined $param{$p} || $param{$p} !~ /\S/) {
      $param{$p} = $sections[$default]{$p};
    }
  }



remove() is the same situation as in add() - largely the same algorithm,
but with a few little bootloader specific sections.  I would use the
same approach as described above.



I notice in each routine you have a short section 'if not a number, do
title lookup'.  I bet that'd make a good helper function, that could be
put in the base class.



In the update() in Grub.pm, you might think about if it can be kept
generic enough that it could be put into the base class (perhaps using a
trick or two from above to handle any Grub-specific stuff).



Beyond that... looks very good!  The number of functions, their name,
the syntax style, etc. all look very clean.  The algorithms look like
they've been well thought out and are fairly easy to follow.  You've got
good error checking and error messages being issued in appropriate
spots.  