#!/usr/bin/perl # Copyright (C) 2005 Mandriva # Olivier Blin # Copyright (C) 2017 Mageia # # 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. use lib qw(/usr/lib/libDrakX); use strict; use MDK::Common; use common; use fs; use modules; use run_program; use Getopt::Long; use Pod::Usage; use File::Temp; use File::Copy qw(mv); use IO::Handle; #- autoflush use POSIX; use MDV::Draklive::Utils; use MDV::Draklive::Live; use MDV::Draklive::Progress; use MDV::Draklive::Loopback; use MDV::Draklive::Config; use MDV::Draklive::Storage; sub get_syslinux_path { my ($media, $opts) = @_; '/' . $media->{storage} . '/syslinux' . ($opts->{boot} && '-boot-' . $opts->{boot}) . '.cfg'; } sub need_media_specific_boot { my ($live) = @_; to_bool(list_selected_loopbacks($live)); } sub get_default_append { my ($live, $opts) = @_; my $append = $opts->{append} || $live->{system}{append}; join(' ', 'root=mgalive:' . $live->{media}->get_media_source_for_nash, if_($live->{system}{vga_mode} && $append !~ /\bvga=\b/, 'splash quiet noiswmd audit=0 rd.luks=0 rd.lvm=0 rd.md=0 rd.dm=0', 'vga=' . $live->{system}{vga_mode}), if_($append, $append), ); } sub get_bootloader_timeout { my ($live) = @_; defined $live->{media}{bootloader_timeout} ? $live->{media}{bootloader_timeout} : 4; } my @syslinux_boot_files = qw(/vmlinuz /syslinux/bootlogo /help.msg); sub build_syslinux_cfg { my ($live, $media, $opts) = @_; my $append = get_default_append($live, $opts); #- syslinux wants files at root (used with vfat fs) my $to_root = $media->get_boot_setting('fs', $opts) eq 'vfat'; my $boot = $live->get_media_prefix('boot', $opts->{boot}); my ($initrd, $kernel, $bootlogo, $help) = map { $to_root ? basename($_) : $_ } map { $boot . $_ } $media->get_initrd_path, @syslinux_boot_files; #- the generated syslinux/bootlogo is incomplete, so replace it my $bootdir = $live->get_builddir . $live->{prefix}{build}{boot}; my $has_bootlogo = $live->{system}{gfxboot} && -e ($bootdir . '/gfxmenu'); cp_f($bootdir . '/gfxmenu', $bootdir . '/syslinux/bootlogo') if $has_bootlogo; my $timeout = get_bootloader_timeout($live) * 10; my $title = $media->{title} || $live->{media}{title}; join("\n", "default $title", "prompt 1", "timeout $timeout", "display $help", if_($has_bootlogo, "ui gfxboot.c32 $bootlogo"), (map { my ($name, $cmdline) = @$_; $name =~ s/\s/_/g; if ($name eq "harddisk") { "label " . $name, " com32 chain.c32 hd0 exit"; } else { "label " . ($name || $title), " kernel $kernel", " append initrd=$initrd $append $cmdline"; } } group_by2(@{$media->{boot_entries}})), "", ); } sub build_grub_cfg { my ($live, $media, $opts, $device) = @_; #- FIXME? first partition is hardcoded for loopback (master images) #- FIXME? use find_partition_index my ($part_nb) = $device =~ m!/dev/loop! ? 1 : $device =~ /(\d+)$/; my $part_idx = $part_nb - 1; my $initrd = $media->get_initrd_path; build_grub_cfg_raw($live, $media, $initrd, $opts, $part_idx); } sub build_grub_cfg_raw { my ($live, $media, $initrd, $opts, $part_idx) = @_; #- FIXME: use the bootloader module from drakx my $grub_part = "(hd0" . (defined $part_idx ? "," . $part_idx : "") . ")"; my $boot = $live->get_media_prefix('boot'); #- FIXME handle boot media #- remove prefix if installing bootloader on separate /boot partition $media->{partitions}[$part_idx]{mntpoint} eq $boot and $boot = ""; my $title = $media->{title} || $live->{media}{title}; join("\n", "timeout " . get_bootloader_timeout($live), if_($live->{system}{gfxboot}, "gfxmenu $grub_part" . $boot . "/gfxmenu"), "default 0", (map { my ($name, $cmdline) = @$_; "title " . $title . if_($name, " ($name)"), "kernel $grub_part" . $boot . "/vmlinuz " . get_default_append($live, $opts) . if_($cmdline, " $cmdline"), if_($initrd, "initrd " . $boot . $initrd); } group_by2(@{$media->{boot_entries}})), ($live->{oem_rescue} && defined $opts->{oem_rescue_idx} ? ( #- FIXME: factorize with above, build_grub_cfg_entry($media) "title " . $live->{oem_rescue}{media}{title}, "kernel (hd0,$opts->{oem_rescue_idx})" . $boot . "/vmlinuz " . $live->{oem_rescue}{append}, "initrd (hd0,$opts->{oem_rescue_idx})" . $boot . $media->get_initrd_path, ) : ()), "", ); } sub get_langs { my ($live) = @_; uniq( (ref $live->{regions} ? @{$live->{regions}{$live->{settings}{region}}} : ()), @{$live->{system}{langs_always}} ); } sub install_system { my ($live) = @_; my $repository = $live->{settings}{repository} . '/' . $live->{settings}{arch}; my $drakx_in_chroot = $repository . '/misc/drakx-in-chroot'; my $remote_repository = $repository =~ m!^(ftp|http)://! && $1; if ($remote_repository) { my $local_drakx_in_chroot = $live->get_builddir . $live->{prefix}{build}{scripts} . '/drakx-in-chroot'; mkdir_p(dirname($local_drakx_in_chroot)); run_('curl', '--silent', '-o', $local_drakx_in_chroot, $drakx_in_chroot) or die "unable to get drakx-in-chroot from remote repository\n"; $drakx_in_chroot = $local_drakx_in_chroot; } local %ENV = ( %ENV, (map { "DRAKLIVE_" . uc($_->[0]) => $_->[1] } group_by2(%{$live->{settings}})), %{$live->{system}{install_env}}, ); $ENV{DRAKLIVE_LANGS} = join(':', get_langs($live)); run_({ targetarch => $live->{settings}{arch} }, 'perl', $drakx_in_chroot, $repository, $live->get_system_root, if_($live->{system}{auto_install}, '--auto_install', $live->{settings}{config_root} . '/' . $live->{system}{auto_install}), if_($live->{system}{patch_install}, '--defcfg', $live->{settings}{config_root} . '/' . $live->{system}{patch_install}), if_($live->{system}{rpmsrate}, '--rpmsrate', $live->{settings}{config_root} . '/' . $live->{system}{rpmsrate}), ($live->{system}{stage2_updates} ? (map { ('--stage2-update', $live->{settings}{config_root} . '/' . $_->[0], $_->[1]) } @{$live->{system}{stage2_updates}}) : ()), ) or die "unable to install system chroot\n"; post_install_system($live); } sub configure_draklive_resize { my ($live) = @_; my $resizable_loopback = find { $_->{min_size} } @{$live->{mount}{dirs} || []}; if ($resizable_loopback) { my $media_loopbacks = $live->get_media_prefix('loopbacks'); my $ext = $loop_types{$resizable_loopback->{type}}{extension}; output($live->get_system_root . '/etc/sysconfig/draklive-resize', <{prefix}{live}{mnt}$live->{prefix}{media}{mnt}${media_loopbacks}$resizable_loopback->{path}$ext TYPE=$resizable_loopback->{fs} MIN_SIZE=$resizable_loopback->{min_size} MOUNT=$live->{prefix}{live}{mnt}$resizable_loopback->{mountpoint}_resized OLD_MOUNT=$live->{prefix}{live}{mnt}$resizable_loopback->{mountpoint} UNION=/ EOF } } sub copy_files_to { my ($live, $files, $root) = @_; foreach (@$files) { my ($source, $dest, $o_opts) = @$_; $dest = $root . '/' . $dest; mkdir_p($dest =~ m|/$| ? $dest : dirname($dest)); my @sources = MDV::Draklive::Utils::glob__($live->{settings}{config_root} . '/' . $source); print STDERR "copying @sources to $dest\n"; cp_af(@sources, $dest); my $o_perm = $o_opts && $o_opts->{mode}; chmod $o_perm, $dest if $o_perm; } } sub join_lists { my ($separator, $head, @lists) = @_; @{$head || []}, map { $separator, @$_ } @lists; } sub remove_files_from { my ($files, $root) = @_; run_('find', $root, '(', join_lists('-o', map { [ '-name', $_ ] } @$files), ')', '-exec', 'rm', '-r', '{}', ';') if $files && @$files; } sub clean_system_conf_file { my ($live, $file) = @_; substInFile { undef $_ if /^[^#]/ } $live->get_system_root . $file; } sub post_install_system { my ($live) = @_; my $previous_umask = umask; #- workaround buggy installation of directories that are not owned by any packages umask 022; mount_system_fs($live); #- copy resolv.conf for name resolution to work when adding media cp_f("/etc/resolv.conf", $live->get_system_root . "/etc/"); #- remove previous draklive leftovers if needed run_({ root => $live->get_system_root }, 'urpmi.removemedia', '-a'); foreach (@{$live->{system}{additional_media}}) { run_({ root => $live->get_system_root }, 'urpmi.addmedia', if_($_->{distrib}, '--distrib'), $_->{name}, $_->{path}) or die "unable to add media from $_->{path}\n"; @{$_->{packages} || []} or next; run_({ root => $live->get_system_root, targetarch => $live->{settings}{arch} }, 'urpmi', '--auto', '--no-verify-rpm', if_(!$_->{distrib}, '--searchmedia', $_->{name}), @{$_->{packages}}) or die "unable to install packages from $_->{path}\n"; } #- additional rpms may have dependencies in additional media if (@{$live->{system}{rpms} || []}) { my $rpm_tmp_dir = '/tmp/draklive_rpms'; mkdir_p($live->get_system_root . $rpm_tmp_dir); cp_f((map { $live->{settings}{config_root} . '/' . $_ } @{$live->{system}{rpms}}), $live->get_system_root . $rpm_tmp_dir); run_({ root => $live->get_system_root, targetarch => $live->{settings}{arch} }, 'urpmi', '--auto', '--no-verify-rpm', map { $rpm_tmp_dir . '/' . basename($_) } @{$live->{system}{rpms}}) or die "unable to install additional system rpms\n"; rm_rf($live->get_system_root . $rpm_tmp_dir); } #- remove urpmi media added by drakx-in-chroot and additional media, they're unusable run_({ root => $live->get_system_root }, 'urpmi.removemedia', '-a'); my $erase = join(' ', @{$live->{system}{erase_rpms} || []}); run_({ root => $live->get_system_root, targetarch => $live->{settings}{arch} }, 'sh', '-c', "rpm -qa $erase | xargs rpm -e ") if $erase; run_({ root => $live->get_system_root }, 'systemctl', 'disable', $_ . '.service') foreach @{$live->{system}{disable_services}}; run_({ root => $live->get_system_root }, 'systemctl', 'disable', $_ . '.timer') foreach @{$live->{system}{disable_timers}}; #- make sure harddrake is run: #- if previous HW config file is empty, we assumes DrakX has just completed the installation #- (do it in chroot, or else Storable from the build box may write an incompatible config file) run_({ root => $live->get_system_root }, 'perl', '-MStorable', '-e', qq(Storable::store({ UNKNOWN => {} }, '/etc/sysconfig/harddrake2/previous_hw'))); #- remove some build-machine specific configuration clean_system_conf_file($live, $_) foreach qw(/etc/mtab /etc/iftab /etc/shorewall/interfaces /etc/mdadm.conf), if_(!$live->{system}{skip_fstab}, '/etc/fstab'); unlink($_) foreach map { glob($live->get_system_root . $_) } @{$live->{system}{remove_files} || []}; if ($live->{system}{modules_conf}) { local $::prefix = $live->get_system_root; local *modules::write_preload_conf = sub {}; #- FIXME, make this an option my $modules_conf = modules::any_conf->vnew; put_in_hash($modules_conf, $live->{system}{modules_conf}); $modules_conf->write; } my $mount_options = $live->{media}->get_media_setting('mount_options') || "defaults"; output_with_perm($live->get_system_root . '/etc/fstab', 0644, $live->{mount}{overlay} ? "none / $live->{mount}{overlay} $mount_options 0 0\n" : $live->{media}->get_media_setting('source') . " / " . $live->{media}->get_media_setting('fs') . " $mount_options 1 1\n" ) unless $live->{system}{skip_fstab}; #- interactive mode can lead to race in initscripts #- (don't use addVarsInSh from MDK::Common, it breaks shell escapes) substInFile { s/^PROMPT=.*/PROMPT=no/ } $live->get_system_root . '/etc/sysconfig/init'; configure_draklive_resize($live); if ($live->{system}{preselect_kdm_user}) { #- preselect specified user in kdm my $kdm_cfg = $live->get_system_root . '/etc/kde/kdm/kdmrc'; update_gnomekderc($kdm_cfg, 'X-:0-Greeter' => (PreselectUser => 'Default', DefaultUser => $live->{system}{preselect_kdm_user})) if -f $kdm_cfg; } #- apply patches and install files after the configuration is cleaned #- to allow special configuration files (especially modprobe.preload) foreach (@{$live->{system}{patches}}) { my $patch = $live->{settings}{config_root} . '/' . $_; my @args = ('-p0', '-d', $live->get_system_root, '-i', $patch); run_program::run('patch', '>', '/dev/null', '--dry-run', '-f', '-R', @args) || run_('patch', @args) or die "unable to apply patch " . $patch . "\n"; } copy_files_to($live, $live->{system}{files}, $live->get_system_root); my @no_install_files = map { $_->[1] } grep { $_->[2] && $_->[2]{no_install} } @{$live->{system}{files}}; output_p($live->get_system_root . '/etc/draklive-install.d/remove.d/draklive', map { "$_\n" } @no_install_files); eval { rm_rf($live->get_builddir . $live->{prefix}{build}{files}) }; mkdir_p($live->get_builddir . $live->{prefix}{build}{files}); if ($live->{media}{files}) { copy_files_to($live, $live->{media}{files}, $live->get_builddir . $live->{prefix}{build}{files}); } remove_files_from($live->{media}{remove_files}, $live->get_builddir . $live->{prefix}{build}{files}); run_({ targetarch => $live->{settings}{arch} }, "chroot", $live->get_system_root, "bash", "-c", $live->{system}{postInstall}) if $live->{system}{postInstall}; clean_system_conf_file($live, "/etc/resolv.conf"); write_dist_lists($live); umount_system_fs($live); umask $previous_umask; } sub write_dist_lists { my ($live) = @_; my $lists_dir = $live->get_builddir . $live->{prefix}{build}{dist}; mkdir_p($lists_dir); run_("chroot " . $live->get_system_root . " rpm -qa | sort > " . $lists_dir . '/' . $live->get_name . '.lst'); run_("chroot " . $live->get_system_root . " rpm -qa --qf '%{name}\n' | sort > " . $lists_dir . '/' . $live->get_name . '.lst.names'); run_("chroot " . $live->get_system_root . qq( sh -c "rpm -qa --qf '[%{NAME} %{FILESIZES} %{FILESTATES}\n]' | awk '{if(\\\$3==0) {s[\\\$1]+=\\\$2}} END{for (p in s){print s[p],p}}' | sort -n" > ) . $lists_dir . '/' . $live->get_name . '.lst.full'); run_("chroot " . $live->get_system_root . qq( sh -c "urpmi_rpm-find-leaves | xargs rpm -q --qf '[%{NAME} %{FILESIZES} %{FILESTATES}\n]' | awk '{if(\\\$3==0) {s[\\\$1]+=\\\$2}} END{for (p in s){print s[p],p}}' | sort -n" > ) . $lists_dir . '/' . $live->get_name . '.lst.leaves'); require lang; my @live_langs = get_langs($live); my @langs = grep { member($_, @live_langs) || member(lang::locale_to_main_locale($_), @live_langs) } lang::list_langs(); my $langs_file = $lists_dir . '/' . $live->get_name . '.langs'; output_p($langs_file, map { lang::l2name($_) . " (" . $_ . ")\n" } sort(@langs)); } sub mount_system_fs { my ($live) = @_; run_('mount', '-t', 'devtmpfs', '/dev', $live->get_system_root . '/dev'); run_('mount', '-t', 'proc', '/proc', $live->get_system_root . '/proc'); run_('mount', '-t', 'sysfs', '/sys', $live->get_system_root . '/sys'); run_('mount', '-t', 'debugfs', '/sys/kernel/debug/usb', $live->get_system_root . '/sys/kernel/debug/usb'); } sub umount_system_fs { my ($live) = @_; eval { fs::mount::umount($live->get_system_root . $_) } foreach qw(/dev /proc /run /sys/kernel/debug/usb /sys); } sub umount_external_fs { my ($live) = @_; eval { fs::mount::umount($live->get_system_root . $_) } foreach map { "/mnt/$_" } all($live->get_system_root . "/mnt"); } sub expand_file_list { my ($live, @files) = @_; map { $_->{path} ? $_->{path} : chomp_(cat_(glob(($_->{rooted} && $live->get_system_root) . $_->{source}))); } @files; } #- hardlink recursively file list to a directory sub hardlink_filtered { my ($src, $dest, $files) = @_; mkdir_p($dest); my $pwd = $ENV{PWD}; chdir($src); my $list_file = tmpnam(); output_p($list_file, map { "$_\n" } grep { -e $src . $_ } @$files); #- cpio -pldm won't copy recursively, use rsync -r instead system('rsync', '-ar', '--files-from=' . $list_file, '--link-dest=' . $src, $src, $dest); unlink $list_file; chdir($pwd); } sub list_loopback_modules { my ($live) = @_; map { my $l = $_; map { my $list = $_; my $name = basename($list); $name =~ s/\.[^.]+$//; { type => $l->{type}, name => $name, files => [ expand_file_list($live, { source => $list }) ] }; } glob(($_->{rooted} && $live->get_system_root) . $_->{source}); } @{$live->{loopbacks}{modules}}; } sub create_loopback_files { my ($live) = @_; # umount filesystem in the live before creating the loopback umount_external_fs($live); umount_system_fs($live); my @excluded_files = expand_file_list($live, @{$live->{loopbacks}{exclude}{files} || []}); my @modules_files = expand_file_list($live, @{$live->{loopbacks}{modules} || []}); foreach (grep { exists $loop_types{$_->{type}}{build} } @{$live->{mount}{dirs} || []}) { local $_->{exclude} = [ @excluded_files, @modules_files ]; $loop_types{$_->{type}}{build}->($live, $_); } foreach my $module (list_loopback_modules($live)) { my $copy_tree = $live->get_system_root . "/tmp/draklive/loop/$module->{name}"; eval { rm_rf($copy_tree) }; hardlink_filtered($live->get_system_root, $copy_tree, $module->{files}); my $loop = $loop_types{$module->{type}}; $loop->{build}->($live, { path => "$live->{prefix}{build}{modules}/$module->{name}", root => $copy_tree, exclude => \@excluded_files }); eval { rm_rf($copy_tree) }; } if (@excluded_files) { my $excluded_tree = $live->get_system_root . "/tmp/draklive/excluded/all"; eval { rm_rf($excluded_tree) }; hardlink_filtered($live->get_system_root, $excluded_tree, \@excluded_files); foreach my $module (list_loopback_modules($live)) { my $copy_tree = $live->get_system_root . "/tmp/draklive/excluded/$module->{name}"; eval { rm_rf($copy_tree) }; hardlink_filtered($excluded_tree, $copy_tree, $module->{files}); my $loop = $loop_types{$module->{type}}; $loop->{build}->($live, { path => "$live->{prefix}{build}{modules}/excluded-$module->{name}", root => $copy_tree }); eval { rm_rf($copy_tree) }; } my $loop = $loop_types{$live->{loopbacks}{exclude}{type}}; $loop->{build}->($live, { path => "/excluded", root => $excluded_tree, exclude => \@modules_files }); eval { rm_rf($excluded_tree) }; } } sub list_selected_loopbacks { my ($live) = @_; my @pack = $live->{settings}{pack} ? @{$live->{packs}{$live->{settings}{pack}} || []} : (); my @pack_modules = grep { member($_->{name}, @pack) } list_loopback_modules($live); (map { $loop_types{$_->{type}}{is_loopback} && $_->{path} ? $_->{path} . $loop_types{$_->{type}}{extension} : () } @{$live->{mount}{dirs} || []}), (map { $live->{prefix}{build}{modules} . '/' . $_->{name} . $loop_types{$_->{type}}{extension} } @pack_modules); } sub get_media_device { my ($live, $opts) = @_; return $opts->{device} if $opts->{device}; my $label = $live->{media}->get_media_label or die "no device and no label"; my $device = chomp_(`readlink -f /dev/disk/by-label/$label`) or die "unable to find device for /dev/disk/by-label/$label\n"; $device; } sub prepare_bootloader { my ($live) = @_; create_initrd($live); create_bootloader($live) if !($live->{system}{skip_bootloader_config} || $live->{system}{skip_bootloader_install}); } sub create_initrd { my ($live) = @_; my $root = $live->get_system_root; mount_system_fs($live); my $kernel = $live->find_kernel; print "using kernel $kernel->{version}\n"; my $initrd_file = $root . '/boot/' . $live->get_initrd_name; unlink($initrd_file); { my $bootloader = {}; local $::prefix = $root; bootloader::add_kernel($bootloader, $kernel, { label => 'linux', vga => $live->{system}{vga_mode} }, '', $live->{system}{no_initrd}); } umount_system_fs($live); if (need_media_specific_boot($live)) { my $rootfs_initrd = $root . '/boot/' . $live->get_initrd_name; my $media_initrd = $live->get_builddir . $live->{prefix}{build}{boot} . $live->{media}->get_initrd_path; mkdir_p(dirname($media_initrd)); mv($rootfs_initrd, $media_initrd) or die "can not move initrd: $!"; } } sub create_bootloader { my ($live) = @_; my $root = $live->get_system_root; my $kernel = $live->find_kernel->{version}; my $vmlinuz_long = '/boot/vmlinuz-' . $kernel; -e $root . $vmlinuz_long or die "can not find kernel $kernel\n"; if ($live->{system}{gfxboot}) { mount_system_fs($live); #- would be run by bootloader::add_boot_splash and make-boot-splash, but not called when we don't generate an initrd run_({ root => $root }, '/usr/share/bootsplash/scripts/switch-themes', '-u'); #- grub-gfxmenu would be run by bootloader::write_grub from DrakX run_({ root => $root }, '/usr/sbin/grub-gfxmenu', '--update-gfxmenu'); my $gfxmenu = $root . '/boot/gfxmenu'; if (-e $gfxmenu) { my $boot_dir = $live->get_builddir . $live->{prefix}{build}{boot}; mkdir_p($boot_dir); cp_f($gfxmenu, $boot_dir); } else { warn "no gfxmenu file\n"; } umount_system_fs($live); } # this will copy (among other things) the gfxboot theme to the media # so this must be done before the creating the bootloader since the code # in there may check if a bootlogo is present or not create_syslinux_msg_files($live); if (need_media_specific_boot($live)) { create_media_bootloader($live); } else { create_classical_bootloader($live); } create_efi_boot_image($live) if $live->{settings}{arch} eq 'x86_64'; } sub remove_unneeded_bootlogo_locales { use File::Temp; use Cwd; my ($bootlogo, @locales) = @_; $bootlogo = Cwd::realpath($bootlogo); -f $bootlogo or return; my $cwd = Cwd::getcwd(); my $tempdir = File::Temp::tempdir("mgagfxbootXXXX", CLEANUP => 1); chdir $tempdir; !system("cpio -id < $bootlogo") or return; # Make sure we include the en locale push @locales, 'en'; my @kept_locales; foreach my $file (glob "*.tr") { if (!any { $file =~ /^$_\.tr$/ } @locales) { unlink $file; } else { my ($locale_name) = $file =~ /(.*)\.tr$/; push @kept_locales, $locale_name; } } system(qq(echo init 16x16.fnt *.tr |sed "s/ /\\n/g" |cpio -o >$bootlogo)); chdir $cwd; print "gfxboot locales: " . join(" ", @kept_locales) . "\n"; return @kept_locales; } #- forked from bootsplash::themes_read_sysconfig sub get_bootsplash_theme() { my $sysconfig_file = "/etc/sysconfig/bootsplash"; local $_; my %theme; foreach (cat_($::prefix . $sysconfig_file)) { /^THEME=(.*)/ and $theme{name} = $1; } \%theme; } sub create_syslinux_msg_files { my ($live) = @_; my $syslinux_dir = $live->get_builddir . $live->{prefix}{build}{boot} . '/syslinux'; mkdir_p($syslinux_dir); if ($live->{system}{gfxboot}) { my $product = common::parse_LDAP_namespace_structure(cat_($live->get_system_root . '/etc/product.id')); my $default_gfxboot_theme = $product->{distribution}; my $theme = do { local $::prefix = $live->get_system_root; get_bootsplash_theme(); }; print "copying $default_gfxboot_theme gfxboot theme\n"; cp_f(glob_($live->get_system_root . "/usr/share/gfxboot/themes/$default_gfxboot_theme/install/*"), $syslinux_dir); if ($theme->{name} ne $default_gfxboot_theme) { print "copying $theme->{name} gfxboot theme\n"; cp_f(glob_($live->get_system_root . "/usr/share/gfxboot/themes/$theme->{name}/*"), $syslinux_dir); } my $bootlogo = $syslinux_dir . '/bootlogo'; warn "unable to find gfxboot splash ($bootlogo)\n" if ! -f $bootlogo; my @locales = remove_unneeded_bootlogo_locales($bootlogo, get_langs($live)); output_p($syslinux_dir . '/langs', join("\n", @locales) . "\n"); output_p($syslinux_dir . '/gfxboot.cfg', join("\n", "livecd=1", "mainmenu.pos=210,235", "mainmenu.bar.minwidth=400", "panel.f-key.fg=0x33358c", "", )); } output($live->get_builddir . $live->{prefix}{build}{boot} . '/help.msg', qq( Welcome to $live->{media}{title}! The command line can be used to specify kernel options. $live->{media}{title} )); } sub create_efi_boot_image { my ($live) = @_; my $src_dir = $live->get_builddir . $live->{prefix}{build}{files} . '/EFI'; my $dst_dir = $live->get_builddir . $live->{prefix}{build}{files} . '/boot'; my $label = $live->{media}->get_media_label; run_("sed", "-i", "s/VOLUME_LABEL/$label/g", $src_dir . '/BOOT/grub.cfg'); mkdir_p($dst_dir); my $boot_image = $dst_dir . '/efiboot.img'; eval { rm_rf($boot_image) }; run_("/sbin/mkdosfs", "-F12", "-C", $boot_image, "4096"); run_("mcopy", "-s", "-i", $boot_image, $src_dir, "::"); } sub create_media_bootloader { my ($live) = @_; cp_f($live->get_system_root . '/boot/vmlinuz-' . $live->find_kernel->{version}, $live->get_builddir . $live->{prefix}{build}{boot} . '/vmlinuz'); foreach my $boot ('', @{$live->{media}{extra_boot}}) { my $opts = { boot => $boot }; output($live->get_builddir . $live->{prefix}{build}{boot} . get_syslinux_path($live->{media}, $opts), build_syslinux_cfg($live, $live->{media}, $opts)); } } sub create_classical_bootloader { my ($live) = @_; my $initrd_prefix = "/initrd.img"; my $initrd = $live->get_system_root . $live->get_media_prefix('boot') . $initrd_prefix; my $part_idx = $live->{media}->find_boot_partition_index; my $oem_rescue_idx = $live->{media}->find_partition_index('OEM_RESCUE'); output_p($live->get_system_root . '/boot/grub/menu.lst', build_grub_cfg_raw($live, $live->{media}, -e $initrd && $initrd_prefix, { oem_rescue_idx => $oem_rescue_idx }, $part_idx)); } sub get_iso_master_path { my ($live, $opts) = @_; $live->get_builddir . $live->{prefix}{build}{dist} . '/' . $live->get_name . ($opts->{boot} && "-boot-$opts->{boot}") . '.iso'; } sub create_iso_master { my ($live, $opts) = @_; my $label = $live->{media}->get_media_label or die "the source device must be described by a label\n"; my $dest; unless ($opts->{onthefly}) { $dest = get_iso_master_path($live, $opts); mkdir_p(dirname($dest)); } build_iso_image( $live, $dest, $live->get_builddir . $live->{prefix}{build}{boot} . get_syslinux_path($live->{media}, $opts), $label, $live->get_media_prefix('boot', $opts->{boot}) . '=' . $live->get_builddir . $live->{prefix}{build}{boot}, if_(!$opts->{boot_only}, (map { $live->get_media_prefix('loopbacks', $opts->{boot}) . $_ . '=' . $live->get_builddir . $live->{prefix}{build}{loopbacks} . $_; } list_selected_loopbacks($live)), if_($live->{media}{files}, map { $_ . '=' . $live->get_builddir . $live->{prefix}{build}{files} . '/' . $_; } all($live->get_builddir . $live->{prefix}{build}{files}) ), ), ); } sub build_iso_image { my ($live, $dest, $isolinux_cfg, $label, @opts) = @_; my $progress = MDV::Draklive::Progress->new(100, time()); my $in_progress; autoflush STDOUT 1; local $_; run_foreach(sub { if (/^\s*([0-9.]+)%\s*done,/) { $progress->{current} = int($1); $progress->show(time()); $in_progress = 1; } else { print "\n" if $in_progress; print $_; $in_progress = 0; } }, 'xorriso', '-as', 'mkisofs', '-pad', '-l', '-R', '-J', '-V', $label, #'-A', $application, '-p', $preparer, '-P', $publisher, '-b', 'isolinux/isolinux.bin', '-c', 'isolinux/boot.cat', '-hide-rr-moved', '-no-emul-boot', '-boot-load-size', 4, '-boot-info-table', '-graft-points', if_($live->{settings}{arch} =~ /x86_64/, '-eltorito-alt-boot', '-e', 'boot/efiboot.img', '-no-emul-boot'), if_($dest, '-o', $dest), 'isolinux=' . $live->get_builddir . $live->{prefix}{build}{boot} . '/syslinux', 'isolinux/isolinux.cfg=' . $isolinux_cfg, 'isolinux/isolinux.bin=/usr/lib/syslinux/isolinux.bin', 'isolinux/chain.c32=/usr/lib/syslinux/chain.c32', 'isolinux/gfxboot.c32=/usr/lib/syslinux/gfxboot.c32', 'isolinux/ldlinux.c32=/usr/lib/syslinux/ldlinux.c32', 'isolinux/libcom32.c32=/usr/lib/syslinux/libcom32.c32', 'isolinux/libgpl.c32=/usr/lib/syslinux/libgpl.c32', 'isolinux/libmenu.c32=/usr/lib/syslinux/libmenu.c32', 'isolinux/libutil.c32=/usr/lib/syslinux/libutil.c32', @opts, ) or die "unable to run xorriso\n"; autoflush STDOUT 0; $progress->end; if ($dest) { my $dir = dirname($dest); my $filename = basename($dest); run_('isohybrid', ($live->{settings}{arch} =~ /x86_64/ ? '-u' : '-o 1'), $dest); run_('mgaiso-addmd5', '>', '/dev/null', '2>', '/dev/null', $dest); run_({ chdir => $dir }, 'md5sum', '>', $dest . '.md5', $filename); run_({ chdir => $dir }, 'sha1sum', '>', $dest . '.sha1', $filename); run_({ chdir => $dir }, 'sha512sum', '>', $dest . '.sha512', $filename); run_({ chdir => $dir }, 'date', '>', $dir . '/DATE.txt'); if (my $suffix = $live->get_set_suffix) { if (my ($prefix, $ext) = $dest =~ /(.*)(\.[^.]+)$/) { my $link = $prefix . $suffix . $ext; linkf($dest, $link); print "linked as $link\n"; } } } } #- $opts: #- media: alternate media #- onthefly : if true, the create function must output to stdout sub create_master { my ($live, $opts) = @_; if (my $create = $live->{media}->get_boot_setting('create', $opts)) { $create->($live, $opts); } else { warn "not implemented yet\n"; } } sub maybe_umount_device { my ($device) = @_; run_('umount', $device) if cat_('/proc/mounts') =~ m!^$device\s+!m; } sub clean { my ($live) = @_; # umount filesystem in the live before cleaning umount_external_fs($live); umount_system_fs($live); rm_rf($_) foreach grep { -e $_ } $live->get_builddir, $live->get_system_root; } my @actions = ( { name => 'dump-config', do => \&MDV::Draklive::Config::dump_config }, { name => 'clean', do => \&clean }, { name => 'install', do => \&install_system }, { name => 'post-install', do => \&post_install_system }, { name => 'initrd', do => \&create_initrd }, { name => 'boot', do => \&prepare_bootloader }, { name => 'loop', do => \&create_loopback_files }, { name => 'master', do => \&create_master }, ); my @all = qw(install boot loop master); die "you must be root to run this program\n" if $>; my $live_object = 'MDV::Draklive::Live'->new; my %opts; my $config_root = $MDV::Draklive::Config::default_config_root; my $config_path = $MDV::Draklive::Config::default_config_path; my $settings_path = $MDV::Draklive::Config::default_settings_path; GetOptions( "help" => sub { Pod::Usage::pod2usage('-verbose' => 1) }, "boot-only" => \$opts{boot_only}, "all" => sub { $_->{to_run} = 1 foreach grep { member($_->{name}, @all) } @actions }, (map { $_->{name} => \$_->{to_run} } @actions), "all-regions" => sub { $live_object->{all_regions} = 1 }, "config-root=s" => \$config_root, "config=s" => \$config_path, "settings=s" => \$settings_path, "define=s" => \%{$live_object->{settings}}, ) or Pod::Usage::pod2usage(); require standalone; every { !$_->{to_run} } @actions and Pod::Usage::pod2usage(); MDV::Draklive::Config::read_config($live_object, $config_root, $config_path, $settings_path); MDV::Draklive::Config::check_config($live_object); MDV::Draklive::Config::complete_config($live_object); foreach my $region ($live_object->{all_regions} ? sort(keys %{$live_object->{regions}}) : $live_object->{settings}{region}) { $region and print qq(=== proceeding with region "$region"\n); $live_object->{settings}{region} = $region; foreach (grep { $_->{to_run} } @actions) { print qq(* entering step "$_->{name}"\n); $_->{do}->($live_object, \%opts); print qq(* step "$_->{name}" done\n); } } __END__ =head1 NAME draklive - A live distribution mastering tool =head1 SYNOPSIS draklive [options] Options: --help long help message --install install selected distribution in chroot --boot prepare initrd and bootloader files --loop build compressed loopback files --master build master image --all run all steps, from installation to mastering --clean clean installation chroot and work directory --initrd build initrd only --post-install run post install only (rpms and patches installation) --config-root root directory containing config files and additionnal files defaults to current directory if it contains a configuration file else, "/etc/draklive" is used --config use this configuration file as live description defaults to "config/live.cfg" --settings use this file as live settings (key=value format) defaults to "config/settings.cfg" --define key=value set setting "key" to "value" takes precedence over values from a settings file --all-regions proceed with all configured regions --boot-only copy only boot files (affects master/record steps) Examples: draklive --clean draklive --all draklive --config config/live.cfg --install =head1 OPTIONS =over 8 =item B<--config> Makes draklive use the next argument as a configuration file. This file should contain an hash describing the live distribution, meaning the system (chroot and boot), media (usb, cdrom, nfs), and mount type (simple R/W union, union with squash files). Here's a configuration sample: { settings { repository => '/mnt/ken/2006.0', root => '/chroot/live-move', }, system => { kernel => '2.6.12-12mdk-i586-up-1GB', auto_install => 'config/auto_inst.cfg.pl', patch_install => 'config/patch-2006-live.pl', rpmsrate => 'config/rpmsrate', rpms => [ 'rpms/unionfs-kernel-2.6.12-12mdk-i586-up-1GB-1.1.1.1.20051124.1mdk-1mdk.i586.rpm' ], patches => [ 'patches/halt.loopfs.patch', ], vga_mode => 788, no_initrd => 0, }, media => { storage => 'iso', }, mount => $predefined{mounts}{squash_union} }; =item B<--settings> Makes draklive load the next argument as a file in key=value format into the $live->{settings} hash ($live being the global live configuration hash). Built-in keys: arch: build architecture builddir: directory hosting build files (initrd, loopbacks, images) chroot: directory hosting chrooted installations region: use the matching set of langs from the regions configuration hash repository: path to the Mandriva distribution repository (ftp/http/local) Example keys: desktop media product =back =head1 DESCRIPTION B builds a live distribution according to a configuration file, creates a master image, and optionally installs it on a device. See L =head1 AUTHOR Olivier Blin =cut