diff options
Diffstat (limited to 'perl-install/bootloader.pm')
| -rw-r--r-- | perl-install/bootloader.pm | 3228 | 
1 files changed, 2427 insertions, 801 deletions
| diff --git a/perl-install/bootloader.pm b/perl-install/bootloader.pm index 0cdbfc1bd..d1d757c3d 100644 --- a/perl-install/bootloader.pm +++ b/perl-install/bootloader.pm @@ -1,48 +1,172 @@ -package bootloader; # $Id$ +package bootloader;  use diagnostics;  use strict; -use vars qw(%vga_modes);  #-######################################################################################  #- misc imports  #-######################################################################################  use common; -use partition_table qw(:types); +use fs::type; +use fs::get; +use fs::loopback; +use fs::proc_partitions;  use log;  use any; -use fsedit;  use devices; -use loopback;  use detect_devices;  use partition_table::raw;  use run_program;  use modules; +=head1 SYNOPSYS -our %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 -#-##################################################################################### +B<bootloader> enables to configure various boot loaders (LILO, GRUB Legacy, GRUB2, ...) + +Example of usage: + +    $all_hds = fsedit::get_hds(); +    fs::get_raw_hds('', $all_hds); +    fs::get_info_from_fstab($all_hds); +    $fstab = [ fs::get::fstab($all_hds) ]; +    $bootloader = bootloader::read($all_hds); +    (...) +    bootloader::action($bootloader, 'write', $all_hds); + +A particular bootloader is handled in separate typical functions that can be called by action() by /sbin/bootloader-config: + + +=head1 VFS aka how to support a bootloader + +Most of the code is generic. + +A particular bootloader implementation/support is done through a couple functions: + +=over 4 + +=item * read_FOOBAR() + +read the config file(s) of the FOOBAR bootloader, returning a $bootloader object + +=item * install_FOOBAR($bootloader, $all_hds) + +usually call write_FOOBAR() & install_raw_FOOBAR() if existing, aka: + +=over 4 + +=item * updating config files + +=item * then reinstalling the bootloader + +=back + +=item * install_raw_FOOBAR() + +Optionnal: Low level installation of the stage1 of the bootloader, usually embedding it in MBR or on ESP, ... (eg: running lilo or /boot/grub*/install.sh) + +=item * write_FOOBAR($bootloader, $all_hds) + +update the config files, ie when a kernel was installed or removed. +when_config_changed_FOOBAR() is called right after in case something is needed when config is done (eg: lilo needs to be reinstalled) + +=item * when_config_changed_FOOBAR() + +Run needed action when config has changed or when initrds were regenerated, ... +Usually nothing for intelligent bootloaders such as grub-legacy or grub2. +But eg: lilo needs to rewrite its stage1 (thus it needs to rerun lilo). + +=back + +=head1 Functions + +=over + +=cut + +my $vmlinuz_regexp = 'vmlinu[xz]|win4lin|uImage'; +my $decompose_vmlinuz_name = qr/((?:$vmlinuz_regexp).*?)-(\d+\.\d+.*)/; +my $liblinux_path = '/usr/lib/linux'; + +sub expand_vmlinuz_symlink { +    my ($vmlinuz) = @_; +    my $f = $::prefix . ($vmlinuz =~ m!^/! ? $vmlinuz : "/boot/$vmlinuz"); +    -l $f ? readlink($f) : $vmlinuz; +} + +sub installed_vmlinuz_raw() { grep { /^($vmlinuz_regexp)/ } all("$::prefix/boot") } +sub installed_vmlinuz() { grep { ! -l "$::prefix/boot/$_" } installed_vmlinuz_raw() } +sub vmlinuz2version { +    my ($vmlinuz) = @_; +    expand_vmlinuz_symlink($vmlinuz) =~ /$decompose_vmlinuz_name/ && $2; +} +sub vmlinuz2kernel_str { +    my ($vmlinuz) = @_; +    my ($basename, $version) = expand_vmlinuz_symlink($vmlinuz) =~ /$decompose_vmlinuz_name/ or return; +    {  +	basename => $basename, +	version => $version,  +	$version =~ /([\d.]*)-(\D.*)-((\d+|0\.rc\d+.*)\.mga.*)$/ ? #- eg: 3.0.0-1.mga2 +	  (ext => $2, version_no_ext => "$1-$3") : +	$version =~ /(.*mga)-?(.*)/ ? #- (old) eg: 2.6.17-13mdventerprise +	  (ext => $2, version_no_ext => $1) : (version_no_ext => $version), +    }; +} + +sub kernel_str2short_name { +    my ($kernel) = @_; +    $kernel->{basename}; +} + +sub basename2initrd_basename { +    my ($basename) = @_; +    $basename =~ s!(vmlinu[zx]|uImage)-?!!; #- here we do not use $vmlinuz_regexp since we explicitly want to keep all that is not "vmlinuz" +    'initrd' . ($basename ? "-$basename" : '');     +} +sub kernel_str2vmlinuz_long { +    my ($kernel) = @_; +    $kernel->{basename} . '-' . $kernel->{version}; +} +sub kernel_str2initrd_long { +    my ($kernel) = @_; +    basename2initrd_basename(kernel_str2short_name($kernel)) . '-' . $kernel->{version} . '.img'; +} +sub kernel_str2liblinux_long { +    my ($kernel) = @_; +    $liblinux_path . '-' . $kernel->{version}; +} +sub kernel_str2vmlinuz_short { +    my ($kernel) = @_; +    if ($kernel->{use_long_name}) { +	kernel_str2vmlinuz_long($kernel); +    } else { +	kernel_str2short_name($kernel); +    } +} +sub kernel_str2initrd_short { +    my ($kernel) = @_; +    if ($kernel->{use_long_name}) { +	kernel_str2initrd_long($kernel); +    } else { +	basename2initrd_basename(kernel_str2short_name($kernel)) . '.img'; +    } +} +sub kernel_str2liblinux_short() { +    $liblinux_path; +} + +sub kernel_str2label { +    my ($kernel, $o_use_long_name) = @_; +    if ($o_use_long_name || $kernel->{use_long_name}) { +	_sanitize_ver($kernel); +    } else { +	my $short_name = kernel_str2short_name($kernel); +	$kernel->{ext} =~ /^xen/ ? 'xen' : ($short_name eq 'vmlinuz' ? 'linux' : $short_name); +    } +}  sub get { -    my ($kernel, $bootloader) = @_; -    $_->{kernel_or_dev} && $_->{kernel_or_dev} eq $kernel and return $_ foreach @{$bootloader->{entries}}; +    my ($vmlinuz, $bootloader) = @_; +    $_->{kernel_or_dev} && $_->{kernel_or_dev} eq $vmlinuz and return $_ foreach @{$bootloader->{entries}};      undef;  }  sub get_label { @@ -51,163 +175,673 @@ sub get_label {      undef;  } -sub mkinitrd { -    my ($kernelVersion, $initrdImage, $o_vga) = @_; +=item mkinitrd($kernel_version, $bootloader, $entry, $initrd) -    $::testing || -e "$::prefix/$initrdImage" and return 1; +Regenerates kernel's initrd. -    my $loop_boot = loopback::prepare_boot(); +=cut + +sub mkinitrd { +    my ($kernel_version, $bootloader, $entry, $initrd) = @_; + +    my $dir = dirname($initrd); +    if ($::testing) { +      log::l("Skipping initrd generation: testing mode"); +      return $initrd; +    } elsif (-e "$::prefix/$initrd") { +      log::l("Skipping initrd generation: already exists"); +      return $initrd; +    } elsif ($initrd =~ /\(hd/) { +      log::l("Skipping initrd generation: unrecognized partition"); +      return $initrd; +    } elsif (!-d "$::prefix/$dir") { +      log::l("Skipping initrd generation: dir doesn't exist (probably !mounted foreign part)"); +      return $initrd; +    } + +    # for /boot on dos partitions when installing on loopback file on dos partition +    my $loop_boot = fs::loopback::prepare_boot(); -    my $o_resolution = $o_vga && do { -	require Xconfig::resolution_and_depth; -	my $res = Xconfig::resolution_and_depth::from_bios($o_vga); -	$res && $res->{X}; -    };      modules::load('loop'); -    if (!run_program::rooted($::prefix, "mkinitrd", "-v", "-f", $initrdImage, "--ifneeded", $kernelVersion, if_($o_resolution, '--splash' => $o_resolution))) { -	unlink("$::prefix/$initrdImage"); -	die "mkinitrd failed"; +    my @options = ( +		   if_($::isInstall, "-v"), "-f", $initrd, $kernel_version,  +		   if_($entry->{initrd_options}, split(' ', $entry->{initrd_options})), +		  ); + +    my $err; +    if (!run_program::rooted($::prefix, 'mkinitrd', '2>', \$err, @options)) { +	unlink("$::prefix/$initrd"); +	log::explanations("mkinitrd failed:\n(mkinitrd @options)\nError: <$err>"); +	die "mkinitrd failed:\n(mkinitrd @options)\nError: $err"; +    } +    add_boot_splash($initrd, $entry->{vga} || $bootloader->{vga}); + +    fs::loopback::save_boot($loop_boot); + +    -e "$::prefix/$initrd" && $initrd; +} + +=item rebuild_initrd($kernel_version, $bootloader, $entry, $initrd) + +Saves the old initrd then regenerate it. +If it fails, restore the old initrd. + +=cut + +sub rebuild_initrd { +    my ($kernel_version, $bootloader, $entry, $initrd) = @_; + +    my $old = $::prefix . $entry->{initrd} . '.old'; +    unlink $old; +    rename "$::prefix$initrd", $old; +    if (!mkinitrd($kernel_version, $bootloader, $entry, $initrd)) { +	log::l("rebuilding initrd failed, putting back the old one"); +	rename $old, "$::prefix$initrd";      } -    loopback::save_boot($loop_boot); +} -    -e "$::prefix/$initrdImage"; +sub remove_boot_splash { +    my ($initrd) = @_; +    run_program::rooted($::prefix, '/usr/share/bootsplash/scripts/remove-boot-splash', $initrd);  } +sub add_boot_splash { +    my ($initrd, $vga) = @_; + +    $vga or return; -sub read() { -    my $file = sprintf("/etc/%s.conf", arch() =~ /sparc/ ? 'silo' : arch() =~ /ppc/ ? 'yaboot' : 'lilo'); -    my $bootloader = $file =~ /lilo/ && detect_bootloader() =~ /GRUB/ && -f "/boot/grub/menu.lst" ? read_grub() : read_lilo($file); -    if (my $default = find { $_ && $_->{append} } get_label($bootloader->{default}, $bootloader), @{$bootloader->{entries}}) { -	$bootloader->{perImageAppend} ||= $default->{append}; +    eval { require Xconfig::resolution_and_depth } or return; +    if (my $res = Xconfig::resolution_and_depth::from_bios($vga)) { +	run_program::rooted($::prefix, '/usr/share/bootsplash/scripts/make-boot-splash', $initrd, $res->{X}); +    } else { +	log::l("unknown vga bios mode $vga");      } +} +sub update_splash { +    my ($bootloader) = @_; + +    my %real_initrd_entries; +    foreach (@{$bootloader->{entries}}) { +	if ($_->{initrd} && $_->{vga}) { +	    my $initrd = expand_symlinks($_->{initrd}); +	    $real_initrd_entries{$initrd} = $_; +	} +    } +    foreach (values %real_initrd_entries) { +        log::l("add boot splash to $_->{initrd}\n"); +	add_boot_splash($_->{initrd}, $_->{vga} || $bootloader->{vga}); +    } +} + +=item read($all_hds) + +Reads bootloader config by calling the proper read_XYZ function. + +=cut + +sub read { +    my ($all_hds) = @_; +    my $fstab = [ fs::get::fstab($all_hds) ]; +    foreach my $main_method (main_method_choices()) { +	my $f = $bootloader::{"read_$main_method"} or die "unknown bootloader method $main_method (read)"; +	my $bootloader = $f->($fstab); + +	cleanup_entries($bootloader) if $main_method ne 'refind'; + +	# handle raid-extra-boot (lilo) +	my @devs = $bootloader->{boot}; +	if ($bootloader->{'raid-extra-boot'} =~ /mbr/ &&  +	    (my $md = fs::get::device2part($bootloader->{boot}, $all_hds->{raids}))) { +	    @devs = map { $_->{rootDevice} } @{$md->{disks}}; +	} elsif ($bootloader->{'raid-extra-boot'} =~ m!/dev/!) { +	    @devs = split(',', $bootloader->{'raid-extra-boot'}); +	} + +	#- if $bootloader->{boot} is undefined, @devs contains a single undef element +	my ($type) = map { +	    if (is_uefi()) { +		if (-e "$::prefix/boot/refind_linux.conf") { +		    'refind'; +		} elsif (-f "$::prefix/boot/EFI/EFI/mageia/grub" . uefi_type() . ".efi" || +		         $main_method eq 'grub2' && $bootloader->{removable} && -f "$::prefix/boot/EFI/EFI/BOOT/BOOT" . uc(uefi_type()) . ".EFI") { +		    'grub2'; +		} else { +		    () +		} +	    } elsif (m!/fd\d+$!) { +		warn "not checking the method on floppy, assuming $main_method is right\n"; +		$main_method; +	    } elsif (member($main_method, qw(cromwell uboot))) { +		#- not checking, there's only one bootloader anyway :) +		$main_method; +	    } elsif (my $type = partition_table::raw::typeOfMBR($_)) { +		warn "typeOfMBR $type on $_ for method $main_method\n" if $ENV{DEBUG}; +		$type; +	    } else { () } +	} @devs; + +	if ($type eq $main_method) { +	    return $bootloader if read_($bootloader); +	} +    } +} + +sub read_ { +    my ($bootloader) = @_; +	    my @prefered_entries = map { get_label($_, $bootloader) } $bootloader->{default}, 'linux'; + +	    if (my $default = find { $_ && $_->{type} eq 'image' } (@prefered_entries, @{$bootloader->{entries}})) { +		$bootloader->{default_options} = $default; +		$bootloader->{perImageAppend} ||= $default->{append}; +		log::l("perImageAppend is now $bootloader->{perImageAppend}"); +		$bootloader->{default_vga} ||= $default->{vga}; +		log::l("default_vga is now $bootloader->{default_vga}"); +	    } else { +		$bootloader->{default_options} = {}; +	    } +	    return $bootloader; +} + + +=item is_grub2_already_crypted($password) + +Returns whether grub2 password is already encrypted or not + +=cut + +sub is_grub2_already_crypted { +    my ($password) = @_; +    $password =~ /grub.pbkdf2.sha512/; +} + +=item read_grub2 ($o_fstab) + +Read back GRUB2 config + +=cut + +sub read_grub2() { +    my %bootloader = read_grub2_install_sh() if -e get_grub2_install_sh(); +    return if is_empty_hash_ref(\%bootloader) & !-s "$::prefix/boot/grub2/grub.cfg"; +    my %h = getVarsFromSh("$::prefix/etc/default/grub"); +    $bootloader{timeout} = $h{GRUB_TIMEOUT}; +    # keep suggested perImageAppend and default_vga on first run (during installer) or when migrating from grub-legacy or lilo: +    my ($vga, $other) = partition { /^vga=/ } split(' ', $h{GRUB_CMDLINE_LINUX_DEFAULT}); +    $bootloader{perImageAppend} ||= join(' ', @$other) if @$other; +    $bootloader{default_vga} ||= $vga->[0] =~ /vga=(.*)/ && $1 if @$vga; +    $bootloader{entries} = []; +    my $entry; +    my $f = "$::prefix/boot/grub2/grub.cfg"; +    my @menus; +    foreach (cat_utf8($f)) { +	next if /^#/; +	if (/menuentry\s+['"]([^']+)["']/) { +	    $entry = { label => $1, real_label => join('>', @menus, $1) }; +	} elsif (/linux(?:16)?\s+([^-]\S+)\s+(.*)?/ || /module\s+(\S+vmlinu\S+)\s+(.*)?/) { +	    $entry->{type} = 'image'; +	    @$entry{qw(kernel_or_dev append)} = ($1, $2); +	    my ($vga, $other) = partition { /^vga=/ } split(' ', $entry->{append}); +	    if (@$vga) { +	        $entry->{vga} = $vga->[0] =~ /vga=(.*)/ && $1; +	        $entry->{append} = join(' ', @$other); +	    } +	} elsif (/initrd(?:16)?\s+(\S+)/ || /module\s+(\S+initrd\S+)\s+(.*)?/) { +	    $entry->{initrd} = $1; +	} elsif (/submenu\s+['"]([^']+)["']/) { +	    push @menus, $1; +	} elsif (/^\s*}/) { +	    if ($entry) { +		push @{$bootloader{entries}}, $entry; +		undef $entry; +	    } else { +		pop @menus; +	    } +	} +    } + +    # get default entry: +    foreach (run_program::rooted_get_stdout($::prefix, qw(grub2-editenv list))) { +	$bootloader{default} = $1 if /saved_entry=(.*)/; +	$bootloader{default} =~ s/.*>//; # strip full menu entry path +    } + +    # Get password prior to run update-grub2: +    $bootloader{password} = { getVarsFromSh(get_grub2_users()) }->{GRUB2_PASSWORD}; + +    $bootloader{method} = cat_($f) =~ /set theme=.*maggy/ ? 'grub2-graphic' : 'grub2'; +    \%bootloader; +} + +sub read_grub2_install_sh() { +    my $s = cat_(get_grub2_install_sh()); +    my %h; +    if ($s =~ m!(/dev/\S+)!m) { +	$h{boot} = $1; +    } +    $h{no_esp_or_mbr} = $s =~ m!--grub-setup=/bin/true!; +    $h{removable} = $s =~ m!--removable!; +    %h; +} + +=item read_grub($fstab) + +Reads back Grub Legacy config. + +=cut + +sub read_grub { +    my ($fstab) = @_; + +    my $grub2dev = read_grub_device_map(); +    my $boot_root = read_grub_install_sh(); +    _may_fix_grub2dev($fstab, $grub2dev, $boot_root->{boot_part}); + +    my $bootloader = read_grub_menu_lst($fstab, $grub2dev) or return; + +    if ($boot_root->{boot}) { +	$bootloader->{boot} = grub2dev($boot_root->{boot}, $grub2dev); +    } +      $bootloader;  } -sub read_grub() { + +=item _may_fix_grub2dev($fstab, $grub2dev, $boot_part) + +Adapts device.map (aka $grub2dev) when for example hda is now sda. +nb: + +=over 4 + +=item * $boot_part comes from C</boot/grub/install.sh> C<root (hd...)> line + +=item * $grub2dev is C</boot/grub/device.map> + +=back + +=cut + +sub _may_fix_grub2dev { +    my ($fstab, $grub2dev, $boot_part) = @_; + +    $boot_part or log::l("install.sh does not contain 'root (hd...)' line, no way to magically adapt device.map"), return; + +    my $real_boot_part = fs::get::root_($fstab, 'boot') or +      log::l("argh... the fstab given is useless, it doesn't contain '/'"), return; +     +    my $real_boot_dev = $real_boot_part->{rootDevice} or return; # if /boot is on Linux RAID 1, hope things are all right... + +    if (my $prev_boot_part = fs::get::device2part(grub2dev($boot_part, $grub2dev), $fstab)) { # the boot_device as far as grub config files say +	$real_boot_part == $prev_boot_part and return; +    } + +    log::l("WARNING: we have detected that device.map is inconsistent with the system"); + +    my ($hd_grub, undef, undef) = parse_grub_file($boot_part); # extract hdX  +    if (my $prev_hd_grub = find { $grub2dev->{$_} eq $real_boot_dev } keys %$grub2dev) { +	$grub2dev->{$prev_hd_grub} = $grub2dev->{$hd_grub}; +	log::l("swapping result: $hd_grub/$real_boot_dev and $prev_hd_grub/$grub2dev->{$hd_grub}"); +    } else { +	log::l("argh... can't swap, setting $hd_grub to $real_boot_dev anyway"); +    } +    $grub2dev->{$hd_grub} = $real_boot_dev; +} + +=item read_grub_install_sh() { + +Reads "config" from /boot/grub/install.sh (mainly used partitions) + +=cut + +sub read_grub_install_sh() { +    my $s = cat_("$::prefix/boot/grub/install.sh"); +    my %h; + +    #- matches either: +    #-   setup (hd0) +    #-   install (hd0,0)/boot/grub/stage1 d (hd0) (hd0,0)/boot/grub/stage2 p (hd0,0)/boot/grub/menu.lst +    if ($s =~ /^(?:setup.*|install\s.*\sd)\s+(\(.*?\))/m) { +	$h{boot} = $1; +    }     +    if ($s =~ /^root\s+(\(.*?\))/m) { +	$h{boot_part} = $1; +    } +    \%h; +} + +sub _parse_grub_menu_lst() {      my $global = 1;      my ($e, %b); -    my %mnt_pts = ( -	"/dev/" . devices::from_devfs(readlink('/dev/root')) => "/", #- is this useful??? -	map { (split)[0..1] } cat_("/proc/mounts") -    ); +    my $menu_lst_file = "$::prefix/boot/grub/menu.lst"; +    -e $menu_lst_file or return; -    foreach (cat_("$::prefix/boot/grub/menu.lst")) { +    foreach (MDK::Common::File::cat_utf8($menu_lst_file)) { +	my $verbatim = $_;          chomp;  	s/^\s*//; s/\s*$//;          next if /^#/ || /^$/; -	my ($keyword, $v) = split(' ', $_, 2) or +	my ($keyword, $v) = split('[ \t=]+', $_, 2) or  	  warn qq(unknown line in /boot/grub/menu.lst: "$_"\n), next; +	if ($keyword eq 'root') { +	    #- rename to avoid name conflict +	    $keyword = 'grub_root'; +	} +          if ($keyword eq 'title') {              push @{$b{entries}}, $e = { label => $v };              $global = 0;          } elsif ($global) { -            $b{$keyword} = $v eq '' ? 1 : ungrubify($v, \%mnt_pts); +            $b{$keyword} = $v;          } else { -            $e->{root} = $1 if $v =~ s/root=(\S*)\s*//;              if ($keyword eq 'kernel') {                  $e->{type} = 'image'; -                (my $kernel, $e->{append}) = split(' ', $v, 2); -		$e->{kernel_or_dev} = ungrubify($kernel, \%mnt_pts); -            } elsif ($keyword eq 'root') { +		$e->{kernel} = $v; +            } elsif ($keyword eq 'chainloader') {                  $e->{type} = 'other'; -		if ($v =~ /,/) { -		    $e->{table} = grub2dev($v, 1); -		} else { -		    $e->{unsafe} = 1; -		} -                $e->{kernel_or_dev} = grub2dev($v);                  $e->{append} = ""; -            } elsif ($keyword eq 'initrd') { -                $e->{initrd} = ungrubify($v, \%mnt_pts); -            } +            } elsif ($keyword eq 'configfile') { +                $e->{type} = 'grub_configfile'; +                $e->{configfile} = $v; +            } elsif ($keyword eq 'map') { +		$e->{mapdrive}{$2} = $1 if $v =~ m/\((.*)\) \((.*)\)/; +            } elsif ($keyword eq 'module') { +		push @{$e->{modules}}, $v; +	    } else { +		$e->{$keyword} = $v eq '' ? 1 : $v; +	    }          } +	$e and $e->{verbatim} .= $verbatim;      } -    # Generating /etc/lilo.conf require having a boot device: -    foreach (cat_("$::prefix/boot/grub/install.sh")) { -        $b{boot} = grub2dev($1) if /\s+d\s+(\(.*?\))/; + +    %b; +} + + +=item is_already_crypted($password) + +Returns whether grub password is already encrypted or not + +=cut + +sub is_already_crypted { +    my ($password) = @_; +    $password =~ /^--md5 (.*)/; +} + +=item read_grub_menu_lst($fstab, $grub2dev) + +Read config from /boot/grub/menu.lst + +=cut + +sub read_grub_menu_lst { +    my ($fstab, $grub2dev) = @_; + +    my %b = _parse_grub_menu_lst(); + +    foreach my $keyword (grep { $_ ne 'entries' } keys %b) { +	$b{$keyword} = $b{$keyword} eq '' ? 1 : grub2file($b{$keyword}, $grub2dev, $fstab, \%b);      }      #- sanitize -    foreach (@{$b{entries}}) { -	my ($vga, $other) = partition { /^vga=/ } split(' ', $_->{append}); -	if ($vga) { -	    $_->{vga} = $vga->[0] =~ /vga=(.*)/ && $1; -	    $_->{append} = join(' ', @$other); +    foreach my $e (@{$b{entries}}) { +	if (member($e->{type}, 'other', 'grub_configfile')) { +	    eval { $e->{kernel_or_dev} = grub2dev($e->{rootnoverify} || $e->{grub_root}, $grub2dev) }; +	    $e->{keep_verbatim} = 1 unless $e->{kernel_or_dev};  +	} elsif ($e->{initrd}) { + 	    my $initrd; + 	    eval { $initrd = grub2file($e->{initrd}, $grub2dev, $fstab, $e) }; + 	    if ($initrd) { + 		$e->{initrd} = $initrd; + 	    } else { + 		$e->{keep_verbatim} = 1; + 	    } +	} + +	if ($e->{kernel} =~ /xen/ && @{$e->{modules} || []} == 2 && $e->{modules}[1] =~ /initrd/) { +	    (my $xen, $e->{xen_append}) = split(' ', $e->{kernel}, 2); +	    ($e->{kernel}, my $initrd) = @{delete $e->{modules}}; +	    $e->{xen} = grub2file($xen, $grub2dev, $fstab, $e); +	    $e->{initrd} = grub2file($initrd, $grub2dev, $fstab, $e); +	} +	if (my $v = delete $e->{kernel}) { +	    (my $kernel, $e->{append}) = split(' ', $v, 2); +	    $e->{append} = join(' ', grep { !/^BOOT_IMAGE=/ } split(' ', $e->{append})); +	    $e->{root} = $1 if $e->{append} =~ s/root=(\S*)\s*//; +	    eval { $e->{kernel_or_dev} = grub2file($kernel, $grub2dev, $fstab, $e) }; +	    $e->{keep_verbatim} = 1 if !$e->{kernel_or_dev} || dirname($e->{kernel_or_dev}) ne '/boot'; +	} +	my ($vga, $other) = partition { /^vga=/ } split(' ', $e->{append}); +	if (@$vga) { +	    $e->{vga} = $vga->[0] =~ /vga=(.*)/ && $1; +	    $e->{append} = join(' ', @$other);  	}      }      $b{nowarn} = 1;      # handle broken installkernel -r: -    $b{default} = min($b{default}, scalar(@{$b{entries}}) - 1); -    $b{default} = $b{entries}[$b{default}]{label}; +    if (@{$b{entries}}) { +	$b{default} = min($b{default}, scalar(@{$b{entries}}) - 1); +	$b{default} = $b{entries}[$b{default}]{label}; +    } +    $b{method} = $b{gfxmenu} ? 'grub-graphic' :  'grub-menu';      \%b;  } -sub read_lilo { -    my ($file) = @_; +sub _parse_extlinux_conf() {      my $global = 1; -    my ($e, $v); +    my ($e, %b); + +    my $f = "$::prefix/boot/extlinux/extlinux.conf"; +    -e $f or return; + +    foreach (cat_utf8($f)) { +	chomp; +	s/^\s*//; s/\s*$//; +	next if /^#/ || /^$/; +	my ($keyword, $v) = split('[ \t=]+', $_, 2) or +	    warn qq(unknown line in $f: "$_"\n), next; + +	$keyword = lc($keyword); + +	if ($keyword eq 'label') { +	    push @{$b{entries}}, $e = { label => $v }; +	    $global = 0; +	} elsif ($global) { +	    $b{$keyword} = $v; +	} else { +	    if ($keyword eq 'kernel') { +		$e->{type} = 'image'; +		$e->{kernel_or_dev} = $v; +	    } else { +		$e->{$keyword} = $v; +	    } +	} +    } + +    %b; +} + +sub read_uboot() { +    my %b = _parse_extlinux_conf(); + +    $b{method} = 'uboot'; +    $b{timeout} //= 30; + +    my $is_raspberry = cat_("/proc/device-tree/model") =~ /^Raspberry Pi/; +    if ($is_raspberry) { +	$b{perImageAppend} //= "8250.nr_uarts=1 console=ttyS0,115200 console=tty1 elevator=deadline cma=256M\@512M"; +    } + +    $b{perImageAppend} //= $b{entries}[0]{append}; + +    \%b; +} + +=item read_refind ($fstab) + +Read back rEFInd config + C</boot/refind_linux.conf> + +=cut + +sub read_refind() { +    my %bootloader = (entries => []); +    foreach (cat_utf8("$::prefix/boot/refind_linux.conf")) { +	next if /^#/; +	my ($label, $append) = /"(.*)"\s*"(.*)"/; +	my $root = $1 if $append =~ s/root=(\S*)\s*//; +	my $vga  = $1 if $append =~ s/vga=(\S*)\s*//; +	if ($label && $root) { +	    push @{$bootloader{entries}}, { +		type => 'image', +		kernel_or_dev => '/boot/vmlinuz', +		label  => $label, +		root   => $root, +		append => $append, +		vga    => $vga +	    }; +	} +    } +    $bootloader{method} = 'refind'; +    read_refind_config(\%bootloader); +    \%bootloader; +} + +sub read_refind_config { +    my ($bootloader) = @_; + +    #- These are the rEFInd default values. +    $bootloader->{use_nvram}    = 1; +    $bootloader->{banner_path}  = ''; +    $bootloader->{banner_scale} = 'noscale'; + +    if (-r "$::prefix/boot/EFI/EFI/refind/refind.conf") { +        read_refind_conf($bootloader, "$::prefix/boot/EFI/EFI/refind/refind.conf"); +    } elsif (-r "$::prefix/boot/EFI/EFI/BOOT/refind.conf") { +        read_refind_conf($bootloader, "$::prefix/boot/EFI/EFI/BOOT/refind.conf"); +    } else { +        #- This is the preferred value if rEFInd is not yet installed, +        $bootloader->{banner_path} = 'refind_banner.png'; +    } +} + +sub read_refind_conf { +    my ($bootloader, $config_file) = @_; +    foreach (cat_utf8($config_file)) { +        if ($_ =~ /^\s*use_nvram\s+(false|off|0)/) { +            $bootloader->{use_nvram} = 0; +        } elsif ($_ =~ /^\s*banner\s+(\S*)/) { +            $bootloader->{banner_path} = $1; +        } elsif ($_ =~ /^\s*banner_scale\s+(\S*)/) { +            $bootloader->{banner_scale} = $1; +        } +    } +} + +# FIXME: actually read back previous conf +sub read_cromwell() { +    +{ method => 'cromwell' }; +} + + +sub read_lilo() { +    my $bootloader = read_lilo_like("/etc/lilo.conf", sub { $_[0] }); + +    delete $bootloader->{timeout} unless $bootloader->{prompt}; +    $bootloader->{timeout} = $bootloader->{timeout} / 10 if $bootloader->{timeout}; + +    my $submethod = member($bootloader->{install}, 'text', 'menu') ? $bootloader->{install} : 'menu'; +    $bootloader->{method} = "lilo-$submethod"; +     +    $bootloader; +} +sub read_lilo_like { +    my ($file, $filter_file) = @_; + +    my $global = 1; +    my ($e);      my %b; -    foreach (cat_("$::prefix$file")) { -	next if /^\s*#/ || /^\s*$/; -	($_, $v) = /^\s*([^=\s]+)\s*(?:=\s*(.*?))?\s*$/ or log::l("unknown line in $file: $_"), next; - -	if (/^(image|other)$/) { -	    if (arch() =~ /ppc/) { -		$v =~ s/hd:\d+,//g; -	    }    -	    push @{$b{entries}}, $e = { type => $_, kernel_or_dev => $v }; +    -e "$::prefix$file" or return; +    foreach my $line (cat_("$::prefix$file")) { +	next if $line =~ /^\s*#/ || $line =~ /^\s*$/; +	my ($cmd, $v) = $line =~ /^\s*([^=\s]+)\s*(?:=\s*(.*?))?\s*$/ or log::l("unknown line in $file: $line"), next; + +	if ($cmd =~ /^(?:image|other|macos|macosx|bsd|darwin)$/) { +	    $v = $filter_file->($v); +	    push @{$b{entries}}, $e = { type => $cmd, kernel_or_dev => $v };  	    $global = 0;  	} elsif ($global) { -	    if ($_ eq 'disk' && $v =~ /(\S+)\s+bios\s*=\s*(\S+)/) { +	    if ($cmd eq 'disk' && $v =~ /(\S+)\s+bios\s*=\s*(\S+)/) {  		$b{bios}{$1} = $2; -	    } elsif ($_ eq 'bios') { +	    } elsif ($cmd eq 'bios') {  		$b{bios}{$b{disk}} = $v; -	    } elsif ($_ eq 'init-message') { +	    } elsif ($cmd eq 'init-message') {  		$v =~ s/\\n//g;   		$v =~ s/"//g;  		$b{'init-message'} = $v;  	    } else { -		$b{$_} = $v eq '' ? 1 : $v; +		$b{$cmd} = $v eq '' ? 1 : $v;  	    }  	} else { -	    if ((/map-drive/ .. /to/) && /to/) { +	    if (($cmd eq 'map-drive' .. $cmd eq 'to') && $cmd eq 'to') {  		$e->{mapdrive}{$e->{'map-drive'}} = $v;  	    } else { -		if (arch() =~ /ppc/) { -		    $v =~ s/hd:\d+,//g; -		    $v =~ s/"//g; +		if ($cmd eq 'initrd') { +		    $v = $filter_file->($v);  		} -		$e->{$_} = $v || 1 if !member($_, 'read-only'); +		$e->{$cmd} = $v || 1;  	    }  	}      } -    if (arch() !~ /ppc/) { -	delete $b{timeout} unless $b{prompt}; -	sub remove_quotes_and_spaces { -	    local ($_) = @_; -	    s/^\s*//; s/\s*$//; -	    s/^"(.*?)"$/$1/; -	    $_; + +    sub remove_quotes_and_spaces { +	local ($_) = @_; +	s/^\s*//; s/\s*$//; +	s/^"(.*?)"$/$1/; +	s/\\"/"/g; +	s/^\s*//; s/\s*$//; #- do it again for append=" foo" +	$_; +    } + +    foreach ('append', 'root', 'default', 'raid-extra-boot') { +	$b{$_} = remove_quotes_and_spaces($b{$_}) if $b{$_}; +    } +    foreach my $entry (@{$b{entries}}) { +	foreach ('append', 'root', 'label') { +	    $entry->{$_} = remove_quotes_and_spaces($entry->{$_}) if $entry->{$_}; +	} +	if ($entry->{kernel_or_dev} =~ /\bmbootpack\b/) { +	    $entry->{initrd} = $entry->{kernel_or_dev}; +	    $entry->{initrd} =~ s/\bmbootpack/initrd/; +	    $entry->{kernel_or_dev} =~ s/\bmbootpack/vmlinuz/; +	    $entry->{kernel_or_dev} =~ s/.img$//; +	    #- assume only xen is configured with mbootpack +	    $entry->{xen} = '/boot/xen.gz'; +	    $entry->{root} = $1 if $entry->{append} =~ s/root=(\S*)\s*//; +	    ($entry->{xen_append}, $entry->{append}) = split '\s*--\s*', $entry->{append}, 2;  	} -	$_->{append} = remove_quotes_and_spaces($_->{append}) foreach \%b, @{$b{entries}}; -	$_->{label}  = remove_quotes_and_spaces($_->{label})  foreach @{$b{entries}}; -	$b{default} = remove_quotes_and_spaces($b{default}) if $b{default}; -	$b{timeout} = $b{timeout} / 10 if $b{timeout}; -	delete $b{message};      } -    #- cleanup duplicate labels & bad entries (in case file is corrupted) -    my %seen; -    @{$b{entries}} =  -	grep { !$seen{$_->{label}}++ } -	grep { $_->{type} ne 'image' || -e "$::prefix$_->{kernel_or_dev}" } @{$b{entries}}; +    # cleanup duplicate labels (in case file is corrupted) +    @{$b{entries}} = uniq_ { $_->{label} } @{$b{entries}};      \%b;  } +sub cleanup_entries { +    my ($bootloader) = @_; + +    #- cleanup bad entries (in case file is corrupted) +    @{$bootloader->{entries}} =  +	grep {  +	    my $pb = $_->{type} eq 'image' && !$_->{keep_verbatim} && ! -e "$::prefix$_->{kernel_or_dev}"; +	    log::l("dropping bootloader entry $_->{label} since $_->{kernel_or_dev} doesn't exist") if $pb; +	    !$pb; +	} @{$bootloader->{entries}}; +} +  sub suggest_onmbr {      my ($hd) = @_; @@ -216,7 +850,7 @@ sub suggest_onmbr {      if (my $type = partition_table::raw::typeOfMBR($hd->{device})) {  	if (member($type, qw(dos dummy empty))) {  	    $unsafe = 0; -	} elsif (!member($type, qw(lilo grub))) { +	} elsif (!member($type, qw(lilo grub grub2))) {  	    $onmbr = 0;  	}  	log::l("bootloader::suggest_onmbr: type $type, onmbr $onmbr, unsafe $unsafe"); @@ -224,21 +858,54 @@ sub suggest_onmbr {      ($onmbr, $unsafe);  } -sub mixed_kind_of_disks { -    my ($hds) = @_; -    (find { $_->{device} =~ /^sd/ } @$hds) && (find { $_->{device} =~ /^hd/ } @$hds) || -      (find { $_->{device} =~ /^hd[e-z]/ } @$hds) && (find { $_->{device} =~ /^hd[a-d]/ } @$hds); +=item allowed_boot_disks($bootloader, $all_hds) + +Returns list of disks where we can install the bootloader when not in UEFI mode +(accounting for misssing BIOS boot partitions on GPT disks) +in UEFI mode, grub2 automatically look for the ESP). + +=cut + +sub allowed_boot_disks { +    my ($all_hds) = @_; +    # GPT disks w/o a BIOS boot partition do not have free space for grub2 to embed: +    grep { $_->{pt_table_type} ne 'gpt' || +		 any { isBIOS_GRUB($_) } map { partition_table::get_normal_parts($_) } $_; +    } @{$all_hds->{hds}}; +} + +=item allowed_boot_parts($bootloader, $all_hds) + +Returns list of places where we can install the bootloader when not in UEFI mode +(in UEFI mode, grub2 automatically look for the ESP). + +=cut + +sub allowed_boot_parts { +    my ($bootloader, $all_hds) = @_; +    ( +     allowed_boot_disks($all_hds), # MBR + +     if_($bootloader->{method} =~ /lilo/, +	 grep { $_->{level} eq '1' } @{$all_hds->{raids}} +	), +     (if_(main_method($bootloader->{method}) ne 'grub2', +	  grep { !isFat_or_NTFS($_) } fs::get::fstab($all_hds)), # filesystems except those who do not leave space for our bootloaders +     ), +     detect_devices::floppies(), +    );  }  sub same_entries {      my ($a, $b) = @_;      foreach (uniq(keys %$a, keys %$b)) { -	if ($_ eq 'label') { +	if (member($_, 'label', 'append', 'mapdrive', 'readonly', 'makeactive', 'verbatim')) { +	    next; +	} elsif ($_ eq 'grub_root' && (!$a->{$_} || !$b->{$_})) { +	    #- grub_root is mostly internal stuff. if it misses, it's ok  	    next; -	} elsif ($_ eq 'append') { -	    next if join(' ', sort split(' ', $a->{$_})) eq join(' ', sort split(' ', $b->{$_}))  	} else {  	    next if $a->{$_} eq $b->{$_}; @@ -246,7 +913,7 @@ sub same_entries {  	    next if $inode_a && $inode_b && $inode_a == $inode_b;  	} -	log::l("entries $a->{label} don't have same $_: $a->{$_} ne $b->{$_}"); +	log::l("entries $a->{label} do not have same $_: $a->{$_} ne $b->{$_}");  	return;      }      1; @@ -256,7 +923,8 @@ sub add_entry {      my ($bootloader, $v) = @_;      my $to_add = $v; -    foreach my $label ($v->{label}, map { 'old' . $_ . '_' . $v->{label} } ('', 2..10)) { +    my $label = $v->{label}; +    for (my $i = 0; $i < 100;) {  	my $conflicting = get_label($label, $bootloader);  	$to_add->{label} = $label; @@ -264,6 +932,9 @@ sub add_entry {  	if ($conflicting) {  	    #- replacing $conflicting with $to_add  	    @{$bootloader->{entries}} = map { $_ == $conflicting ? $to_add : $_ } @{$bootloader->{entries}}; + +	    #- we will keep $conflicting, but not with same symlinks if used by the entry to add +	    expand_entry_symlinks($bootloader, $conflicting);  	} else {  	    #- we have found an unused label  	    push @{$bootloader->{entries}}, $to_add; @@ -274,50 +945,177 @@ sub add_entry {  	    return $v;  	}  	$to_add = $conflicting; + +	if ($to_add->{label} eq 'linux') { +	    $label = kernel_str2label(vmlinuz2kernel_str($to_add->{kernel_or_dev}), 'use_long_name'); +	} else { +	    $label =~ s/^alt\d*_//; +	    $label = 'alt' . ($i++ ? $i : '') . "_$label"; +	}      }      die 'add_entry';  } -sub add_kernel { -    my ($bootloader, $version, $ext, $root, $v) = @_; +sub expand_entry_symlinks { +    my ($bootloader, $entry) = @_; + +    foreach my $kind ('kernel_or_dev', 'initrd') { +	my $old_long_name = $bootloader->{old_long_names} && $bootloader->{old_long_names}{$entry->{$kind}} or next; -    #- new versions of yaboot don't handle symlinks -    my $ppcext = $ext; -    if (arch() =~ /ppc/) { -	$ext = "-$version"; +	#- replace all the {$kind} using this symlink to the real file +	log::l("replacing $entry->{$kind} with $old_long_name for bootloader label $entry->{label}"); +	$entry->{$kind} = $old_long_name;      } +} -    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"); +sub _do_the_symlink { +    my ($bootloader, $link, $long_name) = @_; -    my $initrd = "/boot/initrd-$version.img"; -    mkinitrd($version, $initrd, $v->{vga}) or undef $initrd; -    if ($initrd && $ext ne "-$version") { -	$initrd = "/boot/initrd$ext.img"; -	symlinkf("initrd-$version.img", "$::prefix$initrd") or cp_af("$::prefix/boot/initrd-$version.img", "$::prefix$initrd"); +    my $existing_link = readlink("$::prefix$link"); +    if ($existing_link && $existing_link eq $long_name) { +	#- nothing to do :) +	return;      } -    my $label = $ext =~ /-(default)/ ? $1 : $ext =~ /\d\./ && sanitize_ver("linux-$version") || "linux$ext"; +    if ($existing_link) { +	#- the symlink is going to change!  +	#- replace all the {$kind} using this symlink to the real file +	my $old_long_name = $existing_link =~ m!^/! ? $existing_link : "/boot/$existing_link"; +	if (-e "$::prefix$old_long_name") { +	    $bootloader->{old_long_names}{$link} = $old_long_name; +	} else { +	    log::l("ERROR: $link points to $old_long_name which does not exist"); +	} +    } elsif (-e "$::prefix$link") { +	log::l("ERROR: $link is not a symbolic link"); +    } -    #- more yaboot concessions - PPC -    if (arch() =~ /ppc/) { -	$label = $ppcext =~ /-(default)/ ? $1 : "linux$ppcext"; +    #- changing the symlink +    symlinkf($long_name, "$::prefix$link") +      or cp_af("$::prefix/boot/$long_name", "$::prefix$link"); +} + +# for lilo & xen +sub get_mbootpack_filename { +    my ($entry) = @_; +    my $mbootpack_file = $entry->{initrd}; +    $mbootpack_file =~ s/\binitrd/mbootpack/; +    $entry->{xen} && $mbootpack_file; +} + +# for lilo & xen +sub build_mbootpack { +    my ($entry) = @_; + +    my $mbootpack = '/usr/bin/mbootpack'; +    -f $::prefix . $entry->{kernel_or_dev} && -f $::prefix . $entry->{initrd} or return; + +    my $mbootpack_file = get_mbootpack_filename($entry); +    -f ($::prefix . $mbootpack_file) and return 1; + +    my $error; +    my $xen_kernel = '/tmp/xen_kernel'; +    my $xen_vmlinux = '/tmp/xen_vmlinux'; +    my $_b = before_leaving { unlink $::prefix . $_ foreach $xen_kernel, $xen_vmlinux }; +    run_program::rooted($::prefix, '/bin/gzip', '>', $xen_kernel, '2>', \$error, '-dc', $entry->{xen}) +      or die "unable to uncompress xen kernel"; +    run_program::rooted($::prefix, '/bin/gzip', '>', $xen_vmlinux, '2>', \$error, '-dc', $entry->{kernel_or_dev}) +      or die "unable to uncompress xen vmlinuz"; + +    run_program::rooted($::prefix, $mbootpack, +                        "2>", \$error, +                        '-o', $mbootpack_file, +                        '-m', $xen_vmlinux, +                        '-m', $entry->{initrd}, +                        $xen_kernel) +      or die "mbootpack failed: $error"; + +    1; +} + +sub add_kernel { +    my ($bootloader, $kernel_str, $v, $b_nolink, $b_no_initrd) = @_; + +    #- eg: for /boot/vmlinuz-2.6.17-13mdvxen0 (pkg kernel-xen0-xxx)  +    #-      or /boot/vmlinuz-2.6.18-xen (pkg kernel-xen-uptodate) +    if ($kernel_str->{version} =~ /xen/ && -f '/boot/xen.gz') { +	$v->{xen} = '/boot/xen.gz';      }      add2hash($v,  	     {  	      type => 'image', -	      root => "/dev/$root", -	      label => $label, -	      kernel_or_dev => $image, -	      initrd => $initrd, +	      label => kernel_str2label($kernel_str),  	     }); -    $v->{append} = normalize_append("$bootloader->{perImageAppend} $v->{append}"); + +    #- normalize append and handle special options +    { +	my ($simple, $dict) = unpack_append("$bootloader->{perImageAppend} $v->{append}"); +	if ($v->{label} eq 'failsafe') { +	    #- perImageAppend contains resume=/dev/xxx which we don't want +	    @$dict = grep { $_->[0] ne 'resume' } @$dict; +	} +	$v->{append} = pack_append($simple, $dict); +    } + +    $b_nolink ||= $kernel_str->{use_long_name}; + +    #- do not link /boot/vmlinuz to xen +    $b_nolink ||= $v->{xen}; + +    my $vmlinuz_long = kernel_str2vmlinuz_long($kernel_str); +    my $initrd_long = kernel_str2initrd_long($kernel_str); +    $v->{kernel_or_dev} = "/boot/$vmlinuz_long"; +    -e "$::prefix$v->{kernel_or_dev}" or log::l("unable to find kernel image $::prefix$v->{kernel_or_dev}"), return; +    log::l("adding $v->{kernel_or_dev}"); + +    if (!$b_no_initrd) { +	$v->{initrd} = mkinitrd($kernel_str->{version}, $bootloader, $v, "/boot/$initrd_long"); +    } + +    my $liblinux_long = kernel_str2liblinux_long($kernel_str); +    if (-d "$::prefix$liblinux_long") { +	$v->{liblinux} = $v->{fdtdir} = $liblinux_long; +    } + +    if (!$b_nolink) { +	$v->{kernel_or_dev} = '/boot/' . kernel_str2vmlinuz_short($kernel_str); +	    _do_the_symlink($bootloader, $v->{kernel_or_dev}, $vmlinuz_long); + +	if ($v->{initrd}) { +	    $v->{initrd} = '/boot/' . kernel_str2initrd_short($kernel_str); +		_do_the_symlink($bootloader, $v->{initrd}, $initrd_long); +	} + +	if ($v->{liblinux}) { +	    $v->{liblinux} = kernel_str2liblinux_short(); +	    _do_the_symlink($bootloader, $v->{liblinux}, basename($liblinux_long)); +	} +    } +      add_entry($bootloader, $v);  } +=item rebuild_initrds($bootloader) + +Rebuilds all initrds + +=cut + +sub rebuild_initrds { +    my ($bootloader) = @_; + +    my %done; +    foreach my $v (grep { $_->{initrd} } @{$bootloader->{entries}}) { +	my $kernel_str = vmlinuz2kernel_str($v->{kernel_or_dev}) or next; +	my $initrd_long = '/boot/' . kernel_str2initrd_long($kernel_str); +	next if $done{$initrd_long}++; + +	rebuild_initrd($kernel_str->{version}, $bootloader, $v, $initrd_long); +    } +} + +# unused (?)  sub duplicate_kernel_entry {      my ($bootloader, $new_label) = @_; @@ -327,9 +1125,11 @@ sub duplicate_kernel_entry {      add_entry($bootloader, $entry);  } +my $uniq_dict_appends = join('|', qw(acpi pci resume PROFILE XFree)); +  sub unpack_append {      my ($s) = @_; -    my @l = split(' ', $s); +    my @l = "$s " =~ /((?:[^"\s]+|".*?")*)\s+/g;      [ grep { !/=/ } @l ], [ map { if_(/(.*?)=(.*)/, [$1, $2]) } @l ];  }  sub pack_append { @@ -337,43 +1137,40 @@ sub pack_append {      #- normalize      $simple = [ reverse(uniq(reverse @$simple)) ]; -    $dict = [ reverse(uniq_ { my ($k, $v) = @{$_[0]}; $k eq 'mem' ? "$k=$v" : $k } reverse @$dict) ]; +    $dict = [ reverse(uniq_ {  +	my ($k, $v) = @$_;  +	$k =~ /^($uniq_dict_appends)$/ ? $k : "$k=$v"; +    } reverse @$dict) ];      join(' ', @$simple, map { "$_->[0]=$_->[1]" } @$dict);  } -sub normalize_append { -    my ($s) = @_; -    my ($simple, $dict) = unpack_append($s); -    pack_append($simple, $dict); -} - -sub append__mem_is_memsize { $_[0] =~ /^\d+[kM]?$/i } - -sub get_append { -    my ($b, $key) = @_; -    my ($simple, $dict) = unpack_append($b->{perImageAppend}); -    if (member($key, @$simple)) { -	return 1; -    } -    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 modify_append {      my ($b, $f) = @_; -    foreach (\$b->{perImageAppend}, map { \$_->{append} } grep { $_->{type} eq 'image' } @{$b->{entries}}) { +    my @l = grep { $_->{type} eq 'image' && !$_->{keep_verbatim} && !($::isStandalone && $_->{label} eq 'failsafe') } @{$b->{entries}}; + +    foreach (\$b->{perImageAppend}, map { \$_->{append} } @l) {  	my ($simple, $dict) = unpack_append($$_);  	$f->($simple, $dict);  	$$_ = pack_append($simple, $dict);  	log::l("modify_append: $$_");      }  } + +sub get_append_simple { +    my ($b, $key) = @_; +    my ($simple, $_dict) = unpack_append($b->{perImageAppend}); +    member($key, @$simple); +} +sub get_append_with_key { +    my ($b, $key) = @_; +    my ($_simple, $dict) = unpack_append($b->{perImageAppend}); +    my @l = map { $_->[1] } grep { $_->[0] eq $key } @$dict; + +    log::l("more than one $key in $b->{perImageAppend}") if @l > 1; +    $l[0]; +}  sub remove_append_simple {      my ($b, $key) = @_;      modify_append($b, sub { @@ -381,152 +1178,130 @@ sub remove_append_simple {  	@$simple = grep { $_ ne $key } @$simple;      });  } -sub set_append { -    my $has_val = @_ > 2; +sub set_append_with_key {      my ($b, $key, $val) = @_;      modify_append($b, sub { -	my ($simple, $dict) = @_; -	if ($has_val) { -	    @$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 eq '' || $key eq 'mem' && !$val); +	my ($_simple, $dict) = @_; + +	if ($val eq '') { +	    @$dict = grep { $_->[0] ne $key } @$dict;  	} else { -	    @$simple = grep { $_ ne $key } @$simple; -	    push @$simple, $key; +	    push @$dict, [ $key, $val ];  	}      });  } -sub may_append { +sub set_append_simple { +    my ($b, $key) = @_; + +    modify_append($b, sub { +	my ($simple, $_dict) = @_; +	@$simple = uniq(@$simple, $key); +    }); +} +sub may_append_with_key {      my ($b, $key, $val) = @_; -    set_append($b, $key, $val) if !get_append($b, $key); +    set_append_with_key($b, $key, $val) if !get_append_with_key($b, $key); +} + +sub get_append_netprofile { +    my ($e) = @_; +    my ($simple, $dict) = unpack_append($e->{append}); +    my ($p, $dict_) = partition { $_->[0] eq 'PROFILE' } @$dict; +    pack_append($simple, $dict_), $p->[0][1]; +} +sub set_append_netprofile { +    my ($e, $append, $profile) = @_; +    my ($simple, $dict) = unpack_append($append); +    push @$dict, [ 'PROFILE', $profile ] if $profile; +    $e->{append} = pack_append($simple, $dict);  } +=item configure_entry($bootloader, $entry) + +Used when a bootloader $entry has been modified (eg: $entry->{vga}) + +=cut +  sub configure_entry { -    my ($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"; -	    mkinitrd($specific_version, $entry->{initrd}, $entry->{vga}) or undef $entry->{initrd}; -	} +    my ($bootloader, $entry) = @_; +    $entry->{type} eq 'image' or return; + +    if (my $kernel_str = vmlinuz2kernel_str($entry->{kernel_or_dev})) { +	$entry->{initrd} =  +	  mkinitrd($kernel_str->{version}, $bootloader, $entry, +		   $entry->{initrd} || '/boot/' . kernel_str2initrd_short($kernel_str));      } -    $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_before_kernel_remove { +    my ($to_remove_kernel) = @_; +    my @kernels = grep { $_ ne $to_remove_kernel } installed_vmlinuz(); +    map { kernel_str2label($_) => $_ } get_kernel_labels(\@kernels);  } -sub get_kernels_and_labels { -    my ($b_prefer_24) = @_; +sub get_kernels_and_labels() { +    get_kernel_labels([ installed_vmlinuz() ]); +} -    my $dir = "$::prefix/boot"; -    my @l = grep { /^vmlinuz-/ } all($dir); -    my @kernels = grep { ! -l "$dir/$_" } @l; +sub get_kernel_labels { +    my ($kernels) = @_; -    require pkgs; -    @kernels =  -      sort { c::rpmvercmp($b->{version}, $a->{version}) }  -      grep { -d "$::prefix/lib/modules/$_->{complete_version}" } -      map { -	  s/vmlinuz-//; -	  { complete_version => $_, /(.*mdk)-?(.*)/ ? (ext => $2, version => $1) : (version => $_) }; -      } @kernels; - -    if ($b_prefer_24) { -	my ($kernel_24, $other) = partition { $_->{ext} eq '' && $_->{version} =~ /^\Q2.4/ } @kernels; -	@kernels = (@$kernel_24, @$other); +    my @kernels_str =  +      sort { common::cmp_kernel_versions($b->{version_no_ext}, $a->{version_no_ext}) }  +      grep { -d "$::prefix/lib/modules/$_->{version}" } +      map { vmlinuz2kernel_str($_) } @$kernels; + +    my %labels; +    foreach (@kernels_str) { +	if ($labels{$_->{ext}}) { +	    $_->{use_long_name} = 1; +	} else { +	    $labels{$_->{ext}} = 1; +	}      } -    my %labels = ('' => $kernels[0]{complete_version});     -    foreach (@kernels) { -	my @propositions = ( -			    if_($_->{ext}, '-' . $_->{ext}),  -			    '-' . $_->{version} . $_->{ext}, -			   ); -	my $label = find { ! exists $labels{$_} } @propositions; -	$labels{$label} = $_->{complete_version}; -    } -    %labels; +    $kernels_str[0]{ext} = ''; + +    @kernels_str;  } -# sanitize_ver: long function when it could be shorter but we are sure -#		to catch everything and can be readable if we want to -#		add new scheme name. -# DUPLICATED from /usr/share/loader/common.pm -sub sanitize_ver { -    my ($string) = @_; +sub short_ext { +    my ($kernel_str) = @_; -    my ($main_version, undef, $extraversion, $rest) =  -      $string =~ m!(\d+\.\d+\.\d+)(-((?:pre|rc)\d+))?(.*)!; +    my $short_ext = { +	'xen0' => 'xen', +    }->{$kernel_str->{ext}}; -    if (my ($mdkver, $cpu, $nproc, $mem) = $rest =~ m|-(.+)-(.+)-(.+)-(.+)|) { -	$rest = "$cpu$nproc$mem-$mdkver"; -    } +    $short_ext || $kernel_str->{ext}; +} + +sub _sanitize_ver { +    my ($kernel_str) = @_; + +    my $name = $kernel_str->{basename}; +    $name = '' if $name eq 'vmlinuz'; -    my $return = "$main_version$extraversion$rest"; +    my $v = $kernel_str->{version_no_ext}; -    $return =~ s|\.||g; -    $return =~ s|mdk||; -    $return =~ s|64GB|64G|; -    $return =~ s|4GB|4G|; -    $return =~ s|secure|sec|; -    $return =~ s|enterprise|ent|; +    $v =~ s!(md[kv]|mnb)$!!; +    $v =~ s!-0\.(pre|rc)(\d+)\.!$1$2-!; -    return $return; +    my $return = join(' ', grep { $_ } $name, short_ext($kernel_str), $v); + +    $return;  } -sub suggest { -    my ($bootloader, $hds, $fstab, %options) = @_; -    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) = $bootloader->{crushMbr} ? (1, 0) : suggest_onmbr($hds->[0]); -    add2hash_($bootloader, 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 => [], -	 'init-message' => "Welcome to Mandrakelinux!", -	 delay => 30,	#- OpenFirmware delay -	 timeout => 50, -	 enableofboot => 1, -	 enablecdboot => 1, -	 useboot => $boot, -	 xfsroot => $xfsroot, -	} : -	{ -	 bootUnsafe => $unsafe, -	 entries => [], -	 timeout => $onmbr && 10, -	 nowarn => 1, -	   if_(arch() !~ /ia64/, -	 boot => "/dev/" . ($onmbr ? $hds->[0]{device} : fsedit::get_root($fstab, 'boot')->{device}), -	 map => "/boot/map", -         ), -	}); +=item suggest_message_text($bootloader) -    if (!$bootloader->{message} || $bootloader->{message} eq "1") { +Provides a description text for Lilo + +=cut + +sub suggest_message_text { +    my ($bootloader) = @_; + +    if (!$bootloader->{message} && !$bootloader->{message_text}) {  	my $msg_en =  #-PO: these messages will be displayed at boot time in the BIOS, use only ASCII (7bit)  N_("Welcome to the operating system chooser! @@ -536,163 +1311,250 @@ wait 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; -	$bootloader->{message} = $msg; +	#- use the English version if more than 40% of 8bits chars +	#- else, use the translation but force a conversion to ascii +	#- to be sure there won't be undisplayable characters +	if (int(grep { $_ & 0x80 } unpack "c*", $msg) / length($msg) > 0.4) { +	    $msg = $msg_en; +	} else { +	    $msg = Locale::gettext::iconv($msg, "utf-8", "ascii//TRANSLIT"); +	} +	$bootloader->{message_text} = $msg; +    } +} + +sub suggest { +    my ($bootloader, $all_hds, %options) = @_; +    my $fstab = [ fs::get::fstab($all_hds) ]; +    my $root_part = fs::get::root($fstab); +    my $root = isLoopback($root_part) ? '/dev/loop7' : fs::wild_device::from_part('', $root_part); +    my $boot = fs::get::root($fstab, 'boot')->{device}; +    my $mbr; +     +    # If installing onto an USB drive, put the MBR there, else on the first non removable drive +    if ($root_part->{is_removable}) { +        $mbr = fs::get::part2hd($root_part, $all_hds); +    } else { +        $mbr = find { !$_->{is_removable} } allowed_boot_disks($all_hds);      } +    my ($onmbr, $unsafe) = $bootloader->{crushMbr} ? (1, 0) : suggest_onmbr($mbr); +    add2hash_($bootloader, +	{ +	 bootUnsafe => $unsafe, +	 entries => [], +	 timeout => $onmbr && 10, +	 nowarn => 1, +	 boot => "/dev/" . ($onmbr ? $mbr->{device} : $boot), +	 map => "/boot/map", +	 compact => 1, +    	 'large-memory' => 1, +	 color => 'black/cyan yellow/cyan', +	 'menu-scheme' => 'wb:bw:wb:bw' +	}); + +    suggest_message_text($bootloader); +      add2hash_($bootloader, { 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"); -	set_append($bootloader, 'console' => $s); +	set_append_with_key($bootloader, console => $s);  	any::set_login_serial_console($port, $speed);      } -    #- add a restore entry if installation is done from disk, in order to allow redoing it. -    if (my $hd_install_path = any::hdInstallPath()) { -	my ($cmdline, $vga); -	if ($::restore && -e "/tmp/image/boot/vmlinuz" && -e "/tmp/image/boot/all.rdz" && -	    ($cmdline = cat_("/tmp/image/boot/grub/menu.lst") =~ m|kernel \S+/boot/vmlinuz (.*)$|m)) { -	    #- cmdline should'n have any reference to vga=... -	    $cmdline =~ s/vga=(\S+)//g and $vga = $1; -	    log::l("copying kernel and stage1 install to $::prefix/boot/restore"); -	    eval { mkdir "$::prefix/boot/restore"; -		   cp_af("/tmp/image/boot/vmlinuz", "$::prefix/boot/restore/vmlinuz"); -		   cp_af("/tmp/image/boot/all.rdz", "$::prefix/boot/restore/all.rdz") }; -	    unless ($@) { -		log::l("adding a restore bootloader entry on $hd_install_path (remapped to $::prefix/boot/restore)"); -		add_entry($bootloader, { -					type => 'image', -					label => 'restore', -					kernel_or_dev => "/boot/restore/vmlinuz", -					initrd => "/boot/restore/all.rdz", -					append => "$cmdline recovery", #- the restore entry is a recovery entry -					if_($vga, vga => $vga), -				       }); -	    } -	} else { -	    log::l("no restore bootloader need to be used on $hd_install_path"); -	} -    } +    my @kernels = get_kernels_and_labels() or die "no kernel installed"; +    log::l("found kernels: ", join(', ', map { $_->{version} } @kernels)); + +    my %old_kernels = map { vmlinuz2version($_->{kernel_or_dev}) => 1 } @{$bootloader->{entries}}; +    @kernels = grep { !$old_kernels{$_->{version}} } @kernels; -    my %labels = get_kernels_and_labels(); -    $labels{''} or die "no kernel installed"; +    #- remove existing failsafe and linux-nonfb, do not care if the previous one was modified by the user? +    #- but only if we are going to add new ones. +    @{$bootloader->{entries}} = grep { !member($_->{label}, qw(failsafe linux-nonfb)) } @{$bootloader->{entries}} +      if @kernels; -    while (my ($ext, $version) = each %labels) { -	add_kernel($bootloader, $version, $ext, $root, +    foreach my $kernel (@kernels) { +	my $e = add_kernel($bootloader, $kernel,  	       { -		if_($options{vga_fb} && $ext eq '', vga => $options{vga_fb}), #- using framebuffer -		if_($options{vga_fb} && $options{quiet}, append => "splash=silent"), +		root => $root, +		if_($options{vga_fb}, vga => $options{vga_fb}), #- using framebuffer +		if_($options{vga_fb} && $options{splash}, append => "splash noiswmd audit=0"), +		if_($options{quiet}, append => "splash quiet noiswmd audit=0"),  	       }); -	if ($options{vga_fb} && $ext eq '') { -	    add_kernel($bootloader, $version, $ext, $root, { label => 'linux-nonfb' }); +	if ($options{vga_fb} && $e->{label} eq 'linux') { +	    add_kernel($bootloader, $kernel, { root => $root, label => 'linux-nonfb' });  	}      } -    #- remove existing libsafe, don't care if the previous one was modified by the user? -    @{$bootloader->{entries}} = grep { $_->{label} ne 'failsafe' } @{$bootloader->{entries}}; - -    add_kernel($bootloader, $labels{''}, '', $root, -	       { label => 'failsafe', append => 'devfs=nomount 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($bootloader, -			  { -			   type => 'other', -			   kernel_or_dev => $path, -			   label => "sunos"   . ($sunos++ ? $sunos : ''), -			  }) if $path && isSunOS($_) && type2name($_->{type}) =~ /root/i; -	    } -	} -    } elsif (arch() =~ /ppc/) { -	#- if we identified a MacOS partition earlier - add it -	if (defined $partition_table::mac::macos_part) { +    add_kernel($bootloader, $kernels[0], +	       { root => $root, label => 'failsafe', append => 'failsafe noiswmd audit=0' }) +      if @kernels; + +	#- search for dos (or windows) boot partition. Do not look in extended partitions! +	my @windows_boot_parts = +	  grep { $_->{active} +		 && isFat_or_NTFS($_) && member(fs::type::fs_type_from_magic($_), qw(vfat ntfs ntfs-3g)) +		 && !$_->{is_removable} +		 && !isRecovery($_); +	     } +	    map { @{$_->{primary}{normal}} } @{$all_hds->{hds}}; +	each_index {  	    add_entry($bootloader,  		      { -		       label => "macos", -		       kernel_or_dev => $partition_table::mac::macos_part +		       type => 'other', +		       kernel_or_dev => "/dev/$_->{device}", +		       label => 'windows' . ($::i || ''), +		       table => "/dev/$_->{rootDevice}", +		       makeactive => 1,  		      }); -	} -    } elsif (arch() !~ /ia64/) { -	#- search for dos (or windows) boot partition. Don't look in extended partitions! -	my %nbs; -	foreach (@$hds) { -	    foreach (@{$_->{primary}{normal}}) { -		isFat_or_NTFS($_) or next; -		my $from_magic = { type => fsedit::typeOfPart($_->{device}) }; -		isFat_or_NTFS($from_magic) or next; -		my $label = 'windows'; -		add_entry($bootloader, -			  { -			   type => 'other', -			   kernel_or_dev => "/dev/$_->{device}", -			   label => $label . ($nbs{$label}++ ? $nbs{$label} : ''), -			     $_->{device} =~ /[1-4]$/ ? ( -			   table => "/dev/$_->{rootDevice}" -			     ) : ( -			   unsafe => 1 -                             ), -			  }) -	    } -	} -    } +	} @windows_boot_parts; -    my @preferred = map { "linux-$_" } 'p3-smp-64GB', 'secure', 'enterprise', 'smp', 'i686-up-4GB'; +    my @preferred = map { "linux-$_" } 'server';      if (my $preferred = find { get_label($_, $bootloader) } @preferred) {  	$bootloader->{default} ||= $preferred;      }      $bootloader->{default} ||= "linux"; -    $bootloader->{method} ||= first(method_choices($fstab, $bootloader)); +    $bootloader->{method} ||= first(method_choices($all_hds, 1), # best installed +				    method_choices($all_hds, 0)); # or best if no valid one is installed + +    $bootloader->{perImageAppend} = $bootloader->{entries}[0]{append}; +    $bootloader->{default_vga} = $options{vga_fb}; + +    if (main_method($bootloader->{method}) eq 'grub') { +	my %processed_entries = {}; +	foreach my $c (find_other_distros_grub_conf($fstab)) {	     +	    my %h = ( +		     type => 'grub_configfile', +		     label => $c->{name}, +		     kernel_or_dev => "/dev/$c->{bootpart}{device}", +		     configfile => $c->{grub_conf}, +		    ); +	    if ($c->{root}) { +		my $key = "$c->{name} - $c->{linux} - $c->{initrd}"; +		next if $processed_entries{$key}; +		$processed_entries{$key} = 1; +		add_entry($bootloader, { +		    %h, +		    linux => $c->{linux}, +		    initrd => $c->{initrd}, +		    root => $c->{root}, +		}); +	    } else { +		add_entry($bootloader, \%h); +	    } +	} +    }  } -sub detect_bootloader() { -    chomp_(run_program::rooted_get_stdout($::prefix, 'detectloader')); +sub detect_main_method { +    my ($all_hds) = @_; +    my $bootloader = &read($all_hds); +    $bootloader && main_method($bootloader->{method});  } -sub method_choices { -    my ($fstab, $bootloader) = @_; -    my %choices = ( -	if_(arch() =~ /sparc/, -	    'silo' => N("SILO"), -        ), if_(arch() !~ /sparc|ppc/ && !isLoopback(fsedit::get_root($fstab)) && whereis_binary('lilo'), -	    if_(!detect_devices::matching_desc('ProSavageDDR'), 'lilo-graphic' => N("LILO with graphical menu")), -	    'lilo-menu'    => N("LILO with text menu"), -	), if_(arch() !~ /sparc|ppc/ && !isRAID(fsedit::get_root($fstab)) && whereis_binary('grub'), -	    'grub' => N("Grub"), -        ), if_(arch() =~ /ppc/, -	    'yaboot' => N("Yaboot"), -        ), +sub main_method { +    my ($method) = @_; +    $method =~ /(\w+)/ && $1; +} + +sub config_files() { +    my %files = ( +	lilo => '/etc/lilo.conf', +	grub => '/boot/grub/menu.lst', +	grub_install => '/boot/grub/install.sh', +	uboot => '/boot/extlinux/extlinux.conf',      ); -    my $prefered; -    $prefered ||= 'grub' if $::isStandalone && detect_bootloader() =~ /GRUB/; -    $prefered ||= 'lilo-' . (member($bootloader->{install}, 'text', 'menu', 'graphic') ? $bootloader->{install} : 'graphic'); -    my $default = exists $choices{$prefered} ? $prefered : first(keys %choices); +     +    map_each {  +	my $content = cat_("$::prefix/$::b"); +	{ main_method => main_method($::a), name => $::a, file => $::b, content => $content }; +    } %files; +} -    $default, \%choices; +sub method2text { +    my ($method) = @_; +    +{ +	'lilo-menu'    => N("LILO with text menu"), +	'grub2-graphic' => N("GRUB2 with graphical menu"), +	'grub2'        => N("GRUB2 with text menu"), +	'grub-graphic' => N("GRUB with graphical menu"), +	'grub-menu'    => N("GRUB with text menu"), +	'refind'       => N("rEFInd with graphical menu"), +	'uboot'        => N("U-Boot/Extlinux with text menu"), +    }->{$method};  } -sub suggest_floppy { -    my ($bootloader) = @_; -    my $floppy = detect_devices::floppy() or return; -    $floppy eq 'fd0' or log::l("suggest_floppy: not adding $floppy"), return; +=item method_choices_raw($b_prefix_mounted) + +Returns list of bootloaders. + +method_choices_raw(1) will return the list of installed boot loaders. + +method_choices_raw(0) will return the list of all boot loaders supported by drakboot. + +Returns: ("grub2", "grub2-graphic") -    add_entry($bootloader, -      { -       type => 'other', -       kernel_or_dev => '/dev/fd0', -       label => 'floppy', -       unsafe => 1 -      }); +=cut + +sub method_choices_raw { +    my ($b_prefix_mounted) = @_; +    detect_devices::is_xbox() ? 'cromwell' : +    arch() =~ /arm/ ? 'uboot' : +       if_(!$b_prefix_mounted || whereis_binary('grub2-reboot', $::prefix),  +	   'grub2-graphic', 'grub2'), +      if_(uefi_type() eq kernel_uefi_type() && (!$b_prefix_mounted || whereis_binary('refind-install', $::prefix)), 'refind'), +      # only grub2 & rEFInd work on UEFI: +      # lilo & grub-legacy do not suppport new ext4/xfs format and are unmainted so only allow them on upgrade: +      if_(!is_uefi() && !($::isInstall && !$::o->{isUpgrade} || $::isLiveInstall), ( +       if_(!$b_prefix_mounted || whereis_binary('grub', $::prefix) && -f "$::prefix/boot/grub/install.sh",  +	   'grub-graphic', 'grub-menu'), +       if_(!$b_prefix_mounted || whereis_binary('lilo', $::prefix) && -f "$::prefix/etc/lilo.conf",  +	   'lilo-menu'), +      ));  } +=item method_choices($all_hds, $b_prefix_mounted) + +Returns list of supported bootloaders according to what is detected. + +Like method_choices_raw(), the $b_prefix_mounted parameter enables to return the list of either installed supported methods or the list of all supported boot loaders. + +=cut + +sub method_choices { +    my ($all_hds, $b_prefix_mounted) = @_; +    my $fstab = [ fs::get::fstab($all_hds) ]; +    my $root_part = fs::get::root($fstab); +    my $boot_part = fs::get::root($fstab, 'boot'); +    my $have_dmraid = find { fs::type::is_dmraid($_) } @{$all_hds->{hds}}; + +    grep { +	!(/lilo/ && (isLoopback($root_part) || $have_dmraid))			# LILO doesn't work fake raid +	  && (/grub2/ || $boot_part->{fs_type} ne 'btrfs')			# Only grub2 works on btrfs +	  && !(/grub2?-graphic/ && cat_("/proc/cmdline") =~ /console=ttyS/);	# No Gfx mode on console +    } method_choices_raw($b_prefix_mounted); +} + +=item main_method_choices($b_prefix_mounted) + +Returns list of supported bootloaders, not distinging text/gfx mode. + +Like method_choices_raw(), the $b_prefix_mounted parameter enables to return the list of either installed supported methods or the list of all supported boot loaders. + +=cut + +sub main_method_choices { +    my ($b_prefix_mounted) = @_; +    uniq(map { main_method($_) } method_choices_raw($b_prefix_mounted)); +} +sub configured_main_methods() { +    my @bad_main_methods = map { if_(!$_->{content}, $_->{main_method}) } config_files(); +    difference2([ main_method_choices(1) ], \@bad_main_methods); +} + +# for lilo  sub keytable {      my ($f) = @_;      $f or return; @@ -705,524 +1567,1288 @@ sub keytable {      -r "$::prefix/$f" && $f;  } -sub has_profiles { my ($b) = @_; to_bool(get_label("office", $b)) } -sub set_profiles { -    my ($b, $want_profiles) = @_; - -    my $office = get_label("office", $b); -    if ($want_profiles xor $office) { -	my $e = get_label("linux", $b); -	if ($want_profiles) { -	    push @{$b->{entries}}, { %$e, label => "office", append => "$e->{append} prof=Office" }; -	    $e->{append} .= " prof=Home"; -	} else { -	    # remove profiles -	    $e->{append} =~ s/\s*prof=\w+//; -	    @{$b->{entries}} = grep { $_ != $office } @{$b->{entries}}; -	} -    } - +sub check_enough_space() { +    my $e = "$::prefix/boot/.enough_space"; +    output $e, 1; -s $e or die N("not enough room in /boot"); +    unlink $e;  } -sub get_of_dev { -    my ($unix_dev) = @_; -    my $of_dev; -    run_program::rooted_or_die($::prefix, "/usr/sbin/ofpath", ">", \$of_dev, $unix_dev); -    chomp($of_dev); -    log::l("OF Device: $of_dev"); -    $of_dev; +sub install_uboot {  +    my ($bootloader, $all_hds) = @_; +    write_uboot($bootloader, $all_hds); +    when_config_changed_uboot($bootloader);  } -sub install_yaboot { -    my ($bootloader, $_fstab, $_hds) = @_; -    $bootloader->{prompt} = $bootloader->{timeout}; +sub _write_extlinux_conf { +    my ($bootloader, $_all_hds) = @_; -    if ($bootloader->{message}) { -	eval { output("$::prefix/boot/message", $bootloader->{message}) } -	  or $bootloader->{message} = 0; +    if (!get_label($bootloader->{default}, $bootloader)) { +	log::l("default bootloader entry $bootloader->{default} is invalid, choosing another one"); +	$bootloader->{default} = $bootloader->{entries}[0]{label};      } -    { -        local $\ = "\n"; -	my $f = "$::prefix/etc/yaboot.conf"; -	local *F; -	open F, ">$f" or die "cannot create yaboot config file: $f"; -	log::l("writing yaboot config to $f"); - -	print F "#yaboot.conf - generated by DrakX"; -	print F qq(init-message="\\n$bootloader->{'init-message'}\\n") if $bootloader->{'init-message'}; - -	if ($bootloader->{boot}) { -	    print F "boot=$bootloader->{boot}"; -	    my $of_dev = get_of_dev($bootloader->{boot}); -	    print F "ofboot=$of_dev"; -	} else { -	    die "no bootstrap partition defined." -	} -	 -	$bootloader->{$_} and print F "$_=$bootloader->{$_}" foreach qw(delay timeout); -	print F "install=/usr/lib/yaboot/yaboot"; -	print F "magicboot=/usr/lib/yaboot/ofboot"; -	$bootloader->{$_} and print F $_ foreach qw(enablecdboot enableofboot); -	$bootloader->{$_} and print F "$_=$bootloader->{$_}" foreach qw(defaultos default); -	#- print F "nonvram"; -	my $boot = "/dev/" . $bootloader->{useboot} if $bootloader->{useboot}; -		 -	foreach (@{$bootloader->{entries}}) { -	    if ($_->{type} eq "image") { -		my $of_dev = ''; -		if ($boot !~ /$_->{root}/ && $boot) { -		    $of_dev = get_of_dev($boot); -		    print F "$_->{type}=$of_dev," . substr($_->{kernel_or_dev}, 5); -		} else { -		    $of_dev = get_of_dev($_->{root});    			 -		    print F "$_->{type}=$of_dev,$_->{kernel_or_dev}"; -		} -		print F "\tlabel=", make_label_lilo_compatible($_->{label}); -		print F "\troot=$_->{root}"; -		if ($boot !~ /$_->{root}/ && $boot) { -		    print F "\tinitrd=$of_dev," . substr($_->{initrd}, 5) if $_->{initrd}; -		} else { -		    print F "\tinitrd=$of_dev,$_->{initrd}" if $_->{initrd}; -		} -		#- xfs module on PPC requires larger initrd - say 6MB? -		print F "\tinitrd-size=6144" if $bootloader->{xfsroot}; -		print F qq(\tappend=" $_->{append}") if $_->{append}; -		print F "\tread-write" if $_->{'read-write'}; -		print F "\tread-only" if !$_->{'read-write'}; -	    } else { -		my $of_dev = get_of_dev($_->{kernel_or_dev}); -		print F "$_->{label}=$of_dev";		 -	    } -	} +    my @conf; +    push @conf, "# File generated by DrakX/drakboot"; +    push @conf, "default " . $bootloader->{default} if $bootloader->{default}; +    push @conf, "timeout " . $bootloader->{timeout} if $bootloader->{timeout}; +    # needed to show boot menu and enable timeout +    push @conf, "menu title Boot Menu" if $bootloader->{timeout}; + +    foreach my $entry (@{$bootloader->{entries}}) { +	push @conf, "\nlabel $entry->{label}"; +	push @conf, "  kernel $entry->{kernel_or_dev}"; +	push @conf, "  initrd $entry->{initrd}" if $entry->{initrd}; +	push @conf, "  fdtdir $entry->{fdtdir}" if $entry->{fdtdir}; + +	my @append; +	push @append, "root=" . $entry->{root} if $entry->{root}; +	push @append, $entry->{append} if $entry->{append}; +	push @conf, "  append " . join(' ', @append) if @append;      } -    log::l("Installing boot loader..."); -    my $f = "$::prefix/tmp/of_boot_dev"; -    my $of_dev = get_of_dev($bootloader->{boot}); -    output($f, "$of_dev\n");   -    $::testing and return; -    if (defined $install_steps_interactive::new_bootstrap) { -	run_program::run("hformat", $bootloader->{boot}) or die "hformat failed"; -    }	 -    my $error; -    run_program::rooted($::prefix, "/usr/sbin/ybin", "2>", \$error) or die "ybin failed: $error"; + +    my $f = "$::prefix/boot/extlinux/extlinux.conf"; +    log::l("writing extlinux config to $f"); +    renamef($f, $f . '.old'); +    output_with_perm($f, 0600, map { "$_\n" } @conf);  } -sub install_silo { -    my ($silo, $fstab) = @_; -    my $boot = fsedit::get_root($fstab, 'boot')->{device}; -    my ($wd, $_num) = $boot =~ /^(.*\D)(\d*)$/; +sub write_uboot { +    my ($bootloader, $all_hds) = @_; +    _write_extlinux_conf($bootloader, $all_hds); +} -    #- setup boot promvars for. -    require c; -    if ($boot =~ /^md/) { -	#- get all mbr devices according to /boot are listed, -	#- then join all zero based partition translated to prom with ';'. -	#- keep bootdev with the first of above. -	log::l("/boot is present on raid partition which is not currently supported for promvars"); -    } else { -	if (!$silo->{use_partition}) { -	    foreach (@$fstab) { -		if (!$_->{start} && $_->{device} =~ /$wd/) { -		    $boot = $_->{device}; -		    log::l("found a zero based partition in $wd as $boot"); -		    last; -		} -	    } -	} -	$silo->{bootalias} = c::disk2PromPath($boot); -	$silo->{bootdev} = $silo->{bootalias}; -        log::l("preparing promvars for device=$boot"); -    } -    c::hasAliases() or log::l("clearing promvars alias as non supported"), $silo->{bootalias} = ''; +sub when_config_changed_uboot { +    my ($_bootloader) = @_; +    #- do not do anything +} -    if ($silo->{message}) { -	eval { output("$::prefix/boot/message", $silo->{message}) } or $silo->{message} = 0; -    } -    { -        local $\ = "\n"; -	my $f = "$::prefix/boot/silo.conf"; #- always write the silo.conf file in /boot ... -	symlinkf "../boot/silo.conf", "$::prefix/etc/silo.conf"; #- ... and make a symlink from /etc. -	local *F; -	open F, ">$f" or die "cannot create silo config file: $f"; -	log::l("writing silo config to $f"); - -	$silo->{$_} and print F "$_=$silo->{$_}" foreach qw(partition root default append); -	$silo->{$_} and print F $_ foreach qw(restricted); -	print F "password=", $silo->{password} if $silo->{restricted} && $silo->{password}; #- also done by msec -	print F "timeout=", round(10 * $silo->{timeout}) if $silo->{timeout}; -	print F "message=$silo->{boot}/message" if $silo->{message}; - -	foreach (@{$silo->{entries}}) { #-my ($v, $e) = each %{$silo->{entries}}) { -	    my $type = "$_->{type}=$_->{kernel_or_dev}"; $type =~ s|/boot|$silo->{boot}|; -	    print F $type; -	    print F "\tlabel=$_->{label}"; - -	    if ($_->{type} eq "image") { -		my $initrd = $_->{initrd}; $initrd =~ s|/boot|$silo->{boot}|; -		print F "\tpartition=$_->{partition}" if $_->{partition}; -		print F "\troot=$_->{root}" if $_->{root}; -		print F "\tinitrd=$initrd" if $_->{initrd}; -		print F qq(\tappend="$1") if $_->{append} =~ /^\s*"?(.*?)"?\s*$/; -		print F "\tread-write" if $_->{'read-write'}; -		print F "\tread-only" if !$_->{'read-write'}; -	    } -	} -    } -    log::l("Installing boot loader..."); -    if (!$::testing) { -	my $error; -	run_program::rooted($::prefix, "silo", \$error, if_($silo->{use_partition}, "-t")) or  -	  run_program::rooted($::prefix, "silo", "-p", "2", if_($silo->{use_partition}, "-t")) or -	    die "silo failed: $error"; +sub install_cromwell {  +    my ($_bootloader, $_all_hds) = @_; +    log::l("XBox/Cromwell - nothing to install..."); +} +sub write_cromwell {  +    my ($_bootloader, $_all_hds) = @_; +    log::l("XBox/Cromwell - nothing to write..."); +} +sub when_config_changed_cromwell { +    my ($_bootloader) = @_; +    #- do not do anything +} -	#- try writing in the prom. -	log::l("setting promvars alias=$silo->{bootalias} bootdev=$silo->{bootdev}"); -	require c; -	c::setPromVars($silo->{bootalias}, $silo->{bootdev}); -    } +sub simplify_label { +    my ($label) = @_; + +    length($label) < 31 or $label =~ s/\.//g; + +    $label = substr($label, 0, 31); #- lilo does not handle more than 31 char long labels +    $label =~ s/ /_/g; #- lilo does not support blank character in image names, labels or aliases +    $label;  }  sub make_label_lilo_compatible { -    my ($label) = @_;  -    $label = substr($label, 0, 31); #- lilo doesn't handle more than 31 char long labels -    $label =~ s/ /_/g; #- lilo does not support blank character in image names, labels or aliases -    qq("$label"); +    my ($label) = @_; +    '"' . simplify_label($label) . '"';  } -sub write_lilo_conf { -    my ($bootloader, $fstab, $hds) = @_; -    $bootloader->{prompt} = $bootloader->{timeout}; +sub write_lilo { +    my ($bootloader, $all_hds, $o_backup_extension) = @_; +    $bootloader->{prompt} ||= $bootloader->{timeout};      my $file2fullname = sub {  	my ($file) = @_;  	if (arch() =~ /ia64/) { -	    (my $part, $file) = fsedit::file2part($fstab, $file); +	    my $fstab = [ fs::get::fstab($all_hds) ]; +	    (my $part, $file) = fs::get::file2part($fstab, $file);  	    my %hds = map_index { $_ => "hd$::i" } map { $_->{device} }  -	      sort { isFat($b) <=> isFat($a) || $a->{device} cmp $b->{device} } fsedit::get_fstab(@$hds); +	      sort {  +		  my ($a_is_fat, $b_is_fat) = ($a->{fs_type} eq 'vfat', $b->{fs_type} eq 'vfat'); +		  $a_is_fat <=> $b_is_fat || $a->{device} cmp $b->{device}; +	      } @$fstab;  	    $hds{$part->{device}} . ":" . $file;  	} else { -	    $file +	    $file;  	}      }; -    my %bios2dev = map_index { $::i => $_ } dev2bios($hds, $bootloader->{first_hd_device} || $bootloader->{boot}); -    my %dev2bios = reverse %bios2dev; +    my $quotes = sub { +	my ($s) = @_; +	$s =~ s/"/\\"/g; +	qq("$s"); +    }; -    if (is_empty_hash_ref($bootloader->{bios} ||= {})) { -	my $dev = $hds->[0]{device}; -	if ($dev2bios{$dev}) { -	    log::l("Since we're booting on $bios2dev{0}, make it bios=0x80, whereas $dev is now " . (0x80 + $dev2bios{$dev})); -	    $bootloader->{bios}{"/dev/$bios2dev{0}"} = '0x80'; -	    $bootloader->{bios}{"/dev/$dev"} = sprintf("0x%x", 0x80 + $dev2bios{$dev}); -	} -	foreach (0 .. 3) { -	    my ($letter) = $bios2dev{$_} =~ /hd([^ac])/; #- at least hda and hdc are handled correctly :-/ -	    next if $bootloader->{bios}{"/dev/$bios2dev{$_}"} || !$letter; -	    next if  -	      $_ > 0	     #- always print if first disk is hdb, hdd, hde... -		&& $bios2dev{$_ - 1} eq "hd" . chr(ord($letter) - 1); -	    #- no need to help lilo with hdb (resp. hdd, hdf...) -	    log::l("Helping lilo: $bios2dev{$_} must be " . (0x80 + $_)); -	    $bootloader->{bios}{"/dev/$bios2dev{$_}"} = sprintf("0x%x", 0x80 + $_); -	} +    my $quotes_if_needed = sub { +	my ($s) = @_; +	$s =~ /["=\s]/ ? $quotes->($s) : $s; +    }; +     + +    my @sorted_hds = sort_hds_according_to_bios($bootloader, $all_hds); + +    if (is_empty_hash_ref($bootloader->{bios} ||= {}) && $all_hds->{hds}[0] != $sorted_hds[0]) { +	log::l("Since we're booting on $sorted_hds[0]{device}, make it bios=0x80"); +	$bootloader->{bios} = { "/dev/$sorted_hds[0]{device}" => '0x80' };      } -    { -        local $\ = "\n"; -	my $f = arch() =~ /ia64/ ? "$::prefix/boot/efi/elilo.conf" : "$::prefix/etc/lilo.conf"; +    my @conf; -	open(my $F, ">$f") or die "cannot create lilo config file: $f"; -	log::l("writing lilo config to $f"); +    #- normalize: RESTRICTED and MANDATORY are only valid if PASSWORD is set +    if ($bootloader->{password}) { +	# lilo defaults to mandatory, use restricted by default to have  +	# the same behaviour as with grub +	$bootloader->{restricted} = 1; +    } else { +        delete $bootloader->{mandatory} if !$bootloader->{password}; +        delete $bootloader->{restricted} if !$bootloader->{password}; +    } +    foreach my $entry (@{$bootloader->{entries}}) { +	delete $entry->{mandatory} if !$entry->{password} && !$bootloader->{password}; +	delete $entry->{restricted} if !$entry->{password} && !$bootloader->{password}; +    } +    if (get_append_with_key($bootloader, 'console') =~ /ttyS(.*)/) { +	$bootloader->{serial} ||= $1; +    } + +    if (!get_label($bootloader->{default}, $bootloader)) { +	log::l("default bootloader entry $bootloader->{default} is invalid, choosing another one"); +	$bootloader->{default} = $bootloader->{entries}[0]{label}; +    } +    push @conf, "# File generated by DrakX/drakboot"; +    push @conf, "# WARNING: do not forget to run lilo after modifying this file\n"; +    push @conf, "default=" . make_label_lilo_compatible($bootloader->{default}) if $bootloader->{default}; +    push @conf, map { $_ . '=' . $quotes_if_needed->($bootloader->{$_}) } grep { $bootloader->{$_} } qw(boot root map install serial vga keytable raid-extra-boot menu-scheme vmdefault); +    push @conf, grep { $bootloader->{$_} } qw(linear geometric compact prompt mandatory nowarn restricted static-bios-codes large-memory); +    push @conf, "append=" . $quotes->($bootloader->{append}) if $bootloader->{append}; +    push @conf, "password=" . $bootloader->{password} if $bootloader->{password}; #- also done by msec +    push @conf, "timeout=" . round(10 * $bootloader->{timeout}) if $bootloader->{timeout}; +     +    push @conf, "message=$bootloader->{message}" if $bootloader->{message}; -	chmod 0600, $f if $bootloader->{password}; +    push @conf, "ignore-table" if any { $_->{unsafe} && $_->{table} } @{$bootloader->{entries}}; -	#- normalize: RESTRICTED is only valid if PASSWORD is set -	delete $bootloader->{restricted} if !$bootloader->{password}; +    push @conf, map_each { "disk=$::a bios=$::b" } %{$bootloader->{bios}}; -	if (every { $_->{label} ne $bootloader->{default} } @{$bootloader->{entries}}) { -	    log::l("default bootloader entry $bootloader->{default} is invalid, choose another one"); -	    $bootloader->{default} = $bootloader->{entries}[0]{label}; +    foreach my $entry (@{$bootloader->{entries}}) { +	my $mbootpack_file = get_mbootpack_filename($entry); +        if ($mbootpack_file && !build_mbootpack($entry)) { +	    warn "mbootpack is required for xen but unavailable, skipping\n"; +	    next;  	} -	local $bootloader->{default} = make_label_lilo_compatible($bootloader->{default}); -	print $F "# File generated by DrakX/drakboot"; -	print $F "# WARNING: do not forget to run lilo after modifying this file\n"; -	$bootloader->{$_} and print $F "$_=$bootloader->{$_}" foreach qw(boot map install vga default keytable); -	$bootloader->{$_} and print $F $_ foreach qw(linear geometric compact prompt nowarn restricted); -	print $F qq(append="$bootloader->{append}") if $bootloader->{append}; - 	print $F "password=", $bootloader->{password} if $bootloader->{password}; #- also done by msec -	print $F "timeout=", round(10 * $bootloader->{timeout}) if $bootloader->{timeout}; -	print $F "serial=", $1 if get_append($bootloader, 'console') =~ /ttyS(.*)/; - -	print $F "message=/boot/message" if arch() !~ /ia64/; -	print $F "menu-scheme=wb:bw:wb:bw" if arch() !~ /ia64/; - -	print $F "ignore-table" if any { $_->{unsafe} && $_->{table} } @{$bootloader->{entries}}; - -	while (my ($dev, $bios) = each %{$bootloader->{bios}}) { -	    print $F "disk=$dev bios=$bios"; +	if ($entry->{type} eq 'grub_configfile') { +	    next;  	} -	foreach (@{$bootloader->{entries}}) { -	    print $F "$_->{type}=", $file2fullname->($_->{kernel_or_dev}); -	    print $F "\tlabel=", make_label_lilo_compatible($_->{label}); - -	    if ($_->{type} eq "image") {		 -		print $F "\troot=$_->{root}" if $_->{root}; -		print $F "\tinitrd=", $file2fullname->($_->{initrd}) if $_->{initrd}; -		print $F qq(\tappend="$_->{append}") if $_->{append}; -		print $F "\tvga=$_->{vga}" if $_->{vga}; -		print $F "\tread-write" if $_->{'read-write'}; -		print $F "\tread-only" if !$_->{'read-write'}; -	    } else { -		print $F "\ttable=$_->{table}" if $_->{table}; -		print $F "\tunsafe" if $_->{unsafe} && !$_->{table}; +	push @conf, "$entry->{type}=" . $file2fullname->($mbootpack_file || $entry->{kernel_or_dev}); +	my @entry_conf; +	push @entry_conf, "label=" . make_label_lilo_compatible($entry->{label}) if $entry->{label}; + +	if ($entry->{type} eq "image") {		 +	    push @entry_conf, 'root=' . $quotes_if_needed->($entry->{root}) if $entry->{root} && !$entry->{xen}; +	    push @entry_conf, "initrd=" . $file2fullname->($entry->{initrd}) if $entry->{initrd} && !$mbootpack_file; +	    my $append = join(' ', if_($entry->{xen_append}, $entry->{xen_append}), +	                           if_($entry->{xen}, '--', 'root=' . $entry->{root}), +	                           if_($entry->{append}, $entry->{append})); +	    push @entry_conf, "append=" . $quotes->($append) if $append; +	    push @entry_conf, "vga=$entry->{vga}" if $entry->{vga}; +	    push @entry_conf, grep { $entry->{$_} } qw(read-write read-only optional); +	    push @entry_conf, "mandatory" if $entry->{lock}; +	} else { +	    delete $entry->{unsafe} if $entry->{table}; #- we can't have both +	    push @entry_conf, map { "$_=$entry->{$_}" } grep { $entry->{$_} } qw(table boot-as); +	    push @entry_conf, grep { $entry->{$_} } qw(unsafe master-boot); -		if (my ($dev) = $_->{table} =~ m|/dev/(.*)|) { -		    if ($dev2bios{$dev}) { -			#- boot off the nth drive, so reverse the BIOS maps -			my $nb = sprintf("0x%x", 0x80 + $dev2bios{$dev}); -			$_->{mapdrive} ||= { '0x80' => $nb, $nb => '0x80' };  -		    } -		} -		while (my ($from, $to) = each %{$_->{mapdrive} || {}}) { -		    print $F "\tmap-drive=$from"; -		    print $F "\t   to=$to"; +	    if ($entry->{table}) { +		#- hum, things like table=c: are needed for some os2 cases, +		#- in that case $hd below is undef +		my $hd = fs::get::device2part($entry->{table}, $all_hds->{hds}); +		if ($hd && $hd != $sorted_hds[0]) {		        +		    #- boot off the nth drive, so reverse the BIOS maps +		    my $nb = sprintf("0x%x", 0x80 + (find_index { $hd == $_ } @sorted_hds)); +		    $entry->{mapdrive} ||= { '0x80' => $nb, $nb => '0x80' };   		}  	    } +	    if ($entry->{mapdrive}) { +		push @entry_conf, map_each { "map-drive=$::a", "   to=$::b" } %{$entry->{mapdrive}}; +	    }  	} +	push @entry_conf, "password=$entry->{password}" if $entry->{password}; +	push @entry_conf, grep { $entry->{$_} } qw(mandatory vmwarn vmdisable); + +	push @conf, map { "\t$_" } @entry_conf;      } +    my $f = arch() =~ /ia64/ ? "$::prefix/boot/efi/elilo.conf" : "$::prefix/etc/lilo.conf"; + +    log::l("writing lilo config to $f"); +    renamef($f, $f . ($o_backup_extension || '.old')); +    output_with_perm($f, $bootloader->{password} ? 0600 : 0644, map { "$_\n" } @conf);  }  sub install_lilo { -    my ($bootloader, $fstab, $hds, $method) = @_; +    my ($bootloader, $all_hds) = @_; -    if (my ($install) = $method =~ /lilo-(text|menu)/) { +    if (my ($install) = $bootloader->{method} =~ /lilo-(text|menu)/) {  	$bootloader->{install} = $install;      } else {  	delete $bootloader->{install};      } -    output("$::prefix/boot/message-text", $bootloader->{message}) if $bootloader->{message}; -    symlinkf "message-" . ($method ne 'lilo-graphic' ? 'text' : 'graphic'), "$::prefix/boot/message"; +    if ($bootloader->{message_text}) { +	output("$::prefix/boot/message-text", $bootloader->{message_text}); +    } +    my $message = "message-text"; +    if (-r "$::prefix/boot/$message") { +	symlinkf $message, "$::prefix/boot/message"; +	$bootloader->{message} = '/boot/message'; +    } + +    #- ensure message does not contain the old graphic format +    if ($bootloader->{message} && -s "$::prefix$bootloader->{message}" > 65_000) { +	output("$::prefix$bootloader->{message}", ''); +    } -    write_lilo_conf($bootloader, $fstab, $hds); +    write_lilo($bootloader, $all_hds); -    if (!$::testing && arch() !~ /ia64/ && $bootloader->{method} =~ /lilo/) { +    when_config_changed_lilo($bootloader); + +    configure_kdm_BootManager('Lilo'); +} + +sub install_raw_lilo { +    my ($o_force_answer) = @_; + +    my $error; +    my $answer = $o_force_answer || ''; +    run_program::rooted($::prefix, "echo $answer | lilo", '2>', \$error) or die "lilo failed: $error"; +} + +sub when_config_changed_lilo { +    my ($bootloader) = @_; +    if (!$::testing && $bootloader->{method} =~ /lilo/) {  	log::l("Installing boot loader on $bootloader->{boot}..."); -	my $error; -	run_program::rooted($::prefix, "lilo", "2>", \$error) or die "lilo failed: $error"; +	install_raw_lilo($bootloader->{force_lilo_answer});      }  } -sub dev2bios { -    my ($hds, $where) = @_; -    $where =~ s|/dev/||; -    my @dev = map { $_->{device} } @$hds; -    member($where, @dev) or ($where) = @dev; #- if not on mbr,  +#- NB: ide is lower than scsi, this is important for sort_hds_according_to_bios() +sub hd2bios_kind { +    my ($hd) = @_; +    lc(join('_', $hd->{bus}, $hd->{host})); +} -    s/h(d[e-g])/x$1/ foreach $where, @dev; #- emulates ultra66 as xd_ +sub ensafe_first_bios_drive { +    my ($hds) = @_; +    mixed_kind_of_disks($hds) || @$hds > 1 && _not_first_bios_drive($hds->[0]); +} +sub mixed_kind_of_disks { +    my ($hds) = @_; +    (uniq_ { hd2bios_kind($_) } @$hds) > 1; +} +sub _not_first_bios_drive { +    my ($hd) = @_; +    my $bios = $hd && $hd->{bios_from_edd}; +    $bios && $bios ne '80'; +} -    my $start = substr($where, 0, 2); +sub sort_hds_according_to_bios { +    my ($bootloader, $all_hds) = @_; +    my $boot_hd = fs::get::device2part($bootloader->{first_hd_device} || $bootloader->{boot}, $all_hds->{hds}); #- $boot_hd is undefined when installing on floppy +    my $boot_kind = $boot_hd && hd2bios_kind($boot_hd);      my $translate = sub { -	my ($dev) = @_; -	$dev eq $where ? "aaa" : #- if exact match, value it first -	  $dev =~ /^$start(.*)/ ? "ad$1" : #- if same class (ide/scsi/ultra66), value it before other classes -	  $dev; +	my ($hd) = @_; +	my $kind = hd2bios_kind($hd); +	$boot_hd ? ($hd == $boot_hd ? 0 : $kind eq $boot_kind ? 1 : 2) . "_$kind" : $kind;      }; -    @dev = map { $_->[0] } -           sort { $a->[1] cmp $b->[1] } -	   map { [ $_, $translate->($_) ] } @dev; - -    s/x(d.)/h$1/ foreach @dev; #- switch back; +    sort { $translate->($a) cmp $translate->($b) } @{$all_hds->{hds}}; +} -    @dev; +sub device_string2grub { +    my ($dev, $legacy_floppies, $sorted_hds) = @_; +    if (my $device = fs::get::device2part($dev, [ @$sorted_hds, fs::get::hds_fstab(@$sorted_hds) ])) { +	device2grub($device, $sorted_hds); +    } elsif (my $floppy = fs::get::device2part($dev, $legacy_floppies)) { +	my $bios = find_index { $floppy eq $_ } @$legacy_floppies; +	"(fd$bios)"; +    } else { +	internal_error("unknown device $dev"); +    }  } +sub device2grub { +    my ($device, $sorted_hds) = @_; -sub dev2grub { -    my ($dev, $dev2bios) = @_; -    $dev =~ m|^(/dev/)?(...)(.*)$| or die "dev2grub (bad device $dev), caller is " . join(":", caller()); -    my $grub = $dev2bios->{$2} or die "dev2grub ($2)"; -    "($grub" . ($3 && "," . ($3 - 1)) . ")"; +    if (isRAID($device) && $device->{level} == 1) { +	#- we can take any disk +	$device = $device->{disks}[0]; +    } +    my ($hd, $part_nb) =  +      $device->{rootDevice} ? +	(fs::get::device2part($device->{rootDevice}, $sorted_hds), $device->{device} =~ /(\d+)$/) : +	$device; +    my $bios = eval { find_index { $hd eq $_ } @$sorted_hds }; +    if (defined $bios) { +	my $part_string = defined $part_nb ? ',' . ($part_nb - 1) : '';     +	"(hd$bios$part_string)"; +    } else { +	undef; +    }  }  sub read_grub_device_map() { -    my %grub2dev = map { m!\((.*)\) /dev/(.*)$! } cat_("$::prefix/boot/grub/device.map"); +    my %grub2dev = map { m!\((.*)\)\s+/dev/(.*)$! } cat_("$::prefix/boot/grub/device.map");      \%grub2dev;  } +sub write_grub_device_map { +    my ($legacy_floppies, $sorted_hds) = @_; +    my $f = "$::prefix/boot/grub/device.map"; +    renamef($f, "$f.old"); +    output($f, +	   (map_index { "(fd$::i) /dev/$_->{device}\n" } @$legacy_floppies), +	   (map_index { "(hd$::i) /dev/$_->{device}\n" } @$sorted_hds)); +} -sub grub2dev { -    my ($grub_file, $o_block_device) = @_; +=item parse_grub_file($grub_file) + +Parses things like C<(hd0,4)/boot/vmlinuz> + +Returns: ("hd0", 4, "boot/vmlinuz") + +=cut + +sub parse_grub_file { +    my ($grub_file) = @_;      my ($grub_dev, $rel_file) = $grub_file =~ m!\((.*?)\)/?(.*)! or return;      my ($hd, $part) = split(',', $grub_dev); +    ($hd, $part, $rel_file); +} + +=item grub2dev_and_file($grub_file, $grub2dev, $o_block_device) + +Takes things like C<(hd0,4)/boot/vmlinuz> + +Returns: ("/dev/sda5", "boot/vmlinuz") + +=cut + +sub grub2dev_and_file { +    my ($grub_file, $grub2dev, $o_block_device) = @_; +    my ($hd, $part, $rel_file) = parse_grub_file($grub_file) or return; +    $grub2dev->{$hd} or internal_error("$hd has no mapping in device.map (when translating $grub_file)");      $part = $o_block_device ? '' : defined $part && $part + 1; #- grub wants "(hdX,Y)" where lilo just want "hdY+1" -    my $device = '/dev/' . read_grub_device_map()->{$hd} . $part; -    wantarray() ? ($device, $rel_file) : $device; +    my $device = '/dev/' . ($part eq '' ? $grub2dev->{$hd} : devices::prefix_for_dev($grub2dev->{$hd}) . $part); +    $device, $rel_file;  } -# replace dummy "(hdX,Y)" in "(hdX,Y)/boot/vmlinuz..." by appropriate path if needed -sub ungrubify { -    my ($grub_file, $mnt_pts) = @_; -    my ($device, $rel_file) = grub2dev($grub_file) or return $grub_file; -    ($mnt_pts->{$device} || '') . '/' . $rel_file; +=item grub2devd($grub_file, $grub2dev, $o_block_device) + +Takes things like C<(hd0,4)/boot/vmlinuz> + +Returns: "/dev/sda5" + +=cut + +sub grub2dev { +    my ($grub_file, $grub2dev, $o_block_device) = @_; +    first(grub2dev_and_file($grub_file, $grub2dev, $o_block_device));  } -sub write_grub_config { -    my ($bootloader, $fstab, $hds) = @_; -    my %dev2bios = ( -      (map_index { $_ => "fd$::i" } detect_devices::floppies_dev()), -      (map_index { $_ => "hd$::i" } dev2bios($hds, $bootloader->{first_hd_device} || $bootloader->{boot})), -    ); +=item grub2file($grub_file, $grub2dev, $fstab, $o_entry) -    { -	my %bios2dev = reverse %dev2bios; -	output "$::prefix/boot/grub/device.map",  -	  join '', map { "($_) /dev/$bios2dev{$_}\n" } sort keys %bios2dev; +Replaces + +=over 4 + +=item * C</vmlinuz> with C</boot/vmlinuz> when "root" or "rootnoverify" is set for the entry + +=item * C<(hdX,Y)> in C<(hdX,Y)/boot/vmlinuz...> by appropriate path if possible/needed + +=back + +=cut + +sub grub2file { +    my ($grub_file, $grub2dev, $fstab, $o_entry) = @_; + +    if ($grub_file =~ m!^/!) { +	my $root = $o_entry && ($o_entry->{rootnoverify} || $o_entry->{grub_root}); +	$root and $grub_file = "$root$grub_file";      } -    my $file2grub = sub { -	my ($part, $file) = fsedit::file2part($fstab, $_[0], 'keep_simple_symlinks'); -	dev2grub($part->{device}, \%dev2bios) . $file; + +    if (my ($device, $rel_file) = grub2dev_and_file($grub_file, $grub2dev)) {	 +	my $part = fs::get::device2part($device, $fstab); +	if (my $mntpoint = $part && $part->{mntpoint})  { +	    ($mntpoint eq '/' ? '' : $mntpoint) . '/' . $rel_file; +	} else { +	    log::l("ERROR: unknown device $device (computed from $grub_file)"); +	    $grub_file; +	} +    } else { +	$grub_file; +    } +} + +sub boot_copies_dir() { '/boot/copied' } +sub create_copy_in_boot { +    my ($file) = @_; + +    my $s = $file; +    $s =~ s!/!_!g; +    my $file2 = boot_copies_dir() . "/$s"; + +    log::l("$file is not available at boot time, creating a copy ($file2)"); +    mkdir_p(boot_copies_dir()); +    output("$file2.link", $file . "\n"); +    update_copy_in_boot("$file2.link"); + +    $file2; +} +sub update_copy_in_boot { +    my ($link) = @_; +    my $orig = chomp_(cat_("$::prefix$link")); +    (my $dest = $link) =~ s/\.link$// or internal_error("update_copy_in_boot: $link"); +    if (-e "$::prefix$orig") { +	log::l("updating $dest from $orig"); +	cp_af("$::prefix$orig", "$::prefix$dest"); +    } else { +	log::l("removing $dest since $orig does not exist anymore"); +	unlink "$::prefix$link", "$::prefix$orig"; +    } +} + +sub crypt_grub_password { +    my ($password) = @_; +    require IPC::Open2; +    local $ENV{LC_ALL} = 'C'; +    my ($his_out, $his_in); +    my $cmd = ($::prefix ? "chroot $::prefix " : "") . "/sbin/grub-md5-crypt"; + +    my $pid = IPC::Open2::open2($his_out, $his_in, $cmd); + +    my ($line, $res); +    while (sysread($his_out, $line, 100)) { +        if ($line =~ /Password/i) { +            syswrite($his_in, "$password\n"); +        } else { +            $res = $line; +        } +    } +    waitpid($pid, 0); +    my $status = $? >> 8;            +    die "failed to encrypt password (status=$status)" if $status != 0; +    chomp_($res); +} + +sub get_grub2_first_entry { +    my ($bootloader) = @_; +    # set default parameters: +    my ($entry) = grep { $_->{kernel_or_dev} =~ /vmlin/ } @{$bootloader->{entries}}; +    $entry; +} + +sub get_grub2_append { +    my ($bootloader) = @_; +    # get default parameters from first entry: +    my ($entry) = get_grub2_first_entry($bootloader); +    my $append = $entry->{append}; +    if (my $vga = $entry->{vga} || $bootloader->{vga}) { +	$append .= " vga=$vga"; +    } +    $append =~ s/root=\S+//g; +    $append =~ s/\bro\b//g; +    $append =~ s/\s+/ /g; +    $append; +} + +sub crypt_grub2_password { +    my ($password) = @_; +    require IPC::Open2; +    local $ENV{LC_ALL} = 'C'; +    my ($his_out, $his_in); +    my $pid = IPC::Open2::open2($his_out, $his_in, "$::prefix/bin/grub2-mkpasswd-pbkdf2"); + +    my ($line, $res); +    while (sysread($his_out, $line, 100)) { +        if ($line =~ /enter.*password:/i) { +            syswrite($his_in, "$password\n"); +        } else { +            chomp($line); +            $res .= $line if $line; +        } +    } +    $res =~ s/^PBKDF2 hash of your password is //; +    waitpid($pid, 0); +    my $status = $? >> 8; +    die "failed to encrypt password (status=$status)" if $status != 0; +    chomp_($res); +} + +sub write_grub2_sysconfig { +    my ($bootloader, $_all_hds, $o_backup_extension) = @_; + +    # Set password prior to run update-grub2: +    my $pw_f = get_grub2_users(); +    if ($bootloader->{password}) { +	if (!is_grub2_already_crypted($bootloader->{password})) { +	    $bootloader->{password} = crypt_grub2_password($bootloader->{password}); +        } +	output_with_perm($pw_f, 0600, "GRUB2_PASSWORD=$bootloader->{password}"); +    } else { +	unlink($pw_f); +    } + +    my $f = "$::prefix/etc/default/grub"; +    my %conf = getVarsFromSh($f); + +    my $append = $bootloader->{perImageAppend} || get_grub2_append($bootloader); +    my $vga = $bootloader->{default_vga}; +    $append .= " vga=$vga" if $append !~ /vga=/ && $vga && $vga ne "normal"; + +    $conf{GRUB_CMDLINE_LINUX_DEFAULT} = $append; +    $conf{GRUB_GFXPAYLOAD_LINUX} = 'auto' if is_uefi(); +    $conf{GRUB_DISABLE_RECOVERY} = 'false'; # for 'failsafe' entry +    $conf{GRUB_DEFAULT} //= 'saved'; # for default entry but do not overwrite user choice +    $conf{GRUB_SAVEDEFAULT} //= 'true'; # for default entry but do not overwrite user choice +    # special case so that setVarsInSh() doesn't emit the line when timeout is 0 +    $conf{GRUB_TIMEOUT} = $bootloader->{timeout} eq 0 ? "0 " : $bootloader->{timeout}; +    renamef($f, $f . ($o_backup_extension || '.old')); +    setVarsInSh($f, \%conf); +} + +sub write_grub2_default_entry { +    my ($bootloader, $_all_hds, $o_backup_extension) = @_; + +    my $default = $bootloader->{default}; +    # menu entry must be identified by its full path. eg: "submenu1>submenu2>title": +    if (my $def = find { $_->{label} eq $bootloader->{default} } @{$bootloader->{entries}}) { +	$default = $def->{real_label} if $def->{real_label}; +    } + +    # set default entry: +    eval { +	my $f2 = "$::prefix/boot/grub2/grubenv"; +	cp_af($f2, $f2 . ($o_backup_extension || '.old')); +	my $error; +	run_program::rooted($::prefix, 'grub2-set-default', '2>', \$error, $default) or die "grub2-set-default failed: $error";      }; -    { -        local $\ = "\n"; -	my $f = "$::prefix/boot/grub/menu.lst"; -	local *F; -	open F, ">$f" or die "cannot create grub config file: $f"; -	log::l("writing grub config to $f"); +    if (my $err = $@) { +	log::l("error while running grub2-set-default: $err"); +    } +} -	$bootloader->{$_} and print F "$_ $bootloader->{$_}" foreach qw(timeout); +sub write_grub2 { +    my ($bootloader, $o_all_hds, $o_backup_extension) = @_; +    my $error; -	print F "color black/cyan yellow/cyan"; -	print F "serial --unit=$1 --speed=$2\nterminal --timeout=" . ($bootloader->{timeout} || 0) . " console serial" if get_append($bootloader, 'console') =~ /ttyS(\d),(\d+)/; +    write_grub2_sysconfig($bootloader, $o_all_hds, $o_backup_extension); -	each_index { -	    print F "default $::i" if $_->{label} eq $bootloader->{default}; -	} @{$bootloader->{entries}}; +    my $f1 = "$::prefix/boot/grub2/grub.cfg"; +    #- we won't just rename as grub2-mkconfig (more likely os-prober) may fail: +    cp_af($f1, $f1 . '.old') if -e $f1; +    #- don't use the update-grub2 script here, it hangs when run by service_harddrake +    #- during boot because it causes systemd to try to start a user session +    run_program::rooted($::prefix, 'grub2-mkconfig', '2>', \$error, '-o', '/boot/grub2/grub.cfg') or die "grub2-mkconfig failed: $error"; +    log::l("grub2-mkconfig logs: $error"); + +    write_grub2_default_entry($bootloader, $o_all_hds, $o_backup_extension); +    check_enough_space(); +} + +sub get_grub2_users() { +    "$::prefix/boot/grub2/user.cfg"; +} + +sub get_grub2_install_sh() { +    "$::prefix/boot/grub2/install.sh"; +} + +sub write_grub2_install_sh    { +    my ($bootloader, $o_backup_extension) = @_; +    my $f = get_grub2_install_sh(); +    my $boot = $bootloader->{boot}; +    my @options; +    if (is_uefi()) { +	if ($bootloader->{no_esp_or_mbr}) { +	    push @options, qw(--bootloader-id=tmp --no-nvram); +	} elsif ($bootloader->{removable}) { +	    push @options, '--removable'; +	} +    } else { +	@options = $bootloader->{no_esp_or_mbr} ? ('--grub-setup=/bin/true', $boot) : $boot; +    } +    renamef($f, $f . ($o_backup_extension || '.old')); +    output_with_perm($f, 0755, join(' ', 'grub2-install', @options)); +} + +sub write_grub { +    my ($bootloader, $all_hds, $o_backup_extension) = @_; -	foreach (@{$bootloader->{entries}}) { -	    print F "\ntitle $_->{label}"; - -	    if ($_->{type} eq "image") { -		my $vga = $_->{vga} || $bootloader->{vga}; -		printf F "kernel %s root=%s %s%s%s\n", -		  $file2grub->($_->{kernel_or_dev}), -		  $_->{root} =~ /loop7/ ? "707" : $_->{root}, #- special to workaround bug in kernel (see #ifdef CONFIG_BLK_DEV_LOOP) -		  $_->{append}, -		  $_->{'read-write'} && " rw", -		  $vga && $vga ne "normal" && " vga=$vga"; -		print F "initrd ", $file2grub->($_->{initrd}) if $_->{initrd}; +    my $fstab = [ fs::get::fstab($all_hds) ];  +    my @legacy_floppies = detect_devices::floppies(); +    my @sorted_hds = sort_hds_according_to_bios($bootloader, $all_hds); +    write_grub_device_map(\@legacy_floppies, \@sorted_hds); + +    my $file2grub; $file2grub = sub { +	my ($file) = @_; +	if ($file =~ m!^\(.*\)/!) { +	    $file; #- it's already in grub format +	} else { +	    my ($part, $rel_file) = fs::get::file2part($fstab, $file, 'keep_simple_symlinks'); +	    if (my $grub = device2grub($part, \@sorted_hds)) { +		$grub . $rel_file; +	    } elsif (!begins_with($file, '/boot/')) { +		log::l("$file is on device $part->{device} which is not available at boot time. Copying it"); +		$file2grub->(create_copy_in_boot($file));  	    } else { -		print F "root ", dev2grub($_->{kernel_or_dev}, \%dev2bios); +		log::l("ERROR: $file is on device $part->{device} which is not available at boot time. Defaulting to a dumb value"); +		"(hd0,0)$file"; +	    } +	} +    }; -		if (my ($dev) = $_->{table} =~ m|/dev/(.*)|) { -		    if ($dev2bios{$dev} =~ /hd([1-9])/) { -			#- boot off the nth drive, so reverse the BIOS maps -			my $nb = sprintf("0x%x", 0x80 + $1); -			$_->{mapdrive} ||= { '0x80' => $nb, $nb => '0x80' };  +    if (get_append_with_key($bootloader, 'console') =~ /ttyS(\d),(\d+)/) { +	$bootloader->{serial} ||= "--unit=$1 --speed=$2"; +	$bootloader->{terminal} ||= "--timeout=" . ($bootloader->{timeout} || 0) . " console serial"; +    } elsif ($bootloader->{method} eq 'grub-graphic') { +	my $bin = '/usr/sbin/grub-gfxmenu'; +	if ($bootloader->{gfxmenu} eq '' && -x "$::prefix$bin") { +	    my $locale = $::o->{locale} || do { require lang; lang::read() }; +	    run_program::rooted($::prefix, $bin, '--lang', $locale->{lang}, '--update-gfxmenu'); +	    $bootloader->{gfxmenu} ||= '/boot/gfxmenu'; +	} +	#- not handled anymore +	delete $bootloader->{$_} foreach qw(splashimage viewport shade); +    } else { +	delete $bootloader->{gfxmenu}; +    } + +    my $format = sub { map { "$_ $bootloader->{$_}" } @_ }; + +    { +	my @conf; + +        if ($bootloader->{password}) { +	    if (!is_already_crypted($bootloader->{password})) { +		my $encrypted = crypt_grub_password($bootloader->{password}); +		$bootloader->{password} = "--md5 $encrypted"; +	    } +        } + +	push @conf, $format->(grep { defined $bootloader->{$_} } qw(timeout)); +	push @conf, $format->(grep { $bootloader->{$_} } qw(color password serial shade terminal viewport background foreground)); + +	push @conf, map { $_ . ' ' . $file2grub->($bootloader->{$_}) } grep { $bootloader->{$_} } qw(gfxmenu); + +	eval { +	    push @conf, "default " . (find_index { $_->{label} eq $bootloader->{default} } @{$bootloader->{entries}}); +	}; + +	foreach my $entry (@{$bootloader->{entries}}) { +	    my $title = "\ntitle $entry->{label}"; + +	    if ($entry->{keep_verbatim}) { +		push @conf, '', $entry->{verbatim}; +	    } elsif ($entry->{type} eq "image") { +		push @conf, $title; +		push @conf, grep { $entry->{$_} } 'lock'; +		push @conf, join(' ', 'kernel', $file2grub->($entry->{xen}), $entry->{xen_append}) if $entry->{xen}; + +		my $vga = $entry->{vga} || $bootloader->{vga}; +		push @conf, join(' ', $entry->{xen} ? 'module' : 'kernel',  +		       $file2grub->($entry->{kernel_or_dev}), +		       $entry->{xen} ? () : 'BOOT_IMAGE=' . simplify_label($entry->{label}), +		       if_($entry->{root}, $entry->{root} =~ /loop7/ ? "root=707" : "root=$entry->{root}"), #- special to workaround bug in kernel (see #ifdef CONFIG_BLK_DEV_LOOP) +		       $entry->{append}, +		       if_($entry->{'read-write'}, 'rw'), +		       if_($vga && $vga ne "normal", "vga=$vga")); +		push @conf, "module " . $_ foreach @{$entry->{modules} || []}; +		if ($entry->{initrd}) { +		    # split partition from initrd path and place +		    # it to a separate 'root' entry. +		    # Grub2's mkconfig takes initrd entry 'as is', +		    # but grub2 fails to load smth like '(hd0,1)/boot/initrd' taken from grub-legacy +		    my $initrd_path = $file2grub->($entry->{initrd}); +		    if ($initrd_path =~ /^(\([^\)]+\))/) { +			push @conf, "root $1"; +			$initrd_path =~ s/^(\([^\)]+\))//; +		    } +		    push @conf, join(' ', $entry->{xen} ? 'module' : 'initrd', $initrd_path); +		} +	    } else { +		my $dev = eval { device_string2grub($entry->{kernel_or_dev}, \@legacy_floppies, \@sorted_hds) }; +		if (!$dev) { +		    log::l("dropping bad entry $entry->{label} for unknown device $entry->{kernel_or_dev}"); +		    next; +		} +		push @conf, $title; +		push @conf, grep { $entry->{$_} } 'lock'; +		if ($entry->{type} ne 'grub_configfile' || $entry->{configfile} !~ /grub\.cfg/ || !$entry->{root}) { +		    push @conf, join(' ', $entry->{rootnoverify} ? 'rootnoverify' : 'root', $dev); +		} + +		if ($entry->{table}) { +		    if (my $hd = fs::get::device2part($entry->{table}, \@sorted_hds)) { +			if (my $bios = find_index { $hd eq $_ } @sorted_hds) { +			    #- boot off the nth drive, so reverse the BIOS maps +			    my $nb = sprintf("0x%x", 0x80 + $bios); +			    $entry->{mapdrive} ||= { '0x80' => $nb, $nb => '0x80' };  +			}  		    }  		} -		if ($_->{mapdrive}) { -		    map_each { print F "map ($::b) ($::a)" } %{$_->{mapdrive}}; -		    print F "makeactive"; +		if ($entry->{mapdrive}) { +		    push @conf, map_each { "map ($::b) ($::a)" } %{$entry->{mapdrive}}; +		} +		push @conf, "makeactive" if $entry->{makeactive}; +		# grub.cfg is grub2 config, can't use it as configfile for grub-legacy +		if ($entry->{type} eq 'grub_configfile' && $entry->{configfile} !~ /grub\.cfg/) { +		    push @conf, "configfile $entry->{configfile}"; +		} elsif ($entry->{linux}) { +		    push @conf, "root $entry->{root}", "kernel $entry->{linux}"; +		    push @conf, "initrd $entry->{initrd}" if $entry->{initrd}; +		} else { +		    push @conf, "chainloader +1";  		} -		print F "chainloader +1";  	    }  	} +	my $f = "$::prefix/boot/grub/menu.lst"; +	log::l("writing grub config to $f"); +	renamef($f, $f . ($o_backup_extension || '.old')); +	output_with_perm($f, 0600, map { "$_\n" } @conf);      } -    my $dev = dev2grub($bootloader->{boot}, \%dev2bios); -    my ($s1, $s2, $m) = map { $file2grub->("/boot/grub/$_") } qw(stage1 stage2 menu.lst); -    my $f = "/boot/grub/install.sh"; -    output "$::prefix$f", +    { +	my $f = "$::prefix/boot/grub/install.sh"; +	my $boot_dev = device_string2grub($bootloader->{boot}, \@legacy_floppies, \@sorted_hds); +	my $files_dev = device2grub(fs::get::root_($fstab, 'boot'), \@sorted_hds); +	renamef($f, $f . ($o_backup_extension || '.old')); +	output_with_perm($f, 0755,  "grub --device-map=/boot/grub/device.map --batch <<EOF -install $s1 d $dev $s2 p $m +root $files_dev +setup --stage2=/boot/grub/stage2 $boot_dev  quit  EOF -"; -    -    my $e = "$::prefix/boot/.enough_space"; -    output $e, 1; -s $e or die N("not enough room in /boot"); -    unlink $e; -    $f; +"); +    } + +    check_enough_space(); +} + +sub configure_kdm_BootManager { +    my ($name) = @_; +    eval { common::update_gnomekderc_no_create("$::prefix/etc/kde/kdm/kdmrc", 'Shutdown' => ( +	BootManager => $name +    )) }; +} + +sub sync_partition_data_to_disk { +    my ($part) = @_; + +    common::sync(); + +    if ($part->{fs_type} eq 'xfs') { +	run_program::rooted($::prefix, 'xfs_freeze', '-f', $part->{mntpoint}); +	run_program::rooted($::prefix, 'xfs_freeze', '-u', $part->{mntpoint}); +    } +} + +sub _dev_to_MBR_backup { +    my ($dev) = @_; +    $dev =~ s!/dev/!!; +    $dev =~ s!/!_!g; +    "$::prefix/boot/boot.backup.$dev"; +} + +sub save_previous_MBR_bootloader { +    my ($dev) = @_; +    my $t; +    open(my $F, $dev); +    CORE::read($F, $t, 0x1b8); #- up to disk magic +    output(_dev_to_MBR_backup($dev), $t); +} + +sub restore_previous_MBR_bootloader { +    my ($dev) = @_; +    log::l("restoring previous bootloader on $dev"); +    output($dev, scalar cat_(_dev_to_MBR_backup($dev))); +} + +sub install_grub2 { +    my ($bootloader, $all_hds) = @_; +    write_grub2($bootloader, $all_hds); +    write_grub2_install_sh($bootloader, '.old'); +    install_raw_grub2(); +} + +sub install_raw_grub2() { +    my $error; +    my $f = '/boot/grub2/install.sh'; +    my ($right_dir, $bad_dir) = ("$::prefix/boot/EFI/EFI/", "$::prefix/boot/EFI/efi/"); +    if (-e $bad_dir && ! -e $right_dir) { +	renamef($bad_dir, $right_dir); +    } +    if (!run_program::rooted($::prefix, "sh", "2>", \$error, $f)) { +	log::explanations("grub2-install failed:\n(" . cat_($f) . ")\nError: <$error>"); +	die "grub2-install failed: $error"; +    }  }  sub install_grub { -    my ($bootloader, $fstab, $hds) = @_; +    my ($bootloader, $all_hds) = @_; -    my $f = write_grub_config($bootloader, $fstab, $hds); +    write_grub($bootloader, $all_hds);      if (!$::testing) { -	log::l("Installing boot loader..."); -	symlink "$::prefix/boot", "/boot"; -	my $error; -	run_program::run("sh", $f, "2>", \$error) or die "grub failed: $error"; -	unlink "/boot"; +	if ($bootloader->{previous_boot} && $bootloader->{previous_boot} eq $bootloader->{boot}) { +	    # nothing to do (already installed in {boot}) +	} else { +	    if ($bootloader->{previous_boot}) { +		restore_previous_MBR_bootloader(delete $bootloader->{previous_boot}); +	    } +	    if (fs::get::device2part($bootloader->{boot}, [ fs::get::hds($all_hds) ])) { +		save_previous_MBR_bootloader($bootloader->{boot}); +		$bootloader->{previous_boot} = $bootloader->{boot}; +	    } +	} + +	my @files = grep { /(stage1|stage2|_stage1_5)$/ } glob("$::prefix/lib/grub/*/*"); +	cp_af(@files, "$::prefix/boot/grub"); +	sync_partition_data_to_disk(fs::get::root([ fs::get::fstab($all_hds) ], 'boot')); +	install_raw_grub();  +    } + +    configure_kdm_BootManager('Grub'); +} +sub install_raw_grub() { +    log::l("Installing boot loader..."); +    my $error; +    run_program::rooted($::prefix, "sh", "2>", \$error, '/boot/grub/install.sh') or die "grub failed: $error"; +} + +sub when_config_changed_grub2 { +    my ($_bootloader) = @_; +    #- do not do anything +} + +sub when_config_changed_grub { +    my ($_bootloader) = @_; +    #- do not do anything + +    update_copy_in_boot($_) foreach glob($::prefix . boot_copies_dir() . '/*.link'); +} + +sub write_refind { +    my ($bootloader, $_all_hds, $o_backup_extension) = @_; + +    my @config; + +    foreach my $entry (@{$bootloader->{entries}}) { +	if ($entry->{type} eq 'image' && $entry->{kernel_or_dev} eq '/boot/vmlinuz') { +	    my $vga = $entry->{vga} || $bootloader->{vga}; +	    my $boot_params = join(' ', +		"root=$entry->{root}", +		$entry->{append}, +		if_($entry->{'read-write'}, 'rw'), +		if_($vga && $vga ne "normal", "vga=$vga") +	    ); +	    push @config, '"' . $entry->{label} . '" "' . $boot_params . '"'; +	} +    } +    if (@config) { +	my $f = "$::prefix/boot/refind_linux.conf"; +	log::l("writing rEFInd config to $f"); +	renamef($f, $f . ($o_backup_extension || '.old')); +	output_with_perm($f, 0600, map { "$_\n" } @config); +	check_enough_space(); +    } else { +	log::l("config has no entries - rEFInd config file not written"); +    } + +    my $default_kernel = readlink("$::prefix/boot/vmlinuz"); +    if ($default_kernel) { +	if ($bootloader->{use_nvram}) { +	    write_refind_previous_boot_var($default_kernel); +	} else { +	    write_refind_previous_boot_file($default_kernel, "$::prefix/boot/EFI/EFI/refind"); +	    write_refind_previous_boot_file($default_kernel, "$::prefix/boot/EFI/EFI/BOOT"); +	} +    } +} + +sub write_refind_previous_boot_var { +    my ($kernel) = @_; +    my ($efivars, $already_mounted) = mount_efivars(); +    my $previous_boot = "$efivars/PreviousBoot-36d08fa7-cf0b-42f5-8f14-68df73ed3740"; +    run_program::run('chattr', '-i', $previous_boot) if -e $previous_boot; +    if (open(my $efivar, '>:raw', $previous_boot)) { +	require Encode; +	log::l("writing rEFInd PreviousBoot variable"); +	print $efivar "\x07\x00\x00\x00"; +	print $efivar Encode::encode('UTF16-LE', $kernel); +	print $efivar "\x00\x00"; +	close($efivar); +    } else { +	log::l("failed to write rEFInd PreviousBoot variable: $@ ($!)");      } +    run_program::run('umount', $efivars) if !$already_mounted;  } -sub lnx4win_file {  -    my $bootloader = shift; -    map { local $_ = $_; s,/,\\,g; "$bootloader->{boot_drive}:\\lnx4win$_" } @_; +sub write_refind_previous_boot_file { +    my ($kernel, $base_path) = @_; +    return if ! -e "$base_path/refind.conf" || ! mkdir_p("$base_path/vars"); +    if (open(my $f, '>:raw', "$base_path/vars/PreviousBoot")) { +	require Encode; +	log::l("writing rEFInd $base_path/vars/PreviousBoot file"); +	print $f Encode::encode('UTF16-LE', "Boot boot\\$kernel"); +	print $f "\x00\x00"; +	close($f); +    } else { +	log::l("failed to write rEFInd $base_path/vars/PreviousBoot file"); +    } +} + +sub install_refind { +    my ($bootloader, $all_hds) = @_; + +    if ($bootloader->{install_mode} ne 'no_install') { +	my (@options, $error); +	if ($bootloader->{install_mode} eq 'as_default') { +	    $bootloader->{esp_device} or die "ESP device is unknown"; +	    push @options, '--usedefault'; +	    push @options, $bootloader->{esp_device}; +	    #- refind_install uses lower case file names. If a default bootloader was previously +	    #- created with an upper case file name, Linux won't overwrite it. +	    my $default_fn = "$::prefix/boot/EFI/EFI/BOOT/BOOT" . uc(uefi_type()) . ".EFI"; +	    unlink($default_fn) if -e $default_fn; +	} elsif ($bootloader->{install_mode} eq 'nvram_only') { +	    push @options, '--nvramonly'; +	} +	run_program::rooted($::prefix, '/sbin/refind-install', '2>', \$error, @options) +	  or die "refind-install failed: $error"; +    } + +    #- This file is not used by rEFInd itself. It just defines the paths to the image +    #- files used for the standard banner choices. +    my %h = getVarsFromSh("$::prefix/etc/sysconfig/refind"); + +    my $banner_source; +    if ($bootloader->{banner_path} eq 'refind_banner.png') { +        $banner_source = "$::prefix" . $h{REFIND_BANNER}; +    } elsif ($bootloader->{banner_path} eq 'mageia_theme.png') { +        $banner_source = "$::prefix" . $h{MAGEIA_THEME}; +    } +    if (defined $banner_source && ! (-f $banner_source && -r $banner_source)) { +        log::l("$banner_source does not exist or is not readable"); +        $bootloader->{banner_path}  = ''; +        $bootloader->{banner_scale} = 'noscale'; +        undef $banner_source; +    } + +    #- Try both possible locations for the main config file. +    modify_refind_config($bootloader, $banner_source, "$::prefix/boot/EFI/EFI/refind"); +    modify_refind_config($bootloader, $banner_source, "$::prefix/boot/EFI/EFI/BOOT"); + +    write_refind($bootloader, $all_hds);  } +sub modify_refind_config { +    my ($bootloader, $banner_source, $esp_dir) = @_; + +    my $config_file = "$esp_dir/refind.conf"; +    return if ! -f $config_file || ! -w $config_file; + +    my $use_nvram = $bootloader->{use_nvram} ? 'true' : 'false'; + +    my $banner_path = $bootloader->{banner_path}; +    cp_f($banner_source, "$esp_dir/$banner_path") if (defined $banner_source); + +    my $banner_scale = $bootloader->{banner_scale}; + +    my @config; + +    my %done; +    foreach (cat_utf8($config_file)) { +        if ($_ =~ /^#?use_nvram\s/) { +            if (! $done{use_nvram}) { +                push @config, "use_nvram $use_nvram\n" ; +                $done{use_nvram} = 1; +            } +        } elsif ($_ =~ /^#?banner\s/) { +            if (! $done{banner_path}) { +                if ($banner_path eq '') { +                    push @config, "#banner my_banner.png\n"; +                } else { +                    push @config, "banner $banner_path\n"; +                } +                $done{banner_path} = 1; +            } +        } elsif ($_ =~ /^#?banner_scale\s/) { +            if (! $done{banner_scale}) { +                push @config, "banner_scale $banner_scale\n"; +                $done{banner_scale} = 1; +            } +        } else { +            push @config, $_; +        } +    } + +    if (@config) { +	log::l("writing rEFInd config to $config_file"); +	renamef($config_file, $config_file . '.old'); +	output_with_perm($config_file, 0600, @config); +    } else { +	log::l("config has no entries - rEFInd config file not written"); +    } +} + +sub when_config_changed_refind { +    my ($_bootloader) = @_; +    #- do not do anything +} + +=item action($bootloader, $action, @para) + +Calls the C<$action> function with @para parameters: + +   $actions->($bootloader, @para) + +If needed, the function name will be resolved to call a boot loader specific function (eg: for LILO/GRUB/...) + +This Swiss army knife function is heavily used by eg /sbin/bootloader-config + +=cut + +sub action { +    my ($bootloader, $action, @para) = @_; + +    my $main_method = main_method($bootloader->{method}); +    my $f = $bootloader::{$action . '_' . $main_method} or die "unknown bootloader method $bootloader->{method} ($action)"; +    $f->($bootloader, @para); +} + +=item install($bootloader, $all_hds) + +Writes back the boot loader config. Calls the proper write_XYZ() function. + +=cut +  sub install { -    my ($bootloader, $fstab, $hds) = @_; +    my ($bootloader, $all_hds) = @_; + +    my ($efivars, $already_mounted) = mount_efivars() if is_uefi(); -    if (my $p = find { $bootloader->{boot} eq "/dev/$_->{device}" } @$fstab) { -	die N("You can't install the bootloader on a %s partition\n", partition_table::type2fs($p)) -	  if isThisFs('xfs', $p); +    $bootloader->{keytable} = keytable($bootloader->{keytable}) if $bootloader->{method} eq 'lilo'; +    action($bootloader, 'install', $all_hds); + +    run_program::run('umount', $efivars) if is_uefi() && !$already_mounted; +} + +sub get_grub2_pkg() { +    my ($prefix, $pkg); +    if (is_uefi()) { +	my %convert = (ia32 => 'i386', aa64 => 'arm64', x64 => 'x86_64'); +	my %pkgs = (ia32 => 'x86-32', aa64 => 'aarch-64', x64 => 'x86-64'); +	$prefix = $convert{uefi_type()} . "-efi"; +	$pkg = "grub2-efi(" . $pkgs{uefi_type()} . ")"; +    } else { +	$prefix = 'i386-pc'; +	$pkg = 'grub2';      } -    $bootloader->{keytable} = keytable($bootloader->{keytable}); +    ($prefix, $pkg); +} + +sub ensure_pkg_is_installed { +    my ($do_pkgs, $bootloader) = @_; -    my ($main_method) = $bootloader->{method} =~ /(\w+)/; -    my $f = $bootloader::{"install_$main_method"} or die "unknown bootloader method $bootloader->{method}"; -    $f->($bootloader, $fstab, $hds, $bootloader->{method}); +    my %suppl = ( +	# method => [ 'pkg_name', 'file_to_test' ], +	'grub-graphic' => [ qw(mageia-gfxboot-theme /usr/share/gfxboot/themes/Mageia/boot/message) ], +	'grub2-graphic' => [ qw(grub2-mageia-theme /boot/grub2/themes/maggy/theme.txt) ], +    ); +    my $main_method = main_method($bootloader->{method}); +    if ($main_method eq 'grub2') { +	my ($prefix, $pkg) = get_grub2_pkg(); +	$do_pkgs->ensure_is_installed($pkg, "/usr/lib/grub/$prefix/ext2.mod", 1) or return 0; +    } elsif ($main_method eq 'refind') { +	$do_pkgs->ensure_is_installed('refind', '/sbin/refind-install', 1) or return 0; +    } elsif (member($main_method, qw(grub grub2 lilo))) { +	$do_pkgs->ensure_binary_is_installed($main_method, $main_method, 1) or return 0; +    } +    # Install gfx theme if needed: +    if (my $pkg = $suppl{$bootloader->{method}}) { +	$do_pkgs->ensure_is_installed(@$pkg, 1) or return 0; +    } +    1; +} + +sub parse_grub2_config { +    my ($l, $grubcfg, $part) = @_; + +    my ($linux, $menuentry, $root, $root_dev, $initrd); + +    foreach (cat_($grubcfg)) { +	chomp; +	if (/^menuentry\s+['"]([^']+)["']/) { +	    if ($menuentry && $root) { +		my $parttype = partition_table::raw::typeOfMBR($root_dev); +		if ((!$parttype || $parttype eq "empty") && $linux) { +	    	    push @$l, { menuentry => $menuentry, bootpart => $part, root => $root, linux => $linux, initrd => $initrd, grub_conf => $grubcfg }; +		} +	    } +	    $menuentry = $1; +	    $root = $linux = undef; +	} elsif (/set root='(\([^\)]+\))'/) { +	    $root = $1; + +	    if ($root =~ /\(([^,]+),msdos(\d+)\)/) { +		my $dev_title = "/" . $1; +		my $part_num = $2; +		my $dec_part_num = $part_num-1; +		$dev_title =~ s!hd!dev/sd!; +		$dev_title =~ tr/0123456789/abcdefghi/; + +	        $root_dev = $part_num ? $dev_title . $part_num : $dev_title; +               $root =~ s/msdos$part_num/$dec_part_num/; +	    } +	} elsif (/^\s+linux\s+(.+)/) { +	    $linux = $1; +	} elsif (/^\s+initrd\s+(.+)/) { +	    $initrd = $1; +	} +    } +} + +=item find_other_distros_grub_conf($fstab) + +Returns a list of other distros' grub.conf + +=cut + +sub find_other_distros_grub_conf { +    my ($fstab) = @_; + +    my @unknown_true_fs =  +      grep { isTrueLocalFS($_) &&  +	     (!$_->{mntpoint} || !member($_->{mntpoint}, '/home', fs::type::directories_needed_to_boot())); +	 } @$fstab; + +    log::l("looking for configured grub on partitions " . join(' ', map { $_->{device} } @unknown_true_fs)); + +    my @l; +    foreach my $part (@unknown_true_fs) { +	my $handle = any::inspect($part, $::prefix) or next; + +	foreach my $bootdir ('', '/boot') { +	    my $f = find { -e "$handle->{dir}$bootdir/$_" } 'grub.conf', 'grub/menu.lst' or next; +	    push @l, { bootpart => $part, bootdir => $bootdir, grub_conf => "$bootdir/$f" }; +	} +	foreach my $bootdir ('', '/boot', '/boot/grub', '/boot/grub2') { +	    my $f = find { -e "$handle->{dir}$bootdir/$_" } 'grub.cfg' or next; +	    my $parttype = partition_table::raw::typeOfMBR($part->{device}); +	    if (!$parttype || $parttype eq "empty") { +		parse_grub2_config(\@l, "$handle->{dir}/$bootdir/$f", $part); +	    } else { +	        push @l, { bootpart => $part, bootdir => $bootdir, grub_conf => "$bootdir/$f" }; +	    } +	} +	if (my $f = common::release_file($handle->{dir})) { +	    my $h = common::parse_release_file($handle->{dir}, $f, $part); +	    $h->{name} = $h->{release}; +	    push @l, $h; +	} elsif ($handle && -e "$handle->{dir}/etc/issue") { +	    my ($s, $dropped) = cat_("$handle->{dir}/etc/issue") =~ /^([^\\\n]*)(.*)/; +	    log::l("found /etc/issue: $s (removed: $dropped)"); +	    push @l, { name => $s, part => $part }; +	} +    } +    my $root; +    my $set_root = sub { +	my ($v) = @_; +	$root and log::l("don't know what to do with $root->{name} ($root->{part}{device})"); +	$root = $v; +    }; +    my @found; +    while (my $e = shift @l) { +	if ($e->{name}) { +	    $set_root->($e); +	} else { +	    if (@l && $l[0]{name}) { +		$set_root->(shift @l); +	    } + +	    my $ok; +	    if ($root && $root->{part} == $e->{bootpart} && $e->{bootdir}) { +		# easy case: /boot is not a separate partition +		$ok = 1; +	    } elsif ($root && $root->{part} != $e->{bootpart} && !$e->{bootdir}) { +		log::l("associating '/' $root->{part}{device} with '/boot' $e->{bootpart}{device}"); +		$ok = 1; +	    } +	    if ($ok) { +		add2hash($e, $root); +		undef $root; +	    } elsif ($root) { +		log::l("weird case for grub conf in $e->{bootpart}{device}, keeping '/' from $root->{part}{device}"); +	    } else { +		log::l("could not recognise the distribution for $e->{grub_conf} in $e->{bootpart}{device}"); +	    } +	    $e->{name} ||= "Linux $e->{bootpart}{device}"; +	    push @found, $e; +	} +    } +    $set_root->(undef); + +    @found;  }  sub update_for_renumbered_partitions { -    my ($in, $renumbering) = @_; +    my ($in, $renumbering, $all_hds) = @_; -    my %files = ( -		 lilo => '/etc/lilo.conf', -		 grub => '/boot/grub/menu.lst', -		 grub_install => '/boot/grub/install.sh', -		 ); - -    my %configs = map { -	my $file = "$::prefix/$files{$_}"; -	if (-e $file) { -	    my $f = cat_($file); -	    $_ => { orig => $f, new => $f, file => $files{$_} }; -	} else { () } -    } keys %files; - -    my %dev2grub = $configs{grub} ? do { -	my $grub2dev = read_grub_device_map(); -	reverse %$grub2dev; -    } : (); +    my @configs = grep { $_->{content} } config_files(); +    $_->{new} = $_->{orig} = $_->{content} foreach @configs; + +    my @sorted_hds; { + 	my $grub2dev = read_grub_device_map(); +	map_each { +	    $sorted_hds[$1] = fs::get::device2part($::b, $all_hds->{hds}) if $::a =~ /hd(\d+)/; +	} %$grub2dev; +    } + +    #- NB: we make the changes with an added string inside so that hda5 is only renamed once to hda6      foreach (@$renumbering) {  	my ($old, $new) = @$_; -	my ($old_grub, $new_grub) = eval { map { dev2grub($_, \%dev2grub) } $old, $new }; -	log::l("renaming $old -> $new  and $old_grub -> $new_grub"); -	foreach (values %configs) { -	    $_->{new} =~ s/\b$old/$new/g; -	    $_->{new} =~ s/\Q$old_grub/$new_grub/g if $old_grub; -	} +	log::l("renaming $old -> $new"); +	(my $lnew = $new) =~ s/(\d+)$/__DRAKX_DONE__$1/; +	$_->{new} =~ s/\b$old/$lnew/g foreach @configs; + +	any { $_->{name} eq 'grub' } @configs or next; + +	my ($old_grub, $new_grub) = map { device_string2grub($_, [], \@sorted_hds) } $old, $new; +	log::l("renaming $old_grub -> $new_grub"); +	(my $lnew_grub = $new_grub) =~ s/\)$/__DRAKX_DONE__)/; +	$_->{new} =~ s/\Q$old_grub/$lnew_grub/g foreach @configs;      } -    any { $_->{orig} ne $_->{new} } values %configs or return 1; # no need to update +    $_->{new} =~ s/__DRAKX_DONE__//g foreach @configs; + +    my @changed_configs = grep { $_->{orig} ne $_->{new} } @configs or return 1; # no need to update      $in->ask_okcancel('', N("Your bootloader configuration must be updated because partition has been renumbered")) or return; -    foreach (values %configs) { -	output("$::prefix/$_->{file}", $_->{new}) if $_->{new} ne $_->{orig}; +    foreach (@changed_configs) { +	renamef("$::prefix/$_->{file}", "$::prefix/$_->{file}.old"); +	output("$::prefix/$_->{file}", $_->{new});      } -    if ($configs{lilo} && $configs{lilo}{orig} ne $configs{lilo}{new} && detect_bootloader() =~ /LILO/ || -	$configs{grub_install} && $configs{grub_install}{orig} ne $configs{grub_install}{new} && detect_bootloader() =~ /GRUB/) { -	$in->ask_warn('', N("The bootloader can't be installed correctly. You have to boot rescue and choose \"%s\"",  + +    my $main_method = detect_main_method($all_hds); +    my @needed = map {  +	$_ eq 'grub' ? 'grub_install' : $_; +    } $main_method ? $main_method : ('lilo', 'grub'); + +    if (intersection(\@needed, [ map { $_->{name} } @changed_configs ])) { +	$in->ask_warn('', N("The bootloader cannot be installed correctly. You have to boot rescue and choose \"%s\"",   			    N("Re-install Boot Loader")));      }      1;  } +=back + +=cut +  1; | 
