package lilo;








use common qw(:common :file);
use partition_table qw(:types);
use log;
use fsedit;
use detect_devices;
use partition_table_raw;
use run_program;
use modules;


%vga_modes = (
 "Ask at boot" => 'ask',
 "Normal" => 'normal',
 "80x50" => '0x0f01',
 "80x43" => '0x0f02',
 "80x28" => '0x0f03',
 "80x30" => '0x0f04',
 "80x34" => '0x0f05',
 "80x60" => '0x0f06',
);






sub mkinitrd($$$) {
    my ($prefix, $kernelVersion, $initrdImage) = @_;

    $::testing and return;

    modules::load('loop');
    run_program::rooted($prefix, "mkinitrd", "-f", $initrdImage, "--ifneeded", $kernelVersion) or unlink("$prefix/$initrdImage");
    -e "$prefix/$initrdImage" or die "mkinitrd failed";
}

sub mkbootdisk($$$;$) {
    my ($prefix, $kernelVersion, $dev, $append) = @_;

    modules::load('loop');
    my @l = qw(mkbootdisk --noprompt); push @l, "--appendargs", $append if $append;
    run_program::rooted($prefix, @l, "--device", "/dev/$dev", $kernelVersion) or die "mkbootdisk failed";
}

sub read($$) {
    my ($prefix, $file) = @_;
    my $global = 1;
    my ($e, $v, $f);
    my %b;
    foreach (cat_("$prefix$file")) {
	($_, $v) = /^\s*(.*?)\s*(?:=\s*(.*?))?\s*$/;

	if (/^(image|other)$/) {
	    $b{entries}{$v} = $e = { type => $_ };
	    $global = 0;
	} elsif ($global) {
	    $b{$_} = $v || 1;
	} else {
	    if ((/map-drive/ .. /to/) && /to/) {
		$e->{mapdrive}{$e->{'map-drive'}} = $v;
	    } else {
		$e->{$_} = $v || 1;
	    }
	}
    }
    delete $b{timeout} unless $b{prompt};
    $b{timeout} = $b{timeout} / 10 if $b{timeout};
    $b{message} = cat_("$prefix$b{message}") if $b{message};
    \%b;
}

sub suggest_onmbr($) {
    my ($hds) = @_;
    
    my $type = partition_table_raw::typeOfMBR($hds->[0]{device});
    !$type || member($type, qw(dos dummy lilo empty)), !$type;
}

sub add_entry($$$) {
    my ($entries, $image, $v) = @_;
    my (%usedold, $freeold);

    do { $usedold{$1 || 0} = 1 if $_->{label} =~ /^old ([^_]*)_/x } foreach (values %$entries);
    foreach (0..scalar keys %usedold) { exists $usedold{$_} or $freeold = $_ || '', last }

    do { $_->{label} = "old${freeold}_$_->{label}" if $_->{label} eq $v->{label} } foreach (values %$entries);
    $entries->{$image} = $v;
}

sub add_kernel($$$$$) {
    my ($prefix, $lilo, $kernelVersion, $specific, $v) = @_;
    my $ext = $specific && "-$specific";
    my ($vmlinuz, $image, $initrdImage) = ("vmlinuz-$kernelVersion$specific", "/boot/vmlinuz$ext", "/boot/initrd$ext.img");
    if (-e "$prefix/boot/$vmlinuz") {
	{
	    my $f = "initrd-$kernelVersion$specific.img";
	    eval { mkinitrd($prefix, "$kernelVersion$specific", "/boot/$f") };
	    undef $initrdImage if $@;
	    symlinkf $f, "$prefix$initrdImage" if $initrdImage;
	}
	add2hash($v,
		 {
		  type => 'image',
		  label => 'linux',
		  initrd => $initrdImage,
		  append => $lilo->{perImageAppend},
		 });
	symlinkf "$vmlinuz", "$prefix/$image" if $specific;
	add_entry($lilo->{entries}, $image, $v);
	1;
    } else {
	log::l("unable to find kernel image $prefix/boot/$vmlinuz");
	0;
    }
}

sub suggest($$$$$) {
    my ($prefix, $lilo, $hds, $fstab, $kernelVersion) = @_;
    my $root = fsedit::get_root($fstab)->{device};
    my ($onmbr, $unsafe) = $lilo->{crushMbr} ? (1, 0) : suggest_onmbr($hds);
    add2hash_($lilo, 
	{
	 boot => "/dev/" . ($onmbr ? $hds->[0]{device} : fsedit::get_root($fstab, 'boot')->{device}),
	 bootUnsafe => $unsafe,
	 map => "/boot/map",
	 default => "linux",
	 timeout => $onmbr && 5,
	 install => "/boot/boot.b",
	});
    $lilo->{disk} ||= "/dev/$hds->[0]{device} bios=0x80" if $hds->[0]{device} =~ /^hd[be]$/;

    if (!$lilo->{message} || $lilo->{message} eq "1") {
	$lilo->{message} = join('', cat_("$prefix/boot/message"));
	if (!$lilo->{message}) {
	    my $msg_en =
__("Welcome to LILO the operating system chooser!

To list the possible choices, press <TAB>.

To load one of them, write its name and press <ENTER> or wait %d seconds for default boot.

");
	    my $msg = translate($msg_en);
	    
	    $msg = $msg_en if int(grep { $_ & 0x80 } unpack "c*", $msg) / length($msg) > 0.2;
	    $lilo->{message} = sprintf $msg, $lilo->{timeout};
	}
    }

    my $isSecure = -e "$prefix/boot/vmlinuz-${kernelVersion}secure";

    my $isSMP = detect_devices::hasSMP();
    if ($isSMP && !-e "$prefix/boot/vmlinuz-${kernelVersion}smp") {
	log::l("SMP machine, but no SMP kernel found") unless $isSecure;
	$isSMP = 0;
    }
    add_kernel($prefix, $lilo, $kernelVersion, $isSecure ? 'secure' : 'smp',
	       {
		label => 'linux',
		root  => "/dev/$root",
	       }) if $isSecure || $isSMP;
    add_kernel($prefix, $lilo, $kernelVersion, '',
	       {
		label => $isSecure || $isSMP ? 'linux-up' : 'linux',
		root  => "/dev/$root",
	       });

    
    my ($dos, $win) = 0, 0;
    foreach (@$hds) {
	foreach (@{$_->{primary}{normal}}) {
	    add_entry($lilo->{entries}, "/dev/$_->{device}",
	      {
	       type => 'other',
	       label => isDos($_) ? "dos"     . ($dos++ ? $dos : '') :
	                            "windows" . ($win++ ? $win : '') ,
	       table => "/dev/$_->{rootDevice}",
	      }) if isFat($_) && isFat({ type => fsedit::typeOfPart($_->{device}) });
	}
    }

    add_entry($lilo->{entries}, '/dev/fd0',
      {
       type => 'other',
       label => 'floppy',
       unsafe => 1
      });
}

sub keytable($$) {
    my ($prefix, $f) = @_;
    local $_ = $f;
    if ($_ && !/\.klt$/) {
	$f = "/boot/$_.klt";
	run_program::rooted($prefix, "keytab-lilo.pl", ">", $f, $_) or undef $f;
    }
    $f && -r "$prefix/$f" && $f;
}

sub install($$) {
    my ($prefix, $lilo) = @_;
    $lilo->{prompt} = $lilo->{timeout};

    $lilo->{keytable} = keytable($prefix, $lilo->{keytable});

    if ($lilo->{message}) {
	local *F;
	open F, ">$prefix/boot/message" and print F $lilo->{message} or $lilo->{message} = 0;
    }
    {
	local *F;
        local $\ = "\n";
	my $f = "$prefix/etc/lilo.conf";
	open F, ">$f" or die "cannot create lilo config file: $f";
	log::l("writing lilo config to $f");

	$lilo->{$_} and print F "$_=$lilo->{$_}" foreach qw(boot map install vga default append keytable disk);
	$lilo->{$_} and print F $_ foreach qw(linear compact prompt restricted);
	
	print F "timeout=", round(10 * $lilo->{timeout}) if $lilo->{timeout};
	print F "message=/boot/message" if $lilo->{message};

	while (my ($v, $e) = each %{$lilo->{entries}}) {
	    print F "$e->{type}=$v";
	    print F "\tlabel=$e->{label}";

	    if ($e->{type} eq "image") {
		print F "\troot=$e->{root}";
		print F "\tinitrd=$e->{initrd}" if $e->{initrd};
		print F "\tappend=\"$1\"" if $e->{append} =~ /^\s*"?(.*?)"?\s*$/;
		print F "\tvga=$e->{vga}" if $e->{vga};
		print F "\tread-write" if $e->{'read-write'};
		print F "\tread-only" if !$e->{'read-write'};
	    } else {
		print F "\ttable=$e->{table}" if $e->{table};
		print F "\tunsafe" if $e->{unsafe} && !$e->{table};
		
		
		$e->{mapdrive} ||= { '0x80' => '0x81', '0x81' => '0x80' } 
		  if $e->{table} && $lilo->{boot} !~ /$e->{table}/;

		while (my ($from, $to) = each %{$e->{mapdrive} || {}}) {
		    print F "\tmap-drive=$from";
		    print F "\t   to=$to";
		}
	    }
	}
    }
    log::l("Installing boot loader...");
    $::testing and return;
    run_program::rooted($prefix, "lilo", "2>", "/tmp/.error") or die "lilo failed";
    unlink "$prefix/tmp/.error";
}




1; #
