diff options
-rwxr-xr-x | draklive1 (renamed from draklive) | 0 | ||||
-rwxr-xr-x | draklive2 | 1853 |
2 files changed, 1853 insertions, 0 deletions
diff --git a/draklive2 b/draklive2 new file mode 100755 index 0000000..ad0ce5c --- /dev/null +++ b/draklive2 @@ -0,0 +1,1853 @@ +#!/usr/bin/perl + +# draklive $Id: draklive 150793 2007-04-05 12:08:47Z blino $ + +# Copyright (C) 2005 Mandriva +# Olivier Blin <oblin@mandriva.com> +# +# 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', <<EOF); +DRAKLIVE_RESIZE=yes +LOOPBACK=$live->{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} <kernel options> + +)); +} + +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 create_tarball { + my ($live) = @_; + run_("tar", "cjf", get_disk_master_prefix($live) . ".tar.bz2", $live->get_system_root); +} + +sub set_device_label { + my ($device, $type, $label) = @_; + if ($type eq 'vfat') { + MDV::Draklive::Utils::mtools_run_('mlabel', '-i', $device, '::' . $label); + } elsif (member($type, 'ext2', 'ext3')) { + run_('e2label', $device, $label); + } else { + die "unable to set label for unsupported media type $type\n"; + } +} + +sub get_cdrom_master_path { + my ($live, $opts) = @_; + $live->get_builddir . $live->{prefix}{build}{dist} . '/' . $live->get_name . ($opts->{boot} && "-boot-$opts->{boot}") . '.iso'; +} + +sub get_cdrom_replicator_path { + my ($live) = @_; + get_disk_replicator_prefix($live) . ".iso"; +} + +sub create_cdrom_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_cdrom_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"; + } + } + } +} + +sub get_disk_master_prefix { + my ($live) = @_; + $live->get_builddir . $live->{prefix}{build}{dist} . '/' . $live->get_name; +} + +sub get_disk_master_path { + my ($live) = @_; + get_disk_master_prefix($live) . '.img'; +} + +sub get_partition_loop { + my ($hd, $part) = @_; + require devices; + my $loop = devices::find_free_loop(); + run_('losetup', '-o', $part->{start} * $common::SECTORSIZE, '-s', $part->{size} * $common::SECTORSIZE, $loop, $hd->{file}) + or die "unable to setup loop device"; + return $loop; +} + +sub get_harddisk_geometry { + my ($media) = @_; + $media->{geom} || { + heads => 16, + sectors => 63, # sectors per track + }; +} + +sub get_hd_from_layout { + my ($media, $dest) = @_; + my $geom = get_harddisk_geometry($media); + my $required_sectors = fold_left { $::a + $::b } map { $_->{size} } @{$media->{partitions}}; + $required_sectors += $geom->{sectors}; # keep one more track + $geom->{cylinders} = POSIX::ceil($required_sectors / ($geom->{sectors} * $geom->{heads})); + my $total_sectors = $geom->{cylinders} * $geom->{heads} * $geom->{sectors}; + bless { + totalsectors => $total_sectors, + geom => $geom, + file => $dest, + }, 'partition_table::dos'; +} + +sub get_hd_from_file { + my ($media, $file) = @_; + my $hd = bless { + geom => get_harddisk_geometry($media), + file => $file, + }, 'partition_table::dos'; + partition_table::read($hd); + return $hd; +} + +sub supplement_media_partitions { + my ($media, $hd) = @_; + #- try to find additional partition details (start, device) + #- by matching actual partition table and partitions list + my @all_parts = partition_table::get_normal_parts($hd); + foreach my $idx (0 .. $#all_parts) { + $media->{partitions}[$idx]{$_} = $all_parts[$idx]{$_} foreach qw(start device); + } +} + +sub set_part_real_device { + my ($hd, $part) = @_; + #- FIXME: find a better way to compute mmcblk device path + my $ext = $hd->{file} =~ m!^/dev/mmcblk! ? 'p' : ''; + $part->{real_device} = -f $hd->{file} ? get_partition_loop($hd, $part) : ($hd->{file} . $ext . $part->{device}); +} + +sub allocate_master { + my ($media, $opts) = @_; + + $media->supplement_slash_size($opts->{slash_size}) if $opts->{slash_size}; + my $hd = get_hd_from_layout($media, $opts->{device}); + + mkdir_p(dirname($opts->{device})); + MDV::Draklive::Utils::device_allocate_file($opts->{device}, $hd->{totalsectors} * $common::SECTORSIZE); +} + +sub format_master { + my ($media, $opts) = @_; + + $media->supplement_slash_size($opts->{slash_size}) if $opts->{slash_size}; + + my $hd = get_hd_from_layout($media, $opts->{device}); + partition_table::raw::zero_MBR($hd); + + #- FIXME: maybe use fsedit::allocatePartitions to factorize even more? + foreach my $part (@{$media->{partitions}}) { + my $hole = find { fs::type::isEmpty($_) && $_->{size} >= $part->{size} } partition_table::get_normal_parts_and_holes($hd) + or die "not enough room for $part->{mntpoint}"; + $part->{start} = $hole->{start}; + fs::type::set_fs_type($part, $part->{fs_type}); + partition_table::add($hd, $part, 'Primary'); + } + + print "writing partition table\n"; + partition_table::write($hd); + #- FIXME: move out from diskdrake::interactive::write_partitions to partition_table::write ? + run_program::run('udevadm', 'settle'); + + my $inode_size = $media->get_media_setting('inode_size'); + foreach my $part (@{$media->{partitions}}) { + set_part_real_device($hd, $part); + MDV::Draklive::Utils::device_mkfs($part->{real_device}, $part->{fs_type}, $part->{device_LABEL}, $inode_size) + or die "unable to format $part->{real_device} in $hd->{file}\n"; + devices::del_loop($part->{real_device}) if -f $hd->{file}; + } +} + +sub format_disk { + my ($live, $opts) = @_; + local $opts->{slash_size} = guess_disk_master_size($live); + format_master($live->{media}, $opts); +} + +sub guess_disk_master_size { + my ($live) = @_; + + # slash_size: + @{$live->{mount}{dirs} || []} ? + (directory_usage($live->get_builddir . $live->{prefix}{build}{loopbacks}, 'apparent') + + directory_usage($live->get_builddir . $live->{prefix}{build}{boot}) + + directory_usage($live->get_builddir . $live->{prefix}{build}{files}) + ) : + directory_usage($live->get_system_root); +} + +sub create_disk_master { + my ($live, $opts) = @_; + local $opts->{slash_size} = guess_disk_master_size($live); + local $opts->{device} = get_disk_master_path($live); + allocate_master($live->{media}, $opts); + format_master($live->{media}, $opts); + record_master($live, $opts); +} + +#- $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 format_cdrom_device { + my ($live, $opts) = @_; + run_('wodim', '-v', 'dev=' . get_media_device($live, $opts), "blank=fast"); +} + +#- $opts: +#- media: alternate media +sub format_device { + my ($live, $opts) = @_; + + get_media_device($live, $opts) or die "no device defined in media configuration\n"; + if (my $format = $live->{media}->get_boot_setting('format', $opts)) { + $format->($live, $opts); + } else { + warn "not implemented yet\n"; + } +} + +sub record_cdrom_path { + my ($live, $path, $opts) = @_; + my $device = get_media_device($live, $opts) + or die "no device defined in media configuration\n"; + + #- CD-Rom images can be hybrid, thus handle recording on both CD-Rom and disks + $device = basename(expand_symlinks($device)); + my $sysfs_device = "/sys/block/$device/capability"; + #- GENHD_FL_CD is 8 (include/linux/genhd.h) + my $is_cdrom = !-e $sysfs_device || hex(cat_($sysfs_device)) & 8; + + if ($is_cdrom) { + my $src = $opts->{onthefly} ? '-' : $path; + run_('wodim', '-v', 'dev=' . $device, $src); + } else { + run_('dd', if_(!$opts->{onthefly}, "if=$path"), "of=$device", "bs=2M"); + } +} + +sub record_cdrom_master { + my ($live, $opts) = @_; + record_cdrom_path($live, get_cdrom_master_path($live, $opts), $opts); +} + +sub record_cdrom_replicator { + my ($live, $opts) = @_; + record_cdrom_path($live, get_cdrom_replicator_path($live), $opts); +} + +sub rsync_delete_options { + my ($opts) = @_; + $opts->{keep_files} ? () : '--delete'; +} + +sub install_grub_to_image { + my ($live, $media, $img, $opts) = @_; + my $media_boot = $live->get_media_prefix('boot', $opts->{boot}); + my $grub_dir = "$media_boot/grub"; + my $grub_script = $grub_dir . "/install.sh"; + my $grub_src = first(glob_($live->get_system_root . "/lib/grub/*-mageia")); + mkdir_p($live->{mnt} . $grub_dir); + cp_af(glob_("$grub_src/*"), $live->{mnt} . $grub_dir); + + my $part_idx = $media->find_boot_partition_index; + my $grub_prefix = $media->find_partition_index('/boot') ? "/grub" : $grub_dir; + + open(my $grub, "| /sbin/grub --batch --no-floppy"); + # using disk loopback fails, have to use image path + print $grub <<EOF; +device (hd0) $img +root (hd0,$part_idx) +setup --prefix=$grub_prefix (hd0) +quit +EOF + close($grub) or die "unable to run grub\n"; + + output($live->{mnt} . $grub_script, <<EOG); +grub --device-map=$media_boot/grub/device.map --batch <<EOF +root (hd0,$part_idx) +setup --stage2=$media_boot/grub/stage2 (hd0) +quit +EOF +EOG + + chmod 0755, $live->{mnt} . $grub_script; +} + +sub install_disk_bootloader { + my ($live, $media, $boot_device, $opts) = @_; + + return if $live->{system}{skip_bootloader_install}; + + my $media_boot = $live->get_media_prefix('boot', $opts->{boot}); + my $device = get_media_device($live, $opts); + my $bootloader = $media->get_boot_setting('bootloader', $opts); + + member($bootloader, 'grub', 'syslinux') or die "no bootloader defined in media configuration\n"; + if ($bootloader eq 'syslinux') { + cp_f($live->get_builddir . $_, $live->{mnt}) foreach map { + $live->{prefix}{boot} . $_; + } get_syslinux_path($media, $opts), $media->get_initrd_path, @syslinux_boot_files; + } elsif ($bootloader eq 'grub') { + if (need_media_specific_boot($live) || $opts->{force_bootloader_config}) { + #- FIXME: add get_grub_path (when building boot configuration files) + # and get_bootloader_path (when copying) + mkdir_p($live->{mnt} . $media_boot . '/grub'); + cp_f($live->get_builddir . $live->{prefix}{build}{boot} . '/gfxmenu', $live->{mnt} . $media_boot) if $live->{system}{gfxboot}; + output_p($live->{mnt} . $media_boot . '/grub/menu.lst', build_grub_cfg($live, $media, $opts, $boot_device)); + } + } + + if (-b $boot_device) { + if ($bootloader eq 'syslinux') { + #- use syslinux -s, "safe, slow and stupid" version of SYSLINUX, unless specified otherwise + run_('syslinux', if_(!$media->{fast_syslinux}, '-s'), $boot_device) + or die "unable to run syslinux on $device\n"; + } elsif ($bootloader eq 'grub') { + install_grub_to_image($live, $media, $device, $opts); + } + } else { + warn "not running $bootloader on non block device $device\n"; + } +} + +sub record_usb_master { + my ($live, $opts) = @_; + my $media = $live->{media}; + my $media_boot = $live->get_media_prefix('boot', $opts->{boot}); + my $media_loopbacks = $live->get_media_prefix('loopbacks', $opts->{boot}); + + my $main_device = get_media_device($live, $opts) + or die "unable to find recording device (missing label? try with --device <device>)\n"; + + my $hd = get_hd_from_file($media, $main_device); + supplement_media_partitions($media, $hd); + + my $slash_idx = $media->find_partition_index('/'); + my $slash = $media->{partitions}[$slash_idx]; + set_part_real_device($hd, $slash); + + if (my $label = !$opts->{boot_only} && $media->get_media_label) { + set_device_label($slash->{real_device}, $media->get_media_setting('fs'), $label); + } + + mkdir_p($live->{mnt}); + run_('mount', $slash->{real_device}, $live->{mnt}) + or die "unable to mount $slash->{real_device}\n"; + + rm_rf($live->{mnt} . $media_boot) if -e $live->{mnt} . $media_boot; + cp_af($live->get_builddir . $live->{prefix}{build}{boot}, $live->{mnt} . $media_boot); + + install_disk_bootloader($live, $media, $slash->{real_device}, $opts); + + do { + my $loopbacks_source = $live->get_builddir . $live->{prefix}{build}{loopbacks} . '/'; + my $total = directory_usage($loopbacks_source); + my $list_file = tmpnam(); + output_p($list_file, map { ".$_\n" } list_selected_loopbacks($live)); + local $/ = "\r"; + my $r = run_foreach(update_progress_rsync($live, $total), + 'rsync', '-vdP', '--inplace', '--files-from=' . $list_file, rsync_delete_options($opts), + $loopbacks_source, $live->{mnt} . $media_loopbacks, + ); + unlink $list_file; + if (!$r) { + run_('umount', $slash->{real_device}); + maybe_umount_device($slash->{real_device}); + devices::del_loop($slash->{real_device}) if -f $hd->{file}; + die "unable to copy loopback files\n"; + } + + cp_af(glob_($live->get_builddir . $live->{prefix}{build}{files} . '/*'), $live->{mnt}); + } unless $opts->{boot_only}; + + my @hidden_files = map { basename($_) } glob_($live->{mnt} . "/.*"), glob_($live->{mnt} . "/autorun.*"); + + run_('umount', $slash->{real_device}); + maybe_umount_device($slash->{real_device}); + + if ($media->get_media_setting('fs') eq 'vfat') { + MDV::Draklive::Utils::mtools_run_('mattrib', '+h', '-i', $slash->{real_device}, '::' . $_) foreach @hidden_files; + MDV::Draklive::Utils::mtools_run_('mattrib', '+r', '+s', '-/', '-i', $slash->{real_device}, '::' . $_) + foreach $media_boot, $media_loopbacks; + } + + devices::del_loop($slash->{real_device}) if -f $hd->{file}; +} + +sub record_harddisk_master { + my ($live, $opts) = @_; + + my $media = $live->{media}; + + my $main_device = get_media_device($live, $opts) + or die "unable to find recording device (missing label? try with --device <device>)\n"; + + my $hd = get_hd_from_file($media, $main_device); + supplement_media_partitions($media, $hd); + my @partitions = grep { $_->{mntpoint} =~ m!^/! } @{$media->{partitions}}; + + mkdir_p($live->{mnt}); + foreach my $part (sort { $a->{mntpoint} cmp $b->{mntpoint} } @partitions) { + set_part_real_device($hd, $part); + my $mnt = $live->{mnt} . $part->{mntpoint}; + mkdir_p($mnt); + run_('mount', $part->{real_device}, $mnt) + or die "unable to mount $part->{real_device}\n"; + } + + my $r = 1; + do { + my $source = $live->get_system_root; + my $total = directory_usage($source); + local $/ = "\r"; + $r = run_foreach(update_progress_rsync($live, $total), 'rsync', rsync_delete_options($opts), '-a', $source . '/', $live->{mnt}) + or last; + } unless $opts->{boot_only}; + + my $boot_idx = $media->find_boot_partition_index; + my $boot_part = $media->{partitions}[$boot_idx]; + install_disk_bootloader($live, $media, $boot_part->{real_device}, $opts); + + foreach my $part (sort { $b->{mntpoint} cmp $a->{mntpoint} } @partitions) { + run_('umount', $part->{real_device}); + maybe_umount_device($part->{real_device}); + devices::del_loop($part->{real_device}) if -f $hd->{file}; + } + + $r or die "unable to copy system files\n"; + + record_oem_rescue($live, $opts) if $live->{oem_rescue}; +} + +#- $opts: +#- onthefly : if true, the record function must read from stdin +sub record_master { + my ($live, $opts) = @_; + + if (my $record = $live->{media}->get_boot_setting('record', $opts)) { + $record->($live, $opts); + } else { + warn "not implemented yet\n"; + } +} + +#- $opts: +#- onthefly : if true, the record function must read from stdin +sub record_replicator { + my ($live, $opts) = @_; + + my $replicator_media = $live->{replicator}{media} or die "no replicator media"; + if (my $record_replicator = $replicator_media->get_boot_setting('record_replicator', $opts)) { + $record_replicator->($live, $opts); + } else { + warn "not implemented yet\n"; + } +} + +sub pipe_subs { + my ($writer, $reader) = @_; + my ($r, $w) = POSIX::pipe; + if (my $pid = fork()) { + POSIX::close($w) or die "couldn't close: $!\n"; + my $stdin = POSIX::dup(0) or die "couldn't dup: $!\n"; + POSIX::dup2($r, 0) or die "couldn't dup2: $!\n"; + POSIX::close($r); + $reader->(); + POSIX::close(0) or warn "writer exited $?\n"; + POSIX::dup2($stdin, 0) or die "couldn't dup2: $!\n"; + waitpid($pid, 0); + } else { + POSIX::close($r) or die "couldn't close: $!\n"; + #- don't screw up reader + POSIX::dup2(POSIX::open('/dev/null', &POSIX::O_WRONLY), 2) or die "couldn't dup2: $!\n"; + POSIX::dup2($w, 1) or die "couldn't dup2: $!\n"; + POSIX::close($w); + $| = 1; #- autoflush write + exit !$writer->(); + } +} + +sub record_onthefly { + my ($live, $opts) = @_; + + my $record = $live->{media}->get_storage_setting('record'); + unless ($record) { + warn "not implemented yet\n"; + return; + } + if (my $create = $live->{media}->get_storage_setting('record_needs_master') && $live->{media}->get_storage_setting('create')) { + local $opts->{onthefly} = 1; + #- pipe creation step to recording step + pipe_subs(sub { $create->($live, $opts) }, + sub { $record->($live, $opts) }); + } else { + #- no creation step, record directly + $record->($live, $opts); + } +} + +sub need_compressed_image { + my ($live) = @_; + #- compress image if not having loopbacks already + !to_bool(list_selected_loopbacks($live)); +} + +sub get_disk_image_path { + my ($live) = @_; + if ($live->{settings}{compression_method} eq 'gzip') { + get_disk_master_path($live) . if_(need_compressed_image($live), '.gz'); + } + else { + get_disk_master_path($live) . if_(need_compressed_image($live), '.bz2'); + } +} + +sub create_disk_image { + my ($live) = @_; + if (!need_compressed_image($live)) { + warn "already using loopback: skipping image creation\n"; + return; + } + my $master = get_disk_master_path($live); + my $dest = get_disk_image_path($live); + mkdir_p(dirname($dest)); + + if ($live->{settings}{compression_method} eq 'gzip') { + run_('gzip', '>', $dest, '-f', '-c', '--fast', $master); + } + else { + run_('bzip2', '>', $dest, '-f', '-k', '-c', $master); + } +} + +sub create_image { + my ($live) = @_; + + if (my $create = $live->{media}->get_media_setting('image')) { + $create->($live); + } else { + warn "not implemented yet\n"; + } +} + +sub create_vm_image { + my ($live) = @_; + + my $vm_type = $live->{settings}{vm_type}; + if (!$vm_type) { + warn "no vm_type has been set in settings, skipping"; + return; + } + + if (!$live->{media}->get_media_setting('image')) { + warn "not implemented yet\n"; + return; + } + + my $master = get_disk_master_path($live); + if (!-f $master) { + warn "no master image, skipping\n"; + } + + my $vm_image = get_disk_master_prefix($live) . ".$vm_type"; + run_("qemu-img", "convert", "-O", $vm_type, $master, $vm_image); +} + +sub get_rescue_files { + my ($live, $rescue_opts, $o_extra_files) = @_; + my $media_boot = $live->get_media_prefix('boot'); + my $initrd = $media_boot . $rescue_opts->{media}->get_initrd_path; + my @stage2_files = $live->{settings}{replicator_type} eq 'drakx' ? ( + '/usr/lib/drakx-installer-stage2/install/stage2/mdkinst.sqfs', + $live->get_system_root . '/bin/dd', + ) : '/usr/lib/drakx-installer-rescue/rescue.sqfs'; + ( + '/usr/lib/drakx-installer-images/isolinux/alt0/vmlinuz' => $media_boot . '/vmlinuz', + '/usr/lib/drakx-installer-images/isolinux/alt0/all.rdz' => $initrd, + (map { $_ => '/install/stage2/' } @stage2_files), + @{$o_extra_files || []}, + #- FIXME: factorize with copy_files_to to handle glob + (map { $live->{settings}{config_root} . '/' . $_->[0] => '/' . $_->[1] } @{$rescue_opts->{files} || []}), + ); +} + +sub record_rescue_files { + my ($mnt, $device, $rescue_files) = @_; + my $failed; + foreach (group_by2(@$rescue_files)) { + my ($src, $dest) = @$_; + $dest = $mnt . $dest; + mkdir_p($dest =~ m!/$! ? $dest : dirname($dest)); + if (!run_('rsync', '-vdP', '--inplace', $src, $dest)) { + $failed = 1; + last; + } + } + + #- FIXME + chmod 0755, $mnt . '/' . 'oem-rescue.sh'; + + if ($failed) { + run_('umount', $mnt); + maybe_umount_device($device); + die "unable to copy rescue files\n"; + } +} + +sub record_oem_rescue { + my ($live, $opts) = @_; + + my $media = $live->{media}; + my $oem_rescue_idx = $media->find_partition_index('OEM_RESCUE'); + defined $oem_rescue_idx or die "no OEM_RESCUE partition"; + + my $main_device = get_media_device($live, $opts) + or die "unable to find recording device (missing label? try with --device <device>)\n"; + + my $hd = get_hd_from_file($media, $main_device); + supplement_media_partitions($media, $hd); + + my $oem_rescue = $media->{partitions}[$oem_rescue_idx]; + set_part_real_device($hd, $oem_rescue); + + mkdir_p($live->{mnt}); + run_('mount', $oem_rescue->{real_device}, $live->{mnt}) + or die "unable to mount $oem_rescue->{real_device}\n"; + + record_rescue_files($live->{mnt}, $oem_rescue->{real_device}, [ get_rescue_files($live, $live->{oem_rescue}) ]); + + run_('umount', $oem_rescue->{real_device}); + maybe_umount_device($oem_rescue->{real_device}); + devices::del_loop($oem_rescue->{real_device}) if -f $hd->{file}; +} + +sub get_disk_replicator_prefix { + my ($live) = @_; + $live->get_builddir . $live->{prefix}{build}{dist} . '/' . $live->get_name . '.rest'; +} + +sub get_disk_replicator_path { + my ($live) = @_; + get_disk_replicator_prefix($live) . ".img"; +} + +sub get_disk_replicator_files { + my ($live) = @_; + get_rescue_files($live, $live->{replicator}, [ get_disk_image_path($live) => '/images/' ]); +} + +sub get_disk_replicator_images_list { + my ($live) = @_; + my $master_path = get_disk_master_path($live); + my $master_size = (stat($master_path))[7]; + "EN,English," . basename(get_disk_image_path($live)) . ",on,$master_size\n"; +} + +sub create_usb_replicator { + my ($live, $opts) = @_; + my %files = get_disk_replicator_files($live); + local $opts->{slash_size} = fold_left { $::a + $::b } map { directory_usage($_, 'apparent') } keys(%files); + local $opts->{device} = get_disk_replicator_path($live); + allocate_master($live->{replicator}{media}, $opts); + format_master($live->{replicator}{media}, $opts); + record_usb_replicator($live, $opts); +} + +sub record_usb_replicator { + my ($live, $opts) = @_; + + my $media = $live->{replicator}{media}; + my $media_boot = $live->get_media_prefix('boot'); + + $opts->{append} ||= $live->{replicator}{append}; + my $main_device = get_media_device($live, $opts) + or die "unable to find recording device (missing label? try with --device <device>)\n"; + + my $hd = get_hd_from_file($media, $main_device); + supplement_media_partitions($media, $hd); + + my $slash_idx = $media->find_partition_index('/'); + my $slash = $media->{partitions}[$slash_idx]; + set_part_real_device($hd, $slash); + + mkdir_p($live->{mnt}); + run_('mount', $slash->{real_device}, $live->{mnt}) + or die "unable to mount $slash->{real_device}\n"; + rm_rf($live->{mnt} . $media_boot) if -e $live->{mnt} . $media_boot; + { + local $opts->{force_bootloader_config} = 1; + install_disk_bootloader($live, $media, $slash->{real_device}, $opts); + } + + record_rescue_files($live->{mnt}, $slash->{real_device}, [ get_disk_replicator_files($live) ]); + output_p($live->{mnt} . "/images/list", get_disk_replicator_images_list($live)); + + run_('umount', $slash->{real_device}); + maybe_umount_device($slash->{real_device}); + devices::del_loop($slash->{real_device}) if -f $hd->{file}; +} + +sub create_cdrom_replicator { + my ($live, $opts) = @_; + + # FIXME: factorize with usb replicator, write in builddir/replicator/images-list + my $images_list = "/tmp/images-replicator.list"; + output_p($images_list, get_disk_replicator_images_list($live)); + + # FIXME: write in builddir/replicator/syslinux.cfg + my $syslinux_cfg = "/tmp/syslinux-replicator.cfg"; + local $opts->{append} = $live->{replicator}{append}; + output_p($syslinux_cfg, build_syslinux_cfg($live, $live->{replicator}{media}, $opts)); + + my $label = $live->{replicator}{media}->get_media_label or die "the source device must be described by a label\n"; + my $dest = get_cdrom_replicator_path($live); + build_iso_image( + $live, + $dest, + $syslinux_cfg, + $label, + $live->get_media_prefix('boot', $opts->{boot}) . '/syslinux=' . $live->get_builddir . $live->{prefix}{build}{boot} . '/syslinux', + "/images/list=$images_list", + (map { + my ($src, $dest) = @$_; + my $dest_file = $dest =~ m!/$! ? $dest . basename($src) : $dest; + $dest_file . "=" . $src; + } group_by2(get_disk_replicator_files($live))), + ); +} + +sub create_replicator { + my ($live, $opts) = @_; + + if (my $create = $live->{replicator}{media} && $live->{replicator}{media}->get_media_setting('replicator')) { + $create->($live, $opts); + } else { + warn "not implemented yet\n"; + } +} + +sub update_progress_rsync { + my ($live, $total) = @_; + my $all_files = 0; + my $current_file = 0; + $live->{update_progress} ? sub { + if (/^\s*(\d+)\s+\d+%\s+/) { + $current_file = $1; + $live->{update_progress}->(undef, $all_files + $current_file, $total); + } + if (/(?:^|\n)\S+/) { + $all_files += $current_file; + $current_file = 0; + } + } : sub { print }; +} + +sub copy_wizard { + my ($live) = @_; + + MDV::Draklive::Config::complete_config($live); + $live->{system}{vga_mode} = 788 if !defined $live->{system}{vga_mode}; + my $live_media = $live->{prefix}{live}{mnt} . $live->{prefix}{media}{mnt}; + + require interactive; + require wizards; + my $in = 'interactive'->vnew('su'); + my (@available_storage, @available_devices); + my ($storage, $device, $format); + my %source_types = ( + live => N("Use current live system"), + file => N("Select a file"), + ); + my ($source_type, $source_path); + my $media_boot = $live->get_media_prefix('boot'); + my $media_loopbacks = $live->get_media_prefix('loopbacks'); + my $source_is_mounted = sub { -d ($live->{settings}{builddir} . $media_boot) }; + my $umount_source = sub { + if ($source_type ne 'live' && $source_is_mounted->()) { + run_('umount', $live->{settings}{builddir}); + rmdir($live->{settings}{builddir}); + } + }; + my $w; + $w = wizards->new({ + name => N("Live system copy wizard"), + pages => { + welcome => { + name => N("Welcome to the live system copy wizard"), + no_back => 1, + next => 'source', + }, + source => { + name => N("Which live system do you want to copy?"), + data => [ if_(-d ($live_media . $media_loopbacks), + { type => 'list', val => \$source_type, + list => sort(keys(%source_types)), + format => sub { $source_types{$_[0]} } }), + { type => 'file', val => \$source_path, + disabled => sub { $source_type eq 'live' } } ], + pre => $umount_source, + complete => sub { + if ($source_type eq 'live') { + $live->{settings}{builddir} = $live_media; + } else { + $live->{settings}{builddir} = File::Temp::tempdir(); + if (!run_('mount', '-o', 'loop', $source_path, $live->{settings}{builddir})) { + $in->ask_warn(N("Error"), N("Unable to use selected file")); + return 1; + } + } + 0; + }, + post => sub { + my $boot = $live->{settings}{builddir} . $media_boot; + @available_storage = sort(grep { -d "$boot/$_" && exists $MDV::Draklive::Storage::storage_types{$_}{detect} } all($boot)); + if (@available_storage == 1) { + $storage = $available_storage[0]; + return 'device'; + } + return 'storage'; + } + }, + storage => { + name => N("Please select the medium type"), + data => [ { type => 'list', allow_empty_list => 1, + val => \$storage, list => \@available_storage } ], + next => 'device', + }, + device => { + name => N("Please select the device that will contain the new live system"), + pre => sub { + my %devices = map { $_->{device} => $_ } $MDV::Draklive::Storage::storage_types{$storage}{detect}->(); + $_->{formatted_name} = $_->{usb_description} || $_->{info} || $_->{device} foreach values %devices; + @available_devices = (); + require fs::proc_partitions; + foreach (fs::proc_partitions::read([ values %devices ])) { + if ($_->{rootDevice} && exists $devices{$_->{rootDevice}}) { + my $description = $devices{$_->{rootDevice}}{usb_description} || $devices{$_->{rootDevice}}{info}; + $_->{formatted_name} = $description ? "$description ($_->{device})" : $_->{device}; + push @available_devices, $_; + } + } + delete $devices{$_->{rootDevice}} foreach @available_devices; + unshift @available_devices, map { $devices{$_} } sort keys %devices; + undef $device; + }, + data => [ { type => 'list', allow_empty_list => 1, + val => \$device, , list => \@available_devices, + format => sub { $_[0]{formatted_name} } }, + { text => N("Format selected device"), val => \$format, type => 'bool' } ], + complete => sub { + return 0 if defined $device; + $in->ask_warn(N("Error"), N("You must select a device!")); + 1; + }, + post => sub { + (my $_wait, $live->{update_progress}) = $in->wait_message_with_progress_bar; + do { + local $::isInstall = 1; # quick hack to embed the wait message + $live->{update_progress}->(N("Copying in progress")); + }; + eval { + my $opts = { media => { storage => $storage, device => '/dev/' . $device->{device} } }; + format_device($live, $opts) if $format; + record_onthefly($live, $opts); + }; + delete $live->{update_progress}; + if (my $error = $@) { + $in->ask_warn(N("Error"), $error); + $w->{pages}{device}{end} = 1; + } + return "end"; + }, + }, + end => { + name => N("Congratulations") . "\n\n" . N("Your live system is now copied."), + no_back => 1, + end => 1, + }, + } + }); + $w->process($in); + $umount_source->(); + $in->exit; +} + +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 => 'tarball', do => \&create_tarball }, + { name => 'loop', do => \&create_loopback_files }, + { name => 'master', do => \&create_master }, + { name => 'image', do => \&create_image }, + { name => 'vm-image', do => \&create_vm_image }, + { name => 'replicator', do => \&create_replicator }, + { name => 'format', do => \&format_device }, + { name => 'record', do => \&record_master }, + { name => 'record-onthefly', do => \&record_onthefly }, + { name => 'record-replicator', do => \&record_replicator }, +); +my @all = qw(install boot loop master image vm-image replicator); + +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) }, + "copy-wizard" => \$live_object->{copy_wizard}, + "keep-files" => \$opts{keep_files}, + "boot-only" => \$opts{boot_only}, + "boot-image=s" => sub { $opts{boot} = $_[1]; $opts{boot_only} = 1 }, + "all" => sub { $_->{to_run} = 1 foreach grep { member($_->{name}, @all) } @actions }, + (map { $_->{name} => \$_->{to_run} } @actions), + "device=s" => sub { $opts{device} = $_[1] }, + "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; +if ($live_object->{copy_wizard}) { + copy_wizard($live_object); +} else { + 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 + --tarball build chroot tarball + --loop build compressed loopback files + --master build master image + --image build compressed master image + --replicator build replicator image (to dump master on systems) + + --all run all steps, from installation to mastering + + --clean clean installation chroot and work directory + + --device <dev> use this device for live recording (not needed + if the device already has the required label) + --format format selected device + --record record live on selected media + --record-onthefly record live by creating master from loopback files + on the fly + --keep-files keep existing files on media when recording + --record-replicator + record replicator on selected media + + --initrd build initrd only + --post-install run post install only (rpms and patches installation) + + --config-root <dir> + root directory containing config files and additionnal files + defaults to current directory if it contains a configuration file + else, "/etc/draklive" is used + + --config <file> use this configuration file as live description + defaults to "config/live.cfg" + + --settings <file> 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 + + --copy-wizard run the copy wizard + + --boot-only copy only boot files + (affects master/record steps) + + --boot-image <method> + create a boot image for the selected method + (affects master/record steps, implies --boot-only) + +Examples: + + draklive --clean + + draklive --all + + draklive --record --device /dev/sdb1 + + 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 => 'cdrom', + }, + 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<draklive> builds a live distribution according to a +configuration file, creates a master image, +and optionally installs it on a device. + +See L<http://qa.mandriva.com/twiki/bin/view/Main/DrakLive> + +=head1 AUTHOR + +Olivier Blin <oblin@mandriva.com> + +=cut |