#!/usr/bin/perl
# -*- Mode: cperl -*-
#--------------------------------------------------------------------
# Copyright (C) 2000, 2001, 2002 by MandrakeSoft.
# Chmouel Boudjnah <chmouel@mandrakesoft.com>.
#
# Redistribution of this file is permitted under the terms of the GNU
# Public License (GPL)
#--------------------------------------------------------------------
# $Id: grub,v 1.28 2004/02/27 07:51:33 tvignaud Exp $
#--------------------------------------------------------------------
## description:
#	      Add/check entry for grub bootloader.

my $debug = 0;

use strict;
use lib qw(/usr/share/loader);
use common;

my ($version, $title, $remove, $blank);
my $boot="/boot/";

{
    local $_;
    while ($ARGV[0] =~ /^-/) {
        $_ = shift;
        if (m/^-r/) {
            $remove++;
        } else {
            print STDERR "Unknown option $_";
        }
    }
}

my $map_file = "/boot/grub/device.map";

my $options = common::getoptions();
# Try absolutely to get the root=
$options .= common::getroot();

unless ($version = shift) {
    print STDERR "No kernel version has been given using the current\n";
    $version = `uname -r`;chomp $version;
}
my $glabel = common::sanitize_ver(${version});

my $cnt = 0;
my $grub_partition;

# we keep entry as hash for future use.
my (%main, %entry);
my $grub_conf = $ENV{GRUB_CONF} || "/boot/grub/menu.lst";

`cp -f $grub_conf ${grub_conf}.old` if -f $grub_conf && !$debug;

{
    my $F;
    open $F, $grub_conf or die "Can't open $grub_conf\n";

    local $_;
    while (<$F>) {
        $blank = "\n" if eof && !/^\s*$/;

        next if /^\s*#/;
        $main{default} = $1 if /^default (\d+)/;

        if (/^title\s+(.*)/) {
            $title=$1;
            $entry{$title}{cnt} = $cnt;
            $main{default} = $title if $entry{$title}{cnt} == $main{default};
            $cnt++;
        }

        if (m/kernel\s+(\(.*\))([^ 	]+)\s+(.*)/) {
            $entry{$title}{label}=$title;
            $entry{$title}{partition} = $1;
            $entry{$title}{kernel} = $2;
            $entry{$title}{options} = $3;
        }
        $entry{$title}{initrd} = $1 if m/\s*initrd.*\)(.*)/;
    }
    close $F;
}

common::check_default_entry(\%entry, \%main);
common::do_check($entry{$glabel}{label}, $version, $remove) unless $debug;

unless ($grub_partition = convert_grub_part(get_boot_partitions())) {
    die "Can't convert grub_partition\n";
}

if ($remove) {
    my $tmp = $debug ? "true" : "mktemp";
    my $output = `$tmp /tmp/.grub.XXXXXX`;chomp($output);
    local $/; local *F; local *O;

    if (-l $entry{$main{default}}{kernel}) {
	my $resolved_link = readlink($entry{$main{default}}{kernel});
	my $type = common::get_kernel_type($entry{$main{default}}{kernel});

	# This should be broken :p
	if ($resolved_link =~ m|vmlinuz-$version|) {

	    my $first_kernel = common::get_first_boot($type, $resolved_link);

	    system("ln -fs vmlinuz-$first_kernel /boot/vmlinuz$type");
	    system("ln -fs initrd-$first_kernel.img /boot/initrd$type.img") if -f "/boot/initrd$type.img";

	}
    }
    open F, $grub_conf;
    open O, ">$output";
    select O unless $debug;
    local $_;
    while (<F>) {
	if (m!title $glabel\nkernel.*?vmlinuz-.*?(?=(title|$))!s) {
 	    if (m!title $glabel\nkernel.*?vmlinuz-.*?title!s) {
             s!title $glabel\nkernel.*?vmlinuz-.*?(?=title)!!s;
 	    } else {
             s!title $glabel\nkernel.*?vmlinuz-.*?$!!s;
 	    }
 	}
	print;

    }
    close F;
    select STDOUT;
    system("mv -f $output $grub_conf") unless $debug;
} else {
    local *F;
    unless ($debug) {
	open F, ">>$grub_conf" or die "Can't write to $grub_conf\n";
	select F;
    }
    print "\n" if $blank;

    my $lock;
    my $version_default = -l $entry{$main{default}}{kernel} ?
      basename(readlink($entry{$main{default}}{kernel})) :
	basename($entry{$main{default}}{kernel});
    $version_default =~ s|vmlinuz-||;
    my $label_default = common::sanitize_ver($version_default);
    while (my $act = each %entry) {
	$lock++ if $entry{$act}{label} =~ $label_default;
    }

    if (!$lock && -f "/boot/vmlinuz-$version_default" && $version !~ $version_default) {
	print <<EOF;
title $label_default
kernel $grub_partition${boot}vmlinuz-$version_default $options
EOF

	print "initrd=/boot/initrd-$version_default.img\n" if -f "/boot/initrd-$version_default.img";
	print "\n";
    }

    print <<EOF;
title $glabel
kernel $grub_partition${boot}vmlinuz-$version $options
EOF
if (-f "/boot/initrd-$version.img") {
    print "initrd $grub_partition${boot}initrd-$version.img\n";
}
close F;
select STDOUT;
}

sub convert_grub_part {
    my ($fpart) = @_;

    my ($disk, $part) =
      $fpart =~ m|(.*)/part(\d+)$| ? ("$1/disc", $2 - 1) :
      $fpart =~ m|([^\d+]*)(\d+)|  ? ($1, $2 - 1) :
	die "convert_grub_part: unknown device $fpart";

    my $is_disk = sub {
	my ($f) = @_;
	$f = "/dev/$f" if $f !~ m|^/|;
	$f eq $disk;
    };

    local *F;
    open F, $map_file or die "Can't open $map_file\n";
    foreach (<F>) {
	my ($grubdev, $dev) = m|\((\S+)\)\s+(.*)| or warn "weird line in $map_file: $_", next;
	if ($is_disk->($dev) || $is_disk->(readlink $dev)) {
	    $grubdev =~ /([\w\d]+)d\d+/ or die "bad grub device $grubdev in $_";
	    return "($grubdev,$part)";
	}
    }
}

sub get_boot_partitions() {
    my $part;
    local *F;
    open F, '/etc/fstab';
    local $_;
    while (<F>) {
 	s/#.*//;
 	my @s = split ' ';
	$part = $s[0] if $s[1] =~ m|/$| && !$part;
	if ($s[1] =~ m|/boot$|) {
	    $boot="/";
	    $part = $s[0];
	}
    };
    close F;
    return  $part
}
sub basename { local $_ = shift; s|/*\s*$||; s|.*/||; $_ }
