diff options
Diffstat (limited to 'lib/MGA/DrakISO/BuildBoot.pm')
-rwxr-xr-x | lib/MGA/DrakISO/BuildBoot.pm | 423 |
1 files changed, 423 insertions, 0 deletions
diff --git a/lib/MGA/DrakISO/BuildBoot.pm b/lib/MGA/DrakISO/BuildBoot.pm new file mode 100755 index 0000000..895f7b7 --- /dev/null +++ b/lib/MGA/DrakISO/BuildBoot.pm @@ -0,0 +1,423 @@ +# Copyright (C) 2005 Mandriva +# Olivier Blin <oblin@mandriva.com> +# Copyright (C) 2017 Mageia +# Martin Whitaker <mageia@martin-whitaker.me.uk> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +package MGA::DrakISO::BuildBoot; + +use strict; + +use MDK::Common; +use common; + +use File::Copy qw(mv); + +use MGA::DrakISO::Live; +use MGA::DrakISO::Utils; + +use Exporter; +our @ISA = qw(Exporter); +our @EXPORT = qw(prepare_live_system_boot prepare_iso_bootloader); + +############################################################################### +# Live System +############################################################################### + +sub prepare_live_system_boot { + my ($live) = @_; + + # Create a build directory. This will contain all the files we need to + # exist in /boot on the ISO. + my $boot_dir = $live->get_builddir . $live->{prefix}{build}{boot}; + mkdir_p($boot_dir); + + # Locate the kernel we want to boot. + my $kernel = $live->find_kernel; + print "Using kernel $kernel->{version}\n"; + + # Copy the kernel into the build directory. + my $vmlinuz = $live->get_system_root . '/boot/vmlinuz-' . $kernel->{version}; + -e $vmlinuz or die "cannot find kernel $kernel->{version} in root system\n"; + cp_f($vmlinuz, $boot_dir . '/vmlinuz'); + + # Build an initrd suitable for Live boot. + my $initrd = $live->get_system_root . '/boot/' . $live->get_initrd_name; + unlink($initrd); + { + my $bootloader = {}; + local $::prefix = $live->get_system_root; + bootloader::add_kernel($bootloader, $kernel, { label => 'linux', vga => $live->{system}{vga_mode} }, '', $live->{system}{no_initrd}); + } + + # Move the initrd into the build directory. + mv($initrd, $boot_dir . '/initrd.gz') or die "cannot move initrd: $!\n"; +} + +############################################################################### +# ISO Bootloader +############################################################################### + +sub prepare_iso_bootloader { + my ($live) = @_; + + # Create a subdirectory to hold the grub2 bootloader. + my $grub2_dir = $live->get_builddir . $live->{prefix}{build}{boot} . '/grub2'; + mkdir_p($grub2_dir); + + # Locate and copy the default font for the bootloader. If we can't find a + # font, don't worry - the bootloader will fall back to text mode. + my $font = $live->get_absolute_path($live->{media}{bootloader_font}); + if (defined $font) { + -e $font or die "cannot find bootloader font file $font\n"; + } else { + $font = '/usr/share/grub/unicode.pf2'; + } + if (-e $font) { + my $fonts_dir = $grub2_dir . '/fonts'; + mkdir_p($fonts_dir); + cp_f($font, $fonts_dir); + } + + # Locate and copy the bootloader theme. Default to the standard Mageia + # theme if the user hasn't specified one. If that's not available either, + # proceed without a theme. + my $theme = $live->get_absolute_path($live->{media}{bootloader_theme}); + if (defined $theme) { + -d $theme or die "cannot find bootloader theme directory $theme\n"; + } else { + $theme = '/boot/grub2/themes/maggy'; + } + my $theme_name = basename($theme); + my @theme_fonts; + if (-d $theme) { + my $themes_dir = $grub2_dir . '/themes'; + mkdir_p($themes_dir); + cp_f($theme, $themes_dir); + @theme_fonts = map { basename($_) } glob("$theme/*.pf2"); + } + + # If the user has provided the necessary configuration data, construct + # the bootloader language and keyboard selection submenus and copy the + # grub2 keyboard layout files. + my $add_lang_menu = defined $live->{media}{bootloader_langs}; + my $add_kbd_menu = defined $live->{media}{bootloader_kbds}; + if ($add_lang_menu) { + my $lang_names = $live->get_absolute_path($live->{media}{bootloader_langs}); + -e $lang_names or die "cannot find bootloader language name file $lang_names\n"; + my @langs = group_by2(eval(cat_($lang_names))) or die "error in language name file $lang_names\n"; + + my $lang_kbds = dirname($lang_names) . '/lang-kbds.txt'; + my $kbds; + if ($add_kbd_menu && -e $lang_kbds) { + $kbds = eval(cat_($lang_kbds)) or die "error in language keyboard file $lang_kbds\n"; + } + + MDK::Common::File::output_utf8($grub2_dir . '/lang-menu.cfg', build_lang_menu_cfg(\@langs, %$kbds)); + } + if ($add_kbd_menu) { + my $kbd_names = $live->get_absolute_path($live->{media}{bootloader_kbds}); + -e $kbd_names or die "cannot find bootloader keyboard name file $kbd_names\n"; + my @kbds = group_by2(eval(cat_($kbd_names))) or die "error in keyboard name file $kbd_names\n"; + + my $layouts = dirname($kbd_names) . '/layouts'; + -d $layouts or die "cannot find bootloader keyboard map directory $layouts\n"; + cp_f($layouts, $grub2_dir); + + MDK::Common::File::output_utf8($grub2_dir . '/kbd-menu.cfg', build_kbd_menu_cfg(\@kbds)); + } + + # Copy any message translation files the user has provided. + my $messages = $live->get_absolute_path($live->{media}{bootloader_messages}); + if (defined $messages) { + -d $messages or die "cannot find bootloader messages directory $messages\n"; + my $locale_dir = $grub2_dir . '/locale'; + mkdir_p($locale_dir); + cp_f(glob($messages . '/*.mo'), $locale_dir); + } + + # If the user has supplied a grub2 image for non-UEFI boot, copy that, + # otherwise build one. + my $eltorito_img = $live->get_absolute_path($live->{media}{eltorito_img}); + if (defined $eltorito_img) { + -e $eltorito_img or die "cannot find El Torito boot image $eltorito_img\n"; + cp_f($eltorito_img, $grub2_dir . '/eltorito.img'); + } else { + build_grub2_eltorito_img($live, $grub2_dir . '/eltorito.img'); + } + + my $label = $live->{media}->get_media_label; + + # If the user has supplied a top-level grub2 configuration file, copy that + # (replacing the "VOLUME_LABEL" template with the actual label for the ISO + # image), otherwise build one. + my $grub2_cfg = $grub2_dir . '/grub.cfg'; + if (defined $live->{media}{grub2_cfg}) { + my $grub_cfg_template = $live->get_absolute_path($live->{media}{grub2_cfg}); + -e $grub_cfg_template or die "cannot find grub2 config file $grub_cfg_template\n"; + cp_f($grub_cfg_template, $grub2_cfg); + run_("sed", "-i", "s/VOLUME_LABEL/$label/g", $grub2_cfg); + } else { + output($grub2_cfg, build_grub2_cfg($live, $theme_name, \@theme_fonts, $add_lang_menu, $add_kbd_menu)); + } + + my $title = $label =~ s/-/ /gr; + + # If we have a theme, replace the menu title with the name of the ISO + # (extracted from the disk label). + my $base_theme_txt = $grub2_dir . "/themes/$theme_name/theme.txt"; + if (-e $base_theme_txt) { + run_('sed', '-i', qq(s/title-text:.*/title-text: "$title"/), $base_theme_txt); + } + + # If we are building a 32-bit ISO, we are done, as we don't support + # 32-bit UEFI boot. + return if $live->{settings}{arch} ne 'x86_64'; + + # Create another build directory. This will contain all the files we need + # to exist in the /EFI directory on the ISO. + my $efi_root_dir = $live->get_builddir . $live->{prefix}{build}{EFI}; + my $efi_boot_dir = $efi_root_dir . '/BOOT'; + mkdir_p($efi_boot_dir); + + # If the user has supplied a grub2 image for UEFI boot, copy that, + # otherwise build one. + my $bootx64_efi = $live->get_absolute_path($live->{media}{bootx64_efi}); + if (defined $bootx64_efi) { + -e $bootx64_efi or die "cannot find EFI boot image $bootx64_efi\n"; + cp_f($bootx64_efi, $efi_boot_dir . '/bootx64.efi'); + } else { + build_grub2_bootx64_efi($live, $efi_boot_dir . '/bootx64.efi'); + } + + # Build a grub2 configuration file for UEFI boot. This just chains to the + # main grub2 configuration file. + output($efi_boot_dir . '/grub.cfg', build_uefi_grub2_cfg($live)); + + # If we have a theme, duplicate the theme configuration file and modify the + # title string to indicate we are doing a UEFI boot. This is useful when + # dealing with user bug reports... + if (-e $base_theme_txt) { + my $uefi_theme_txt = $grub2_dir . "/themes/$theme_name/theme-uefi.txt"; + cp_f($base_theme_txt, $uefi_theme_txt); + run_('sed', '-i', qq(s/title-text:.*/title-text: "$title (UEFI)"/), $uefi_theme_txt); + } + + # Create another build directory for temporarily storing the ESP image. + my $images_dir = $live->get_builddir . $live->{prefix}{build}{images}; + mkdir_p($images_dir); + + # Construct an ESP image. This is needed for USB boot. + my $esp_image = $images_dir . '/esp.img'; + eval { rm_rf($esp_image) }; + run_("/sbin/mkdosfs", "-F12", "-C", $esp_image, "4096"); + run_("mcopy", "-s", "-i", $esp_image, $efi_root_dir, "::"); +} + +sub build_grub2_eltorito_img { + my ($live, $output) = @_; + + my @modules = qw(biosdisk iso9660 fat part_msdos all_video font png gfxterm gfxmenu linux + keylayouts at_keyboard usb_keyboard configfile echo gettext ls search test); + + run_('grub2-mkimage', + '--output', $output, + '--prefix', $live->get_media_prefix('boot') . '/grub2', + '--format', 'i386-pc-eltorito', + @modules + ); +} + +sub build_grub2_bootx64_efi { + my ($live, $output) = @_; + + my @modules = qw(iso9660 fat part_msdos all_video font png gfxterm gfxmenu linux + keylayouts at_keyboard usb_keyboard configfile echo gettext ls search test); + + run_('grub2-mkimage', + '--output', $output, + '--prefix', $live->get_media_prefix('EFI') . '/BOOT', + '--format', 'x86_64-efi', + @modules + ); +} + +sub build_grub2_cfg { + my ($live, $theme_name, $theme_fonts, $add_lang_menu, $add_kbd_menu) = @_; + + my @loadfonts; + if (defined $theme_name) { + @loadfonts = map { " loadfont \$prefix/themes/$theme_name/$_" } @$theme_fonts; + } + + my $gettext = $add_lang_menu ? '$' : ''; + + my $boot_dir = $live->get_media_prefix('boot'); + + join("\n", + "if [ -z \$initialised ] ; then", + " search --no-floppy --set=root -l '" . $live->{media}->get_media_label . "'", + " set prefix=(\$root)" . $live->get_media_prefix('boot') . "/grub2", + "", + " if loadfont \$prefix/fonts/unicode.pf2 ; then", + " set gfxmode=1024x768,800x600,auto", + " set gfxpayload=keep", + " terminal_output gfxterm", + " fi", + if_($theme_name, + "", + " if [ x\$uefi == 'xtrue' ] ; then", + " set theme=\$prefix/themes/$theme_name/theme-uefi.txt", + " else", + " set theme=\$prefix/themes/$theme_name/theme.txt", + " fi", + " export theme", + @loadfonts, + ), + " set initialised=true", + " export initialised", + "fi", + "", + "set default=" . get_bootloader_default($live), + "set timeout=" . get_bootloader_timeout($live), + "", + if_($add_lang_menu, + "export lang", + "export lkbd", + "", + ), + if_($add_kbd_menu, + "export kbd", + "", + ), + (map { + my ($name, $cmdline) = @$_; + join("\n", + "menuentry $gettext\"$name\" {", + " linux $boot_dir/vmlinuz " . get_default_append($live) . if_($cmdline, " $cmdline"), + " initrd $boot_dir/initrd.gz", + "}" + ); + } group_by2(@{$live->{media}{bootloader_entries}})), + if_($add_lang_menu || $add_kbd_menu, + # this acts as a spacer + "menuentry '________________________' {", + " set dummy=true", + "}", + ), + if_($add_lang_menu, + "submenu \"F2: \"$gettext\"Language [\$lang]\" --id language --hotkey f2 {", + " source \$prefix/lang-menu.cfg", + "}", + ), + if_($add_kbd_menu, + "submenu \"F3: \"$gettext\"Keyboard [\$kbd]\" --id keyboard --hotkey f3 {", + " source \$prefix/kbd-menu.cfg", + "}", + ), + "", + ); +} + +sub get_bootloader_default { + my ($live) = @_; + defined $live->{media}{bootloader_default} ? $live->{media}{bootloader_default} : 0; +} + +sub get_bootloader_timeout { + my ($live) = @_; + defined $live->{media}{bootloader_timeout} ? $live->{media}{bootloader_timeout} : 4; +} + +sub get_default_append { + my ($live) = @_; + my $append = $live->{system}{append}; + join(" ", + "root=mgalive:LABEL=" . $live->{media}->get_media_label, + "lang=\$lang kbd=\$kbd", + if_($append, $append), + if_($live->{system}{vga_mode} && $append !~ /\bvga=\b/, + "vga=" . $live->{system}{vga_mode}), + ); +} + +sub build_lang_menu_cfg { + my ($langs, %kbds) = @_; + join("\n", + "function set_language {", + " set lang=\$1", + " set lkbd=\$2", + " configfile \$prefix/grub.cfg", + "}", + "", + "set default=\$lang", + "set timeout=-1", + "", + "menuentry \$\"[more options after boot]\" { set_language '' '' }", + (map { + my ($id, $name) = @$_; + my $kbd = $kbds{$id}; + "menuentry '$name' --id $id { set_language $id $kbd }"; + } (@$langs)), + "", + ); +} + +sub build_kbd_menu_cfg { + my ($kbds) = @_; + join("\n", + "function set_keyboard {", + " if [ -z \$kbd ] ; then", + " terminal_input at_keyboard", + " fi", + " set kbd=\$1", + " set lkbd=", + " keymap \$prefix/layouts/\$kbd.gkb", + " configfile \$prefix/grub.cfg", + "}", + "", + "if [ -z \$kbd ] ; then", + " set default=\$lkbd", + "else", + " set default=\$kbd", + "fi", + "", + "set timeout=-1", + "", + (map { + my ($id, $name) = @$_; + $name =~ s/"/\\"/g; + "menuentry \$\"$name\" --id $id { set_keyboard $id }"; + } (@$kbds)), + "", + ); +} + +sub build_uefi_grub2_cfg { + my ($live) = @_; + join("\n", + "search --no-floppy --set=root -l '" . $live->{media}->get_media_label . "'", + "set prefix=(\$root)" . $live->get_media_prefix('boot') . "/grub2", + "", + "set uefi=true", + "export uefi", + "", + "configfile \$prefix/grub.cfg", + "", + ); +} + +1; |