# Copyright (C) 2005 Mandriva # Olivier Blin # Copyright (C) 2017 Mageia # Martin Whitaker # # 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;