#!/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 list_modules;
use fs;
use modules;
use run_program;
use Getopt::Long;
use Pod::Usage;
use File::Temp;
use IO::Handle; #- autoflush
use MDV::Draklive::Utils;
use MDV::Draklive::Live;
use MDV::Draklive::Progress;
use MDV::Draklive::Loopback;
use MDV::Draklive::Initrd;
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) = @_;
    join(' ',
         if_(!need_media_specific_boot($live),
             'root=' . $live->{media}->get_media_source_for_nash),
         if_($live->{system}{vga_mode},
             'splash=silent',
             'vga=' . $live->{system}{vga_mode}),
         ($opts->{append} ? $opts->{append} : $live->{system}{append} ? $live->{system}{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;
    my $has_bootlogo = $live->{system}{gfxboot} &&
      -e $live->get_builddir . $live->{prefix}{build}{boot} . '/syslinux/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.msg",
         $has_bootlogo ? "ui gfxboot.com $bootlogo" : "display $help",
         (map {
             my ($name, $cmdline) = @$_;
             $name =~ s/\s/_/g;
	     if ($name eq "harddisk") {
		 "label " . $name,
		 "    localboot 0x80";
	     } else {
		"label " . ($name || $title),
		"    kernel $kernel",
		"    append initrd=$initrd $append $cmdline";
	    }
         } group_by2('' => '', @{$live->{system}{boot_entries}})),
         "",
     );
}

sub build_grub_cfg {
    my ($live, $media, $opts, $device) = @_;
    #- FIXME? first partition is hardcoded for loopback (master images)
    my ($part_nb) = $device =~ m!/dev/loop! ? 1 : $device =~ /(\d+)$/;
    my $initrd = $media->get_initrd_path;
    build_grub_cfg_raw($live, $media, $initrd, $opts, $part_nb);
}

sub build_grub_cfg_raw {
    my ($live, $media, $initrd, $opts, $part_nb) = @_;
    #- FIXME: use the bootloader module from drakx
    my $grub_part = "(hd0" . (defined $part_nb ? "," . ($part_nb-1) : "") . ")";
    my $boot = $live->get_media_prefix('boot'); #- FIXME handle boot media
    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"),
             "initrd " . $boot . $initrd;
         } group_by2('' => '', @{$live->{system}{boot_entries}})),
         "",
     );
}

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, %{$live->{system}{install_env}});
    $ENV{DRAKLIVE_LANGS} = join(':', get_langs($live));
    $ENV{DRAKLIVE_REGION} = $live->{settings}{region};
    run_({ setarch => $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_f(@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 post_install_system {
    my ($live) = @_;

    my $previous_umask = umask;
    #- workaround buggy installation of directories that are not owned by any packages
    umask 022;

    #- 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, setarch => $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, setarch => $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, setarch => $live->{settings}{arch} },
	 'sh', '-c', "rpm -qa $erase | xargs rpm -e ") if $erase;

    run_({ root => $live->get_system_root }, 'chkconfig', '--del', $_) foreach @{$live->{system}{disable_services}};

    #- 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
    substInFile { undef $_ if /^[^#]/ } $live->get_system_root . $_
      foreach qw(/etc/fstab /etc/mtab /etc/modprobe.conf /etc/modprobe.preload /etc/iftab /etc/shorewall/interfaces /etc/mdadm.conf /etc/resolv.conf);
    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;
    }

    output_with_perm($live->get_system_root . '/etc/fstab', 0644,
                     $live->{mount}{overlay}
                     ? "none / $live->{mount}{overlay} rw 0 0\n"
                     : $live->{media}->get_media_setting('source') . " / " . $live->{media}->get_media_setting('fs') . " defaults 1 1\n"
                 );

    #- 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_({ setarch => $live->{settings}{arch} },
	 "chroot", $live->get_system_root, "bash", "-c", $live->{system}{postInstall}) if $live->{system}{postInstall};

    write_dist_lists($live);

    umask $previous_umask;
}

sub write_dist_lists {
    my ($live) = @_;

    my $lst = $live->get_builddir . $live->{prefix}{build}{dist} . '/' . $live->get_name . '.lst';
    mkdir_p(dirname($lst));
    run_("chroot " . $live->get_system_root . " rpm -qa | sort > " . $lst);

    my $full = $live->get_builddir . $live->{prefix}{build}{dist} . '/' . $live->get_name . '.lst.full';
    mkdir_p(dirname($full));
    run_("chroot " . $live->get_system_root .
           qq( sh -c "rpm -qa --qf '%{size}      \t%{name}\n' | sort -n" > ) . $full);

    my $leaves = $live->get_builddir . $live->{prefix}{build}{dist} . '/' . $live->get_name . '.lst.leaves';
    mkdir_p(dirname($leaves));
    run_("chroot " . $live->get_system_root .
           qq( sh -c "urpmi_rpm-find-leaves | xargs rpm -q --qf '%{size}       \t%{name}\n' | sort -n" > ) . $leaves);

    require lang;
    my @live_langs = get_langs($live);
    my @langs = grep { member(lang::locale_to_main_locale($_), @live_langs) } lang::list_langs();
    my $langs_file = $live->get_builddir . $live->{prefix}{build}{dist} . '/' . $live->get_name . '.langs';
    output_p($langs_file, map { lang::l2name($_) . " (" . $_ . ")\n" } sort(@langs));
}

sub umount_external_filesystem {
    my ($live) = @_;
    my $mnt = $live->get_system_root . "/mnt/";
    eval { fs::mount::umount("$mnt/$_") } foreach all($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_filesystem($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 return $live->{media}->get_media_setting('source');
    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);
}

sub create_initrd {
    my ($live) = @_;
    if (need_media_specific_boot($live)) {
        MDV::Draklive::Initrd::create_media_initrd($live);
    } else {
        MDV::Draklive::Initrd::create_classical_initrd($live);
    }
}

sub create_bootloader {
    my ($live) = @_;
    if (need_media_specific_boot($live)) {
        create_media_bootloader($live);
    } else {
        create_classical_bootloader($live);
    }

    run_({ root => $live->get_system_root }, '/usr/sbin/grub-gfxmenu', '--update-gfxmenu');
    my $boot_dir = $live->get_builddir . $live->{prefix}{build}{boot};
    mkdir_p($boot_dir);
    my $gfxmenu = $live->get_system_root . '/boot/gfxmenu';
    if (-e $gfxmenu) {
        cp_f($gfxmenu, $boot_dir);
    } else {
        warn "no gfxmenu file\n";
    }
}

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 ("mdvgfxbootXXXX", 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 (!grep { $file =~ /^$_\.tr$/ } @locales) {
            unlink $file;
        } else {
	    my ($locale_name) = ($file =~ /(.*)\.tr$/);
	    push @kept_locales, $locale_name;
        }
    }
    system("echo init 16x16.fnt *.tr |sed \"s/ /\\n/g\" |cpio -o >$bootlogo");
    chdir $cwd;

    print "gfxboot locales: " . join(" ", @kept_locales) . "\n";
    return @kept_locales;
}

sub create_media_bootloader {
    my ($live) = @_;
    cp_f($live->get_system_root . '/boot/vmlinuz-' . $live->find_kernel, $live->get_builddir . $live->{prefix}{build}{boot} . '/vmlinuz');
    require bootsplash;
    my $theme = do {
        local $::prefix = $live->get_system_root;
        bootsplash::themes_read_sysconfig('800x600');
    };
    my $syslinux_dir = $live->get_builddir . $live->{prefix}{build}{boot} . '/syslinux';
    mkdir_p($syslinux_dir);
    my $default_gfxboot_theme = "Mandriva";
    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',
           pack("C*", 0x0E, 0x80, 0x03, 0x00, 0xC) . qq(
Welcome to Mandriva live!

The command line can be used to specify kernel options.

$live->{media}{title} <kernel options>

));

    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 $part_nb = 1; #- FIXME: get from partition layout config
    output_p($live->get_system_root . '/boot/grub/menu.lst', build_grub_cfg_raw($live, $live->{media}, "/initrd.img", {}, $part_nb));
}

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 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(
        $dest,
        $live->get_builddir . $live->{prefix}{build}{boot} . '/syslinux',
        $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 ($dest, $o_isolinux_path, $isolinux_cfg, $label, @opts) = @_;

    my $progress = MDV::Draklive::Progress->new(100, time());
    my $in_progress;
    autoflush STDOUT 1;
    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;
                    }
                },
         'genisoimage', '-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_($dest, '-o', $dest),
         if_($o_isolinux_path, 'isolinux=' . $o_isolinux_path),
         'isolinux/isolinux.cfg=' . $isolinux_cfg,
         'isolinux/isolinux.bin=/usr/lib/syslinux/isolinux.bin',
         'isolinux/gfxboot.com=/usr/lib/syslinux/gfxboot.com',
         @opts,
    ) or die "unable to run genisoimage\n";
    autoflush STDOUT 0;
    $progress->end;
    if ($dest) {
        my $dir = dirname($dest);
        my $filename = basename($dest);
        run_('isohybrid', $dest);
        run_('mkcd', '>', '/dev/null', '2>', '/dev/null', '--addmd5', $dest);
        run_({ chdir => $dir }, 'md5sum', '>', $dest . '.md5', $filename);
        run_({ chdir => $dir }, 'sha1sum', '>', $dest . '.sha1', $filename);
    }
}

sub get_disk_master_path {
    my ($live) = @_;
    $live->get_builddir . $live->{prefix}{build}{dist} . '/' . $live->get_name . '.img';
}

sub allocate_master {
    my ($dest, $size, $fs) = @_;
    mkdir_p(dirname($dest));
    my $heads = 16;
    my $sectors_per_track = 63;
    my $sector_size = 512;
    my $track_size = $sector_size * $sectors_per_track;
    my $cylinder_size = $track_size * $heads;

    my $disk_size = $size + $track_size;
    my $cylinders = int($disk_size / $cylinder_size);

    MDV::Draklive::Utils::device_allocate_file($dest, $disk_size);

    #- FIXME: use parted?
    require fs::type;
    my $pt_type = sprintf('%x', fs::type::fs_type2subpart($fs)->{pt_type});
    open(my $fdisk, "| fdisk -C $cylinders -S $sectors_per_track -H $heads $dest");
    print $fdisk <<EOF;
o
n
p
1


t
$pt_type
a
1
w
EOF
    close($fdisk) or die "unable to run fdisk\n" . ("$!" ? "Error closing fdisk pipe: $!" : "Exit status $? from fdisk");

    my ($part_start, $part_nb_sectors) = run_program::get_stdout("sfdisk -d $dest") =~ /start=\s*(\d+),\s*size=\s*(\d+)/;

    require devices;
    my $loop = devices::find_free_loop();
    run_('losetup', '-o', $part_start * $sector_size, '-s', $part_nb_sectors * $sector_size, $loop, $dest)
      or die "unable to setup loop device";
    MDV::Draklive::Utils::device_mkfs($loop, $fs) or die "unable to format $dest\n";

    return $loop;
}

sub create_disk_master {
    my ($live, $opts) = @_;
    my $dest = get_disk_master_path($live);
    my $disk_size = @{$live->{mount}{dirs} || []} ?
      #- dumb guess, a bit too large, and valid for FAT only
      (directory_usage($live->get_builddir . $live->{prefix}{build}{loopbacks}) +
       directory_usage($live->get_builddir . $live->{prefix}{build}{boot}) +
       directory_usage($live->get_builddir . $live->{prefix}{build}{files}) +
       70000000) :
      (directory_usage($live->get_system_root) + 200000000);

    my $loop = allocate_master($dest, $disk_size, $live->{media}->get_media_setting('fs'));
    local $opts->{device} = $loop;
    local $opts->{disk_device} = $dest;
    record_master($live, $opts);
    run_('losetup', '-d', $loop);
}

#- $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");
}

sub format_usb_device {
    my ($live, $opts) = @_;
    my $device = get_media_device($live, $opts);
    maybe_umount_device($device);
    MDV::Draklive::Utils::device_mkfs($device, $live->{media}->get_media_setting('fs'))
      or die "unable to format device $device\n";
}

#- $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_master {
    my ($live, $opts) = @_;
    my $device = get_media_device($live, $opts)
      or die "no device defined in media configuration\n";
    my $src = $opts->{onthefly} ? '-' : get_cdrom_master_path($live, $opts);
    run_('wodim', '-v', 'dev=' . $device, $src);
}

sub install_grub_to_image {
    my ($live, $img) = @_;
    my $grub_dir = "/boot/grub";
    my $grub_script = $grub_dir . "/install.sh";
    mkdir_p($live->{mnt} . $grub_dir);
    cp_af(glob_($live->get_system_root . "/lib/grub/i386-mandriva/*"), $live->{mnt} . $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,0)
setup (hd0)
quit
EOF
    close ($grub) or die "unable to run grub\n";

    output($live->{mnt} . $grub_script, <<EOG);
grub --device-map=/boot/grub/device.map --batch <<EOF
root (hd0,0)
setup --stage2=/boot/grub/stage2 (hd0)
quit
EOF
EOG
}

sub install_usb_bootloader {
    my ($live, $media, $opts) = @_;
    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') {
        #- 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);
        output_p($live->{mnt} . $media_boot . '/grub/menu.lst', build_grub_cfg($live, $media, $opts, $device));
    }

    if (-b $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'), $device)
              or die "unable to run syslinux on $device\n";
        } elsif ($bootloader eq 'grub') {
            my $master_device = $opts->{disk_device};
            if (!$master_device) {
                $master_device = $device;
                $master_device =~ s/(\d+)$//;
            }
            if ($master_device =~ m!/dev/!) {
                my $local_grub_install = $live->get_builddir . $live->{prefix}{build}{scripts} . '/grub-install';
                mkdir_p(dirname($local_grub_install));
                cp_f($live->get_system_root . '/sbin/grub-install', $local_grub_install);
                my $root = $live->get_system_root;
                #- FIXME: use a grub/install.sh script?
                substInFile {
                    s!^\s*exec_prefix=.*!exec_prefix=$root!;
                    s!^\s*grub_prefix=/boot/!grub_prefix=${media_boot}/!;
                    s!^\s*bootdir=(.*)/boot$!bootdir=$1${media_boot}!;
                } $local_grub_install;
                my $grub_device_map = $live->{mnt} . $media_boot . "/grub/device.map";
                unlink $grub_device_map;
                foreach ($master_device, $device) {
                    run_($local_grub_install, '--root-directory=' . $live->{mnt}, '--no-floppy', $_)
                      or die "unable to run grub on $device\n";
                }
                unlink $grub_device_map;
            } else {
                install_grub_to_image($live, $master_device);
            }
        }
    } 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});

    if (my $label = !$opts->{boot_only} && $opts->{device} && $media->get_media_label) {
        set_device_label($opts->{device}, $media->get_media_setting('fs'), $label);
    }
    my $device = get_media_device($live, $opts)
      or die "unable to find recording device (missing label? try with --device <device>)\n";

    mkdir_p($live->{mnt});
    run_('mount', if_(-f $device, '-o', 'loop'), $device, $live->{mnt})
      or die "unable to mount $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_usb_bootloader($live, $media, $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,
                            $loopbacks_source, $live->{mnt} . $media_loopbacks,
        );
        unlink $list_file;
        if (!$r) {
            run_('umount', $live->{mnt});
            maybe_umount_device($device);
            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', $live->{mnt});
    maybe_umount_device($device);

    if ($media->get_media_setting('fs') eq 'vfat') {
        MDV::Draklive::Utils::mtools_run_('mattrib', '+h', '-i', $device, '::' . $_) foreach @hidden_files;
        MDV::Draklive::Utils::mtools_run_('mattrib', '+r', '+s', '-/', '-i', $device, '::' . $_)
          foreach $media_boot, $media_loopbacks;
    }
}

sub record_harddisk_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});

    if (my $label = !$opts->{boot_only} && $opts->{device} && $media->get_media_label) {
        set_device_label($opts->{device}, $media->get_media_setting('fs'), $label);
    }
    my $device = get_media_device($live, $opts)
      or die "unable to find recording device (missing label? try with --device <device>)\n";

    mkdir_p($live->{mnt});
    run_('mount', if_(-f $device, '-o', 'loop'), $device, $live->{mnt})
      or die "unable to mount $device\n";

    do {
        my $source = $live->get_system_root;
        my $total = directory_usage($source);
        local $/ = "\r";
        my $r = run_foreach(update_progress_rsync($live, $total), 'rsync', '-avP', $source . '/', $live->{mnt});
        if (!$r) {
            run_('umount', $live->{mnt});
            maybe_umount_device($device);
            die "unable to copy system files\n";
        }
    } unless $opts->{boot_only};

    install_grub_to_image($live, $opts->{disk_device});

    run_('umount', $live->{mnt});
    maybe_umount_device($device);
}

#- $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";
    }
}

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) = @_;
    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));
    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 get_disk_replicator_path {
    my ($live) = @_;
    $live->get_builddir . $live->{prefix}{build}{dist} . '/' . $live->get_name . '.rest.img';
}

sub get_disk_replicator_files {
    my ($live) = @_;
    my $media_boot = $live->get_media_prefix('boot');
    my $initrd = $media_boot . $live->{replicator}{media}->get_initrd_path;
    (
        '/usr/lib/drakx-installer-images/isolinux/alt0/vmlinuz' => $media_boot . '/vmlinuz',
        '/usr/lib/drakx-installer-images/isolinux/alt0/all.rdz' => $initrd,
        '/usr/lib/drakx-installer-rescue/rescue.sqfs' => '/install/stage2/',
        get_disk_image_path($live) => '/images/',
        #- FIXME: factorize with copy_files_to to handle glob
        (map { $live->{settings}{config_root} . '/' . $_->[0] => '/' . $_->[1] } @{$live->{replicator}{files} || []}),
    );
}

sub create_usb_replicator {
    my ($live) = @_;
    my $dest = get_disk_replicator_path($live);
    my %files = get_disk_replicator_files($live);
    my $size = fold_left { $::a + $::b } map { directory_usage($_, 'apparent') } keys(%files);
    my $loop = allocate_master($dest, 5000000 + $size, 'vfat');
    my $opts = { device => $loop, disk_device => $dest, append => $live->{replicator}{append} };
    record_usb_replicator($live, $opts);
    run_('losetup', '-d', $loop);
}

sub record_usb_replicator {
    my ($live, $opts) = @_;
    my $media = $live->{replicator}{media};
    my $media_boot = $live->get_media_prefix('boot');
    my $device = get_media_device($live, $opts)
      or die "unable to find recording device (missing label? try with --device <device>)\n";
    mkdir_p($live->{mnt});
    run_('mount', if_(-f $device, '-o', 'loop'), $device, $live->{mnt})
      or die "unable to mount $device\n";
    rm_rf($live->{mnt} . $media_boot) if -e $live->{mnt} . $media_boot;
    install_usb_bootloader($live, $media, $opts);

    my $failed;
    foreach (group_by2(get_disk_replicator_files($live))) {
        my ($src, $dest) = @$_;
        $dest = $live->{mnt} . $dest;
        mkdir_p($dest =~ m!/$! ? $dest : dirname($dest));
        if (!run_('rsync', '-vdP', '--inplace', $src, $dest)) {
            $failed = 1;
            last;
        }
    }
    if ($failed) {
        run_('umount', $live->{mnt});
        maybe_umount_device($device);
        die "unable to copy replicator files\n";
    }

    output_p($live->{mnt} . "/images/list", "EN,English," . basename(get_disk_image_path($live)) . ",on\n");

    run_('umount', $live->{mnt});
    maybe_umount_device($device);
}

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, "EN,English," . basename(get_disk_image_path($live)) . ",on\n");

    # 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_disk_replicator_path($live);
    $dest =~ s/.img$/.iso/;
    build_iso_image(
        $dest,
        '',
        $syslinux_cfg,
        $label,
        "/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) = @_;

    if (my $create = $live->{replicator}{media} && $live->{replicator}{media}->get_media_setting('replicator')) {
        $create->($live);
    } 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_filesystem($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 },
    { name => 'image', do => \&create_image },
    { name => 'replicator', do => \&create_replicator },
    { name => 'format', do => \&format_device },
    { name => 'record', do => \&record_master },
    { name => 'record-onthefly', do => \&record_onthefly },
);
my @all = qw(install boot loop master 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},
    "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
   --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

   --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,
    },
    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
