#!/usr/bin/perl
#
# /usr/sbin/liloconfig -- configure lilo automatically using debconf
#
# Author:	Bruce Perens <bruce@Pixar.com>
#		Bernd Eckenfels <ecki@debian.org>
#		Vincent Renardias <vincent@ldsol.com>
#		Peter Maydell <pmaydell@chiark.greenend.org.uk>
#		Russell Coker <russell@coker.com.au>
#               Andrés Roldán <aroldan@debian.org>
#               William Pitcock <nenolod@dereferenced.org>
#
# Maintainer:   William Pitcock <nenolod@dereferenced.org>

# Updated on 2008/07/26 -- William Pitcock <nenolod@dereferenced.org>
# - Use large-memory by default, hoping that BIOSes now days aren't
#   so broken.
#
# Updated on 2008/06/16 -- William Pitcock <nenolod@sacredspiral.co.uk>
# - Warn about images larger than 6.5MiB because some chipsets
#   cannot load them in real mode.
#
# Updated on 2006/08/27 -- Artur R. Czechowski <arturcz@hell.pl>
# - Communication with user is handled via debconf
#
# Updated on 2004/07/31 -- Andrés Roldán <aroldan@debian.org>
# - Added get_bitmap() to allow one choose the bitmap to use.
#
# Updated on 2003/12/20 -- Andrés Roldán <aroldan@debian.org>
# - Modified get_images() to insert initrd= field when necessary.
#   See Bug#224368.
#   
# Updated on 2003/10/24 -- Andrés Roldán <aroldan@debian.org>
# - Now liloconfig will smartly get the installed kernel images
#   and put them in the configuration file.
#
# Updated on 2003/10/05 -- Andrés Roldán <aroldan@debian.org>
# - Updated install= comments . See Bug#183471.
# - Not using depecrated install=/boot/*b options.
#
# Updated on 2003/08/07 -- Andrés Roldán <aroldan@fluidsignal.com>
# - bitmap= field will search bitmaps on /boot 
#
# Updated on 2003/04/23 -- Andrés Roldán <aroldan@fluidsignal.com>
# - added menu entry for sid and sarge from debian-bootscreen.
#
# Updated on 1999/01/24 -- Vincent Renardias <vincent@ldsol.com>
#  - never return 0 on error.
#  - updated the template to produce a more helpfull (commentwise)
#      resulting lilo.conf
#
# Updated on 1999/11/24 -- Peter Maydell <pmaydell@chiark.greenend.org.uk>
#  - added error checking of various system calls
#  - added $DEBUG switch and pulled lilo.conf and fstab filenames
#    out into config variables.
#  - turned on Perl's -w switch and use strict subs/refs
#  - now does examination of current situation up front, separated
#    from the logic of what we do in various situations.
 #  - added check for special marker in /etc/fstab that indicates that we
 #    are configuring the base filesystem and shouldn't actually do anything.
 #
 # Updated on 2000-01-23 -- Peter Maydell <pmaydell@chiark.greenend.org.uk>
 #  - fixed a (harmless) warning produced if /etc/fstab had a blank line in it
 #  - fixed a bug where partition number was not being pulled out of the
 #    /dev/hda4 string correctly.
 #  - added a paranoia check that the disk/device we get from fstab actually
 #    exist in the filesystem.
 #  - fixed flow-of-control problem where we simply weren't ever installing
 #    MBR and making partition active.
 #  - added & to some function calls, for consistency.
 #  - changed all references to /usr/doc/lilo/ to /usr/share/doc/lilo/.
 #  - added warning that the lilo.conf we produce does not suffice for
 #    complicated situations.
 #  - expanded some of the other prompts and explanatory text presented
 #    to the user.
 #  - added a 'ruler' to separate out questions
 #  - questions no longer accept anything except RET as meaning 'go with
 #    default answer'. [Previously, if the default answer was 'yes',
 #    anything not beginning with Y or y would be interpreted as 'no'.
 #    This is IMHO too lenient considering the consequences of getting
 #    it wrong...]
 #  - updated Maintainer, Author and Wishlist...
 #
 # Updated on 2000-01-25 -- Peter Maydell <pmaydell@chiark.greenend.org.uk>
 #  - fixed important bug causing lilo not to install on RAID arrays.
 #    (Bugs #56153,#56183,#56196)
 #  - minor improvement to a regexp (no actual functional change) (Bug#56127)
 #  - expanded cryptic 'iff' comment (Bug#56127)
 #
 # Updates on 2001-05-04 -- Russell Coker <russell@coker.com.au>
 #  - more work on devfs root support

 # Wishlist:
 # This script is from Bruce's debian.postinst and need to be more
 # intelligent. It should be possible to install lilo in the MBR, too.
 # Support for systems which dual boot (esp. dual Linux/Windows) would
 # also be nice, but could be complicated to get right.

 ###use strict 'subs';
 ###use strict 'refs';
 #### use strict 'vars' falls over on all the global variables :->
use Debconf::Client::ConfModule qw(:all);
version('2.0');
$|=1;

 fset("liloconfig/banner","seen","false");
 fset("liloconfig/use_current_lilo","seen","false");
 fset("liloconfig/wipe_old_liloconf","seen","false");
 fset("liloconfig/instruction","seen","false");
 fset("liloconfig/install_from_root_device","seen","false");
 fset("liloconfig/use_lba32","seen","false");
 fset("liloconfig/install_mbr","seen","false");
 fset("liloconfig/make_active_partition","seen","false");
 fset("liloconfig/select_bitmap","seen","false");

 # Set this to 1 to disable all commands that do things to the
 # hard disk (ie actually running lilo). Note that we still write
 # to $LILOCONF, so you should also tweak that to get a 'safe' test
 # environment.
 #$DEBUG=1;

 # Various files we access
 $LILOCONF='/etc/lilo.conf';
 $FSTAB='/etc/fstab';

 subst("liloconfig/liloconf_exists","liloconf",$LILOCONF);
 subst("liloconfig/lilo_warning","liloconf",$LILOCONF);
 subst("liloconfig/lilo_error","liloconf",$LILOCONF);
 subst("liloconfig/odd_fstab","liloconf",$LILOCONF);
 subst("liloconfig/instruction","liloconf",$LILOCONF);

 subst("liloconfig/fstab_broken","fstab",$FSTAB);
 subst("liloconfig/odd_fstab","fstab",$FSTAB);

 # Print a banner now, to give the user something to look at while
 # we ferret around in the fstab...

 settitle("liloconfig/maintitle");
 input("low","liloconfig/banner");

 # First we analyse the setup and set variables appropriately
 $fstab_broken = 1;	    # is there a valid /etc/fstab? Assume not and prove otherwise.
 $liloconf_exists = 0;	 # is there a preexisting lilo.conf with a non-commented out line?
 $liloconf_incompatible = 0;   # does lilo.conf use options not valid for this version of lilo?
 $configuring_base = 0;	# are we configuring the 'base' filesystem (special case)
 $odd_fstab = 0;         # set if we don't understand the device in the fstab
 # We also set $device, $disk, $partition (assuming fstab_broken == 0)

 if (-f $FSTAB) 
 {
     # Parse fstab for the root partition...
     open(FSTAB, "<$FSTAB") or die "liloconfig: couldn't open $FSTAB: $!\n";
     while (<FSTAB>) 
     {
	 # Check for a magic string which indicates that we are configuring
	 # the base filesystem and not a real machine...
	 $configuring_base = 1 if /^# UNCONFIGURED FSTAB FOR BASE SYSTEM/;
	 next if /^#/;	  # ignore comment lines
	 s/^[ \t]+//;	  # remove space or tab at begin of the line
	 ($device,$filesystem) = split(/[ \t]+/);
	 next unless defined $filesystem;   # ignore empty lines too
	 # Stop if we found the root device...
	 if ($filesystem eq '/') 
	 {
	     $fstab_broken = 0;
	     last;
	 }
     }
     close(FSTAB) or die "liloconfig: couldn't close $FSTAB: $!\n";
 }

 if (! $fstab_broken) 
 {
     # Valid device/filesystem pair, parse them
     $disk = `/usr/sbin/lilo_find_mbr $device`;
     chomp($disk);
     $partition = $device;
     my $devfs = 0;

     # Paranoia check: there should be valid /dev/ nodes for these.
     # We could check for block-special-device-ness, but perhaps
     # some people have symlink forests in /dev/ ?
     # This check will fail on things like RAID arrays, where the
     # devices don't have names like /dev/hda4. In this case we can't
     # do simple autoconfiguration, but we still want to be able
     # to allow the user to install their own handrolled lilo.conf.
     $odd_fstab = 1 unless ($partition =~ /\d+$/ && -e $disk && -e $device);
 }

 # Check for an existing lilo.conf with some non-comment lines in it...
 system ("grep -qsv '^#' $LILOCONF");
 # Exit status is 0 iff lilo.conf exists and contains at least one non-comment line.
 if ($? == 0) 
 {
     $liloconf_exists = 1;
     $liloconf_incompatible = &compatibility_check ();
 }

 ##########################################################
 # Boilerplate arrays used to produce an initial lilo.conf
 ##########################################################

 @header = (
	 "# Generated by liloconfig\n",
	 "\n",
	 );

 @bootheader = (
		"# Specifies the boot device\n",
		);

 @rootheader = (
		"\n",
		"# Specifies the device that should be mounted as root.\n",
		"# If the special name CURRENT is used, the root device is set to the\n",
		"# device on which the root file system is currently mounted. If the root\n",
		"# has been changed with  -r , the respective device is used. If the\n",
		"# variable ROOT is omitted, the root device setting contained in the\n",
		"# kernel image is used. It can be changed with the rdev program.\n"
		);

 @boilerplate1 = (
		  "\n",
		  "# Enables map compaction:\n",
		  "# Tries to merge read requests for adjacent sectors into a single\n",
		  "# read request. This drastically reduces load time and keeps the map\n",
		  "# smaller. Using COMPACT is especially recommended when booting from a\n",
		  "# floppy disk.\n",
		  "# compact\n",
		  "\n",
		  "# Install the specified file as the new boot sector.\n",
		  "# LILO supports built in boot sectory, you only need\n",
		  "# to specify the type, choose one from 'text', 'menu' or 'bitmap'.\n",
		  "# new: install=bmp      old: install=/boot/boot-bmp.b\n",
		  "# new: install=text     old: install=/boot/boot-text.b\n",
		  "# new: install=menu     old: install=/boot/boot-menu.b or boot.b\n",
		  "# default: 'menu' is default, unless you have a bitmap= line\n",
		  "# Note: install=bmp must be used to see the bitmap menu.\n",
		  "# install=menu\n",
		  "install=bmp\n",
		  "\n",
		  "# Specifies the number of _tenths_ of a second LILO should\n",
		  "# wait before booting the first image.  LILO\n",
		  "# doesn't wait if DELAY is omitted or if DELAY is set to zero.\n",
		  "# delay=20\n",
		  "\n",
		  "# Prompt to use certaing image. If prompt is specified without timeout,\n",
		  "# boot will not take place unless you hit RETURN\n",
		  "prompt\n",
		  "timeout=50\n",
		  "\n",
		  "# Enable large memory mode.\n",
		  "large-memory\n",
		  "\n",
		  "# Specifies the location of the map file. If MAP is\n",
		  "# omitted, a file /boot/map is used.\n",
		  "map=/boot/map\n",
		  "\n",
		  "# Specifies the VGA text mode that should be selected when\n",
		  "# booting. The following values are recognized (case is ignored):\n",
		  "#   NORMAL  select normal 80x25 text mode.\n",
		  "#   EXTENDED  select 80x50 text mode. The word EXTENDED can be\n",
		  "#     abbreviated to EXT.\n",
		  "#   ASK  stop and ask for user input (at boot time).\n",
		  "#   <number>  use the corresponding text mode. A list of available modes\n",
		  "#     can be obtained by booting with  vga=ask  and pressing [Enter].\n",
		  "vga=normal\n",
		  "\n",
		  "# Defines non-standard parameters for the specified disk.\n",
		  "#disk=/dev/sda\n",
		  "#\tbios=0x80\n",
		  "\n",
		  "# If you are using removable USB drivers (with mass-storage)\n",
		  "# you will need to tell LILO to not use these devices even\n",
		  "# if defined in /etc/fstab and referenced in /proc/partitions.\n",
		  "# Adjust these lines to your devices:\n",
		  "#\n",
		  "# disk=/dev/sda inaccessible\n",
		  "# disk=/dev/sdb inaccessible\n",
		  "\n"
		  );

 @boilerplate2 = (
		  "# If you have another OS on this machine (say DOS),\n",
		  "# you can boot if by uncommenting the following lines\n",
		  "# (Of course, change /dev/hda2 to wherever your DOS partition is.)\n",
		  "# other=/dev/hda2\n",
		  "#   label=\"MS Windows\"\n",
		  "\n"
		  );

 ####################
 # Utility functions
 ####################

sub get_bitmap
{
    input("high","liloconfig/select_bitmap");
    go();
    ($ret,$val) = get("liloconfig/select_bitmap");
    
    @bitmaps = (
		   [ "/boot/sarge.bmp","1,,0,2,,0","120p,173p,1,15,17","254p,432p,1,0,0" ],
		   [ "/boot/sid.bmp","1,,0,2,,0","120p,173p,1,15,17","254p,432p,1,0,0" ],
		   [ "/boot/coffee.bmp","12,,11,15,,8","385p,100p,1,10","38,2,13,1" ],
		   [ "/boot/debianlilo.bmp","1,,0;9,,0","106p,144p,2,9,144p","514p,144p,6,8,0" ]
		   );

    $counter = 0;

    for $ref (@bitmaps)
    {
	last if ($val eq @$ref[0]);
	$counter++;
    }

    $ans = $counter;

    push (@bitmapconf, "\n# Bitmap configuration for $bitmaps[$ans][0]\n");
    push (@bitmapconf, "bitmap=$bitmaps[$ans][0]\n");
    push (@bitmapconf, "bmp-colors=$bitmaps[$ans][1]\n");
    push (@bitmapconf, "bmp-table=$bitmaps[$ans][2]\n");
    push (@bitmapconf, "bmp-timer=$bitmaps[$ans][3]\n");

    return @bitmapconf;
}

sub get_images
{
    my @images = ();

    $count = 0;
   
    print STDERR "Searching for installed kernels and updating image entries ...\n";
   
    push (@images, "# These images were automagically added. You may need to edit something.\n\n");
    
    # Ignore that /vmlinuz link since it can surely be poiting to 
    # some /boot/vmlinuz* file
    if (-e "/vmlinuz" && ! -l "/vmlinuz")
    {
	push (@images, "image=/vmlinuz\n");
	push (@images, "\tlabel=\"Linux\"\n");
	if (-e "/initrd.img")
	{
 	    push (@images, "\tinitrd=/initrd.img\n");
	}
	push (@images, "\tread-only\n");
	push (@images, "\n");
    }

    # Ignore that /boot/vmlinuz link as well since it can surely be 
    # poiting to some /boot/vmlinuz* file
    if (-e "/boot/vmlinuz" && ! -l "/boot/vmlinuz")
    {
        push (@images, "image=/boot/vmlinuz\n");
        push (@images, "\tlabel=\"Linux 1\"\n");
        if (-e "/boot/initrd.img")
        {
            push (@images, "\tinitrd=/boot/initrd.img\n");
        }
        push (@images, "\tread-only\n");
        push (@images, "\n");
    }

    foreach $image (`/bin/ls /boot/vmlinuz*`)
    {
        chomp $image;

	my $version = "";
	my $complement = "";
	
        if ($image =~ /vmlinuz-(\d+\.\d+\.\d+)-(.+)/)
        {
	    $version = $1;
	    $complement = $2;
	    
            $label = "Lin " . $version . "img" . $count;
        }
	elsif ($image =~ /vmlinuz-(\d+\.\d+\.\d+)$/)
	{
	    $version = $1;
	    
	    $label = "Lin " . $version . "img" . $count;
	}
	else
        {
            $label = "Lin " . "img" . $count;
        }
	
        push (@images, "image=$image\n");
        push (@images, "\tlabel=\"$label\"\n");
	
	if (-e "/boot/initrd.img-$version-$complement")
	{
	    push (@images, "\tinitrd=/boot/initrd.img-$version-$complement\n");
 	}
	elsif (-e "/boot/initrd.img-$version")
	{
	    push (@images, "\tinitrd=/boot/initrd.img-$version\n");
	}

        push (@images, "\tread-only\n");
	push (@images, "\n");
	
        $count++;
    }

    if (-e "/boot/memtest86.bin")
    {
        push (@images, "image=/boot/memtest86.bin\n");
        push (@images, "\tlabel=\"Memory Test\"\n");
        push (@images, "\tread-only\n");
        push (@images, "\n");
    }

    if (-e "/boot/memtest86+.bin")
    {
	push (@images, "image=/boot/memtest86+.bin\n");
	push (@images, "\tlabel=\"Memory Test+\"\n");
	push (@images, "\tread-only\n");
	push (@images, "\n");
    }
	    
    return @images;
}

sub asky 
{
    do 
    {
	print STDERR @_,"? [Yes] ";
	$answer=<STDIN>;
    } 
    while ($answer ne "\n" && !($answer =~ /^[YyNn].*/));
    &ruler ();
    return ( !($answer =~ /^[nN].*/) );
}

sub askn 
{
    do 
    {
	print STDERR @_,"? [No] ";
	$answer=<STDIN>;
    } 
    while ($answer ne "\n" && !($answer =~ /^[YyNn].*/));
    &ruler ();
    return ( $answer =~ /^[yY].*/ );
}

sub compatibility_check 
{
    # Check a lilo.conf for options which are not compatible
    # with the current version of lilo, and return 1 if any
    # incompatible usages are found.
    # This currently just checks for use of the any_* loaders.
    system ("egrep '^[^#]*any_' $LILOCONF");
    return 1 if ($? == 0);
    return 0;
}

sub safe_system($)
{
    # Works like system(), but just echoes the command that would
    # be run if $DEBUG is 1.
    if ($DEBUG) {
	print STDERR "[Would have run: ", join(' ', @_), "]\n";
	$? = 0;
    } 
    else 
    {
	system($_[0].">&2");
    }
}

#####################################
# Actual work is done below here...
#####################################

# Debian's 'base' filesystem is a special case -- it's prebuilt
# by installing and configuring packages into a subdirectory,
# which is then made into a tarball, which is then used to
# make the initial filesystem for a fresh Debian install.
# Thus we can't actually run LILO now, because we know nothing
# of the disk layout. That will be done as part of the install
# process.
if ($configuring_base) 
{
    input("high","liloconfig/configuring_base");
    go();

exit(0);
}

if ($liloconf_exists) 
{
    # Trust and use the existing lilo.conf.
    # FIX: If the current lilo.conf installs a master boot record, ask
    #	to edit it to a partition boot record and install the master boot
    #	record to chain to that.

    input("high","liloconfig/liloconf_exists");
    go();

    if ($liloconf_incompatible) 
    {
	input("high","liloconfig/liloconf_incompatible");
	go();
	exit(1);
    }
    
    set("liloconfig/use_current_lilo","true");
    input("high","liloconfig/use_current_lilo");
    go();
    ($ret,$val)=get("liloconfig/use_current_lilo");
    if ($val eq "true")
    {
	input("high","liloconfig/lilo_warning");
	go();
	stop();
	&safe_system("/sbin/lilo -v");
	if ( $? == 0 ) 
	{
	    exit(0);
	}
	input("high","liloconfig/lilo_error");
	go();
	exit(1);
    } 
    else 
    {
        set("liloconfig/wipe_old_liloconf","false");
	input("high","liloconfig/wipe_old_liloconf");
	go();
	($ret,$val)=get("liloconfig/wipe_old_liloconf");
	if($val eq "true")
	{
	    rename($LILOCONF, "$LILOCONF.OLD") or die "liloconfig: couldn't save old $LILOCONF as $LILOCONF.OLD: $!\n";
	}
	else 
	{
	    input("high","liloconfig/no_changes");
	    go();
	    exit(0);
	}
    }
}

# ASSERT: that we get here only if there is no lilo.conf or the user
# asked us to wipe out the old one...
# We make checks for broken fstabs and odd devices only if we are 
# going to try to write a lilo.conf for the user.
if ($fstab_broken) 
{
    input("high","liloconfig/fstab_broken");
    go();
    exit(1);
}

if ($odd_fstab) 
{
    subst("liloconfig/odd_fstab","device",$device);
    input("high","liloconfig/odd_fstab");
    go();
    exit(1);
}

input("high","liloconfig/instruction");
go();

# Flag so we can print STDERR a warning if we fell out the bottom of the config
# without having run lilo at all.
$lilorun = 0; $madeactive = 0;


subst("liloconfig/install_from_root_device","device",$device);
set("liloconfig/install_from_root_device","false");
input("high","liloconfig/install_from_root_device");
go();
($ret,$val)=get("liloconfig/install_from_root_device");

if($val eq "true")
{
    umask(077);
    open(CONF, ">$LILOCONF") or die "Couldn't open $LILOCONF for writing: $!\n";
    if (!chown(0, 0, "$LILOCONF")) 
    {
	die "Couldn't make $LILOCONF owned by root.root: $!\n" unless $DEBUG;

	# Following message is only shown if $DEBUG is set. So, I do not care
	# about templating it. Let it goes into STDERR. YMMV. <arturcz@hell.pl>
	print STDERR "Oops, couldn't make $LILOCONF owned by root.root. Since you\n";
	print STDERR "have set the DEBUG flag, I'm going to assume this is because\n";
	print STDERR "you're running liloconfig as a normal user, and continue anyway.\n";
    }
    print CONF @header;
    input("high","liloconfig/install_from_root_device");
    go();
    ($ret,$val)=get("liloconfig/install_from_root_device");
    if($val eq "true")
    {
	print CONF "# This allows booting from any partition on disks with more than 1024\n"; 
	print CONF "# cylinders.\n";
	print CONF "lba32\n";
	print CONF "\n";
    }
    @middle_boilerplate = &get_images;
    @bmp_boilerplate = &get_bitmap;

    print CONF @bootheader, "boot=".$device, "\n", @rootheader, "root=".$device, "\n", @bmp_boilerplate, @boilerplate1, @middle_boilerplate, @boilerplate2;
    
    close(CONF) or die "Couldn't close $LILOCONF: $!\n";
    
    &safe_system("/sbin/lilo");
    if ($? != 0) 
    {
	input("high","liloconfig/lilo_error");
	go();
	exit(1);
    }
    $lilorun = 1;
}


subst("liloconfig/install_mbr","disk",$disk);
set("liloconfig/install_mbr","false");
input("high","liloconfig/install_mbr");
go();
($ret,$val)=get("liloconfig/install_mbr");
if($val eq "true")
{
    &safe_system("install-mbr $disk");
    if ($? != 0) 
    {
	input("high","liloconfig/mbr_error");
	go();
	exit(1);
    }
}

subst("liloconfig/make_active_partition","device",$device);
input("high","liloconfig/make_active_partition");
go();
($ret,$val)=get("liloconfig/install_mbr");
if($val eq "true")
{
    my $part_num = $partition;
    $part_num =~ s/^.*[a-z]//;
    print STDERR "Activating Partition $part_num on disk $disk.\n";
    &safe_system("/sbin/activate $disk $part_num");
    if ($? != 0) 
    {
	input("high","liloconfig/activate_error");
	go();
	exit(1);
    }
    $madeactive = 1;
}

# Trailer: summarise what we've done
print STDERR "\n";
if (! $lilorun) 
{
    print STDERR "WARNING: you will have to set up LILO manually to ensure that\n";
    print STDERR "	 your system can be booted successfully!\n";
    print STDERR "You can rerun liloconfig at any time if you change your mind and\n";
    print STDERR "wish to use the default configuration.\n";
} 
elsif ($madeactive) 
{
    # LILO was run and Linux partition made active
    print STDERR "LILO successfully configured; Linux will be booted by default.\n";
    print STDERR "If you installed the master boot record, you can boot a different\n";
    print STDERR "OS by holding down the shift key as the system boots, and then\n";
    print STDERR "pressing the key corresponding to the partition containing that\n";
    print STDERR "OS when you see the \"1234F:\" prompt.\n";
}
else
{
    my $part_num = $partition;
    $part_num =~ s/^.*[a-z]//;
    # LILO run, but Linux partition not made active
    print STDERR "OK. If you installed the master boot record, and the partition\n";
    print STDERR "boot record, you may boot Linux by holding down the shift key\n";
    print STDERR "as the system boots, and then pressing the $part_num key\n";
    print STDERR "when you see the \"1234F:\" prompt.\n";
}

print STDERR "\n";
print STDERR "For more information about LILO, see the documentation in\n";
print STDERR "/usr/share/doc/lilo/. For details about the MBR, see also\n";
print STDERR "/usr/share/doc/mbr/.\n";

exit(0);
