package bootloader; # $Id$ use diagnostics; use strict; use vars qw(%vga_modes); #-###################################################################################### #- misc imports #-###################################################################################### use common; use partition_table qw(:types); use log; use any; use fsedit; use devices; use loopback; use detect_devices; use partition_table_raw; use run_program; use modules; %vga_modes = ( 'ask' => "Ask at boot", 'normal' => "Normal", '0x0f01' => "80x50", '0x0f02' => "80x43", '0x0f03' => "80x28", '0x0f05' => "80x30", '0x0f06' => "80x34", '0x0f07' => "80x60", '0x0122' => "100x30", 785 => "640x480 in 16 bits (FrameBuffer only)", 788 => "800x600 in 16 bits (FrameBuffer only)", 791 => "1024x768 in 16 bits (FrameBuffer only)", 794 => "1280x1024 in 16 bits (FrameBuffer only)", ); #-##################################################################################### #- Functions #-##################################################################################### sub get { my ($kernel, $bootloader) = @_; $_->{kernel_or_dev} && $_->{kernel_or_dev} eq $kernel and return $_ foreach @{$bootloader->{entries}}; undef; } sub get_label { my ($label, $bootloader) = @_; $_->{label} && $_->{label} eq $label and return $_ foreach @{$bootloader->{entries}}; undef; } sub mkinitrd($$$) { my ($prefix, $kernelVersion, $initrdImage) = @_; $::testing || -e "$prefix/$initrdImage" and return; my $loop_boot = loopback::prepare_boot($prefix); modules::load('loop'); run_program::rooted($prefix, "mkinitrd", "-v", "-f", $initrdImage, "--ifneeded", $kernelVersion) or unlink("$prefix/$initrdImage"); loopback::save_boot($loop_boot); -e "$prefix/$initrdImage" or die "mkinitrd failed"; } sub mkbootdisk($$$;$) { my ($prefix, $kernelVersion, $dev, $append) = @_; modules::load_multi(arch() =~ /sparc/ ? 'romfs' : (), 'loop'); my @l = qw(mkbootdisk --noprompt); push @l, "--appendargs", $append if $append; eval { modules::load('vfat') }; run_program::rooted_or_die($prefix, @l, "--device", "/dev/$dev", $kernelVersion); } 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)$/) { push @{$b{entries}}, $e = { type => $_, kernel_or_dev => $v }; $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}; $_->{append} =~ s/^\s*"?(.*?)"?\s*$/$1/ foreach \%b, @{$b{entries}}; $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 grub empty)), !$type; } sub compare_entries ($$) { my ($a, $b) = @_; my %entries; @entries{keys %$a, keys %$b} = (); $a->{$_} eq $b->{$_} and delete $entries{$_} foreach keys %entries; scalar keys %entries; } sub add_entry($$) { my ($entries, $v) = @_; my (%usedold, $freeold); do { $usedold{$1 || 0} = 1 if $_->{label} =~ /^old([^_]*)_/ } foreach @$entries; foreach (0..scalar keys %usedold) { exists $usedold{$_} or $freeold = $_ || '', last } foreach (@$entries) { if ($_->{label} eq $v->{label}) { compare_entries($_, $v) or return; #- avoid inserting it twice as another entry already exists ! $_->{label} = "old${freeold}_$_->{label}"; } } push @$entries, $v; } sub add_kernel { my ($prefix, $lilo, $version, $ext, $root, $v) = @_; #- new versions of yaboot don't handle symlinks my $ppcext = $ext; if (arch() =~ /ppc/) { $ext = "-$version"; } log::l("adding vmlinuz$ext as vmlinuz-$version"); -e "$prefix/boot/vmlinuz-$version" or log::l("unable to find kernel image $prefix/boot/vmlinuz-$version"), return; my $image = "/boot/vmlinuz" . ($ext ne "-$version" && symlinkf("vmlinuz-$version", "$prefix/boot/vmlinuz$ext") ? $ext : "-$version"); my $initrd = eval { mkinitrd($prefix, $version, "/boot/initrd-$version.img"); "/boot/initrd" . ($ext ne "-$version" && symlinkf("initrd-$version.img", "$prefix/boot/initrd$ext.img") ? $ext : "-$version") . ".img"; }; my $label = $ext =~ /-(default)/ ? $1 : "linux$ext"; #- more yaboot concessions - PPC if (arch() =~ /ppc/) { $label = $ppcext =~ /-(default)/ ? $1 : "linux$ppcext"; } add2hash($v, { type => 'image', root => "/dev/$root", label => $label, kernel_or_dev => $image, initrd => $initrd, append => $lilo->{perImageAppend}, }); add_entry($lilo->{entries}, $v); $v; } sub unpack_append { my ($s) = @_; my @l = split(' ', $s); [ grep { !/=/ } @l ], [ map { if_(/(.*?)=(.*)/, [$1, $2]) } @l ]; } sub pack_append { my ($simple, $dict) = @_; join(' ', @$simple, map { "$_->[0]=$_->[1]" } @$dict); } sub append__mem_is_memsize { $_[0] =~ /^\d+[kM]?$/i } sub get_append { my ($b, $key) = @_; my (undef, $dict) = unpack_append($b->{perImageAppend}); my @l = map { $_->[1] } grep { $_->[0] eq $key } @$dict; #- suppose we want the memsize @l = grep { append__mem_is_memsize($_) } @l if $key eq 'mem'; log::l("more than one $key in $b->{perImageAppend}") if @l > 1; $l[0]; } sub add_append { my ($b, $key, $val) = @_; foreach (\$b->{perImageAppend}, map { \$_->{append} } @{$b->{entries}}) { my ($simple, $dict) = unpack_append($$_); @$dict = grep { $_->[0] ne $key || $key eq 'mem' && append__mem_is_memsize($_->[1]) != append__mem_is_memsize($val) } @$dict; push @$dict, [ $key, $val ] if $val; $$_ = pack_append($simple, $dict); log::l("add_append: $$_"); } } sub may_append { my ($b, $key, $val) = @_; add_append($b, $key, $val) if !get_append($b, $key); } sub configure_entry($$) { my ($prefix, $entry) = @_; if ($entry->{type} eq 'image') { my $specific_version; $entry->{kernel_or_dev} =~ /vmlinu.-(.*)/ and $specific_version = $1; readlink("$prefix/$entry->{kernel_or_dev}") =~ /vmlinu.-(.*)/ and $specific_version = $1; if ($specific_version) { $entry->{initrd} or $entry->{initrd} = "/boot/initrd-$specific_version.img"; unless (-e "$prefix/$entry->{initrd}") { eval { mkinitrd($prefix, $specific_version, "$entry->{initrd}") }; undef $entry->{initrd} if $@; } } } $entry; } sub dev2prompath { #- SPARC only my ($dev) = @_; my ($wd, $num) = $dev =~ /^(.*\D)(\d*)$/; require c; $dev = c::disk2PromPath($wd) and $dev = $dev =~ /^sd\(/ ? "$dev$num" : "$dev;$num"; $dev; } sub get_kernels_and_labels { my ($prefix) = @_; my $dir = "$prefix/boot"; my @l = grep { /^vmlinuz-/ } all($dir); my @kernels = grep { ! -l "$dir/$_" } @l; my @preferred = ('', 'secure', 'enterprise', 'smp'); my %weights = map_index { $_ => $::i } @preferred; require pkgs; @kernels = sort { pkgs::versionCompare($b->[1], $a->[1]) || $weights{$a->[2]} <=> $weights{$b->[2]} } map { if (my ($version, $ext) = /vmlinuz-((?:[\-.\d]*(?:mdk)?)*)(.*)/) { [ "$version$ext", $version, $ext ]; } else { log::l("non recognised kernel name $_"); (); } } @kernels; my %majors; foreach (@kernels) { push @{$majors{$1}}, $_ if $_->[1] =~ /^(2\.\d+)/ } while (my ($major, $l) = each %majors) { $l->[0][1] = $major if @$l == 1; } my %labels; foreach (@kernels) { my ($complete_version, $version, $ext) = @$_; my $label = ''; if (exists $labels{$label}) { $label = "-$ext"; if (!$ext || $labels{$label}) { $label = "-$version$ext"; } } $labels{$label} = $complete_version; } %labels; } sub suggest { my ($prefix, $lilo, $hds, $fstab, $vga_fb) = @_; my $root_part = fsedit::get_root($fstab); my $root = isLoopback($root_part) ? "loop7" : $root_part->{device}; my $boot = fsedit::get_root($fstab, 'boot')->{device}; my $partition = first($boot =~ /\D*(\d*)/); #- PPC xfs module requires enlarged initrd my $xfsroot = isThisFs("xfs", $root_part); require c; c::initSilo() if arch() =~ /sparc/; my ($onmbr, $unsafe) = $lilo->{crushMbr} ? (1, 0) : suggest_onmbr($hds); add2hash_($lilo, arch() =~ /sparc/ ? { entries => [], timeout => 10, use_partition => 0, #- we should almost always have a whole disk partition. root => "/dev/$root", partition => $partition || 1, boot => $root eq $boot && "/boot", #- this helps for getting default partition for silo. } : arch =~ /ppc/ ? { defaultos => "linux", entries => [], initmsg => "Welcome to Mandrake Linux!", delay => 30, #- OpenFirmware delay timeout => 50, enableofboot => 1, enablecdboot => 1, useboot => $boot, xfsroot => $xfsroot, } : { bootUnsafe => $unsafe, entries => [], timeout => $onmbr && 10, if_(arch() !~ /ia64/, lba32 => 1, boot => "/dev/" . ($onmbr ? $hds->[0]{device} : fsedit::get_root($fstab, 'boot')->{device}), map => "/boot/map", install => "/boot/boot.b", ), }); if (!$lilo->{message} || $lilo->{message} eq "1") { $lilo->{message} = join('', cat_("$prefix/boot/message")); if (!$lilo->{message}) { my $msg_en = #-PO: these messages will be displayed at boot time in the BIOS, use only ASCII (7bit) __("Welcome to %s the operating system chooser! Choose an operating system in the list above or wait %d seconds for default boot. "); my $msg = translate($msg_en); #- use the english version if more than 20% of 8bits chars $msg = $msg_en if int(grep { $_ & 0x80 } unpack "c*", $msg) / length($msg) > 0.2; $lilo->{message} = sprintf $msg, arch() =~ /sparc/ ? "SILO" : "LILO", $lilo->{timeout}; } } add2hash_($lilo, { memsize => $1 }) if cat_("/proc/cmdline") =~ /\bmem=(\d+[KkMm]?)(?:\s.*)?$/; if (my ($s, $port, $speed) = cat_("/proc/cmdline") =~ /console=(ttyS(\d),(\d+)\S*)/) { log::l("serial console $s $port $speed"); add_append($lilo, 'console' => $s); any::set_login_serial_console($prefix, $port, $speed); } my %labels = get_kernels_and_labels($prefix); $labels{''} or die "no kernel installed"; while (my ($ext, $version) = each %labels) { my $entry = add_kernel($prefix, $lilo, $version, $ext, $root, { if_($vga_fb && $ext eq '', vga => $vga_fb), #- using framebuffer }); $entry->{append} .= " quiet" if $vga_fb && $version !~ /smp|enterprise/; if ($vga_fb && $ext eq '') { add_kernel($prefix, $lilo, $version, $ext, $root, { label => 'linux-nonfb' }); } } my $failsafe = add_kernel($prefix, $lilo, $labels{''}, '', $root, { label => 'failsafe' }); $failsafe->{append} .= " failsafe"; if (arch() =~ /sparc/) { #- search for SunOS, it could be a really better approach to take into account #- partition type for mounting point. my $sunos = 0; foreach (@$hds) { foreach (@{$_->{primary}{normal}}) { my $path = $_->{device} =~ m|^/| && $_->{device} !~ m|^/dev/| ? $_->{device} : dev2prompath($_->{device}); add_entry($lilo->{entries}, { type => 'other', kernel_or_dev => $path, label => "sunos" . ($sunos++ ? $sunos : ''), }) if $path && isSunOS($_) && type2name($_->{type}) =~ /root/i; } } } elsif (arch() =~ /ppc/) { #- if we id