#!/usr/bin/perl

# Must be done as early as possible to avoid issues when displaying translated
# strings
BEGIN {
    push @::textdomains, 'draklive-install';
}

use lib qw(/usr/lib/libDrakX);
use standalone;
use interactive;
use fs;
use fs::any;
use fs::type;
use fs::partitioning;
use fs::partitioning_wizard;
use check_min_sys_requirements;
use partition_table;
use MDK::Common;
use common;
use feature qw(state);
use File::Copy;
use background;
use autoinstall;
use Filesys::Df;
use File::stat;
use POSIX ':sys_wait_h';

($::real_windowwidth, $::real_windowheight) = (600, 400);
my $portable_initrd = 1;

{
    use diskdrake::interactive;
    package diskdrake::interactive;
    my $old = \&hd_possible_actions_base;
    undef *hd_possible_actions_base;
    *hd_possible_actions_base = sub {
	#- for the partition wizard to show the auto-allocate option
	local $::isInstall = 1;
	&$old;
    };
    undef *Done;
    #- skip the fstab/reboot checks
    *Done = \&diskdrake_interactive_Done;
    #- don't ask whether to Move/Hide old files
    undef *need_migration;
    *need_migration = sub { 'hide' };
}

install_live();

sub install_live() {
    my $in = 'interactive'->vnew('su');
    my $running_wm = any::running_window_manager();
    background::show_bg_window if ($running_wm eq 'drakx-matchbox-window-manager');
    $in->{pop_wait_messages} = 0;

    $::isWizard = 1;
    $::Wizard_no_previous = 1;
    $::Wizard_pix_up = "ROSAOne-install-icon";
    any::set_wm_hints_if_needed($in);

    my $all_hds = {};
    my $fstab = [];
    $::prefix = '/mnt/install';

    my $system_file = '/etc/sysconfig/draklive-install';
    my %settings = getVarsFromSh($system_file);

    my $copy_source = $settings{SOURCE} || '/';
    my $live_media = '/live/media';

    display_start_message();
    if (!$autoinstall::enabled) {
        check_min_sys_requirements::main($in);
    }
    init_hds($in, $all_hds, $fstab, $live_media);
    ask_partitions_loop($in, $all_hds, $fstab, $copy_source);
    if ($settings{'remove_unused_packages'} ne 'no') {
        remove_unused_packages($in, $copy_source);
    }
    my $selinux;
    if ($settings{'copy_with_selinux_context'} eq 'yes') {
        $selinux = '--selinux';
    }
    prepare_root($in, $all_hds);
    copy_root($in, $copy_source, $selinux);
    complete_install($in, $all_hds);
    setup_bootloader($in, $all_hds, $fstab);

    my $wait = $in->wait_message('', N("Please wait"));

    # Call firstboot initialization
    # X authorization would fail when running finish-install from chroot, temporarily turn it off
    # TODO: More correct way would be 'xauth extract' + 'xauth merge', but it fails when running
    # not from Live system but during direct installation. Find out why and fix it.
    system('xhost +');
    # Prepare the chroot environment by mounting system-related mount points
    if (system("(mount --bind /proc $::prefix/proc && mount --bind /sys $::prefix/sys) >/tmp/draklive-install.log 2>&1") == 0) {
        if ($settings{'remount_selinuxfs'} eq 'yes') {
            system("(mount -t selinuxfs selinuxfs $::prefix/sys/fs/selinux)")
        }

        # Now launch finish-install (which implements the firstboot) and start reading its output.
        my $fh;
        my $success = open($fh, '-|', "chroot $::prefix /bin/sh -c '/usr/sbin/finish-install --from-installer'");
        if ($success) {
            while (<$fh>) {
                if (m/GUI start\./) {
                    # finish-install initialization takes ~6 seconds. If we hide our window too early user would see an empty screen
                    # and may think installation is finished. So, finish-install prints a specific message when it's ready to show its window,
                    # so that we knew when to hide our own window.
                    $::WizardWindow->hide;
                    mygtk2::flush;
                }
                elsif (m/GUI end\./) {
                    # finish-install closed its window (but not yet terminated), show our window, again, to avoid empty screen.
                    $::WizardWindow->show;
                    mygtk2::flush;
                }
                else {
                    # All other messages from finish-install are simply redirected to the output.
                    print;
                }
            }
            close($fh);
            $success = ($? == 0);
        }
        if (!$success) {
            # Something bad happened
            if ($autoinstall::enabled) {
                log::l("Post-install setup failed.");
            }
            else {
                $in->ask_warn(N("Error"), N("Failed to launch post-install setup. It will be started on next boot."));
            }
        }
    }
    else {
        # Something bad happened
        if ($autoinstall::enabled) {
            log::l("Failed to prepare chroot for post-install setup.");
        }
        else {
            $in->ask_warn(N("Error"), N("Failed to prepare chroot for post-install setup. Post-install will be started on next boot."));
        }
    }

    # firstboot completed, unmount the system-related mount points from chroot
    system("umount $::prefix/proc; umount $::prefix/sys");
    # Return X authorization to normal state
    system('xhost -');

    finish_installation($fstab);
    system('sync');
    undef $wait;
    display_end_message($in);
    background::hide_bg_window if ($running_wm eq 'drakx-matchbox-window-manager');
    $in->exit(0);
}

sub umount_all {
    my ($fstab) = @_;
    # Unmount everything from the new root
    # First, create the list of active mount points mentioned in $fstab
    # Also, unmount all swaps from $fstab (otherwise, if there are file swaps, they would prevent partitions from unmounting)
    my %fstab_umounts = ();
    foreach (grep { $_->{isMounted} } @$fstab) {
        if (isSwap($_)) {
            fs::mount::umount_part($_);
        }
        else {
            my $mntpoint = ($_->{real_mntpoint} || fs::get::mntpoint_prefixed($_));
            $mntpoint =~ s!/+$!!;
            $fstab_umounts{$mntpoint} = $_;
        }
    }
    # Start with unmounting partitions in the new root
    foreach (sort { $b cmp $a } grep { /^$::prefix/ } map { (split)[1] } cat_('/proc/mounts')) {
        if ($fstab_umounts{$_}) {
            # Entry in $fstab is present - use specific interface to keep data consistent
            fs::mount::umount_part(delete($fstab_umounts{$_}));
        }
        else {
            # Entry is not present, simply unmount it
            system('umount', $_);
        }
    }
}

sub on_reboot_needed {
    my ($in) = @_;
    fs::partitioning_wizard::warn_reboot_needed($in);
    $in->exit(0);
}

sub display_start_message() {
    require any;
    my $running_wm = any::running_window_manager();
    my $has_running_wm = to_bool($running_wm);   
    local $::isStandalone = $has_running_wm; # center me if run in xsetup.d script
    my $w = ugtk2->new(N("ROSA Live"));

    # Skip if running in normal installation or autoinstallation mode (not launched from Live system)
    return if ($autoinstall::enabled || ($running_wm eq 'drakx-matchbox-window-manager'));

    # Move window to the center of the screen
    any::center_window($w->{window});

    ugtk2::gtkadd($w->{window},
                  ugtk2::gtkcreate_img("ROSAOne-install"),
                  ugtk2::gtknew('Label', height => 5),
                  N("This wizard will help you to install the live distribution."),
                  ugtk2::create_okcancel($w));
    $w->{ok}->grab_focus;
    $w->main;
}

sub umount_first_pass() {
    local $::prefix = undef;
    my $all_hds = fsedit::get_hds();
    fs::get_raw_hds('', $all_hds);
    fs::get_info_from_fstab($all_hds);
    my $fstab = [ fs::get::fstab($all_hds) ];
    fs::merge_info_from_mtab($fstab);

    #- inlined from fs::mount::umount_all to go on when one umount fail
    #- (maybe the sort function could be shared)
    log::l("unmounting all filesystems");
    foreach (sort { $b->{mntpoint} cmp $a->{mntpoint} } 
	       grep { $_->{mntpoint} && !$_->{real_mntpoint} } @$fstab) {
	eval { fs::mount::umount_part($_) };
	log::l("error unmounting $_->{mntpoint}: $@") if $@;
    }
}

sub init_hds {
    my ($in, $all_hds, $fstab, $live_media) = @_;
    my $wait = $in->wait_message('', N("Please wait"));

    umount_first_pass();
    eval { fs::any::get_hds($all_hds, $fstab, [], {}, 'skip_mtab', $in) };

    #- fs::any::get_hds does not return mounts that are not in fstab
    my @mounted = fs::read_fstab('', '/proc/mounts');
    my $live_part = find { $_->{mntpoint} eq $live_media } @mounted;
    my $live_device = $live_part && $live_part->{device};
    #- remove live device from the detected hds, so that bootloader is not installed on it:
    #-  bootloader installation uses first device from detect_devices::get, which tries to list devices
    #-  by booting order, and our live system is likely to be here in first position
    #- it can be either a partition (USB) or the full disk (Hybrid on USB)
    @{$all_hds->{hds}} = grep {
        $_->{device} ne $live_device &&
        !member($live_device, map { $_->{device} } partition_table::get_normal_parts_and_holes($_));
    } @{$all_hds->{hds}} if $live_device;

    my $err = $@;
    umount_all($fstab);
    if ($err) {
        undef $wait;
        c::set_tagged_utf8($err);
        $in->ask_warn(N("Error"), [ formatError($err) ]);
        $in->exit(1);
    }
}

sub ask_partitions_loop {
    my ($in, $all_hds, $fstab, $copy_source) = @_;

    while (1) {
        eval { ask_partitions($in, $all_hds, $fstab, $copy_source) };
        my $err = $@ or last;
        if ($autoinstall::enabled) {
            log::l('Error during partitioning: ' . $err);
            last;
        }
        # diskdrake modules return a utf8-tagged error string which does not contain valid Unicode text.
        # It might be due to fact that they use utf8 pragma, though it is not for sure.
        # Experimentally discovered that utf8::decode transforms all kinds of strings into GUI-acceptable text,
        # except internal Unicode representation with utf8::is_utf8 set to 0 (could not reproduce that one).
        utf8::decode($err);
        $in->exit(1) if $err =~ /wizcancel/ ||
          !$in->ask_warn(N("Error"), [ N("An error occurred"), formatError($err) ]);
    }
}

sub ask_partitions {
    my ($in, $all_hds, $fstab, $copy_source) = @_;
    # Partitioning procedures are heavily nested, so it's very hard to implement autoinstalling nicely.
    # At the moment, parts of code are simply extracted and duplicated here, so all applicable changes
    # in fs/fsedit modules are required to be copied here as well.
    # TODO: Get rid of code duplicating.
    if ($autoinstall::enabled) {
        # At the moment only the clean installation is supported
        # (erasing all the data on the first disk and automatically allocating it).
        my $hd = $all_hds->{'hds'}->[0];
        $hd->clear_existing;
        partition_table::initialize($hd, 'dos');
        fsedit::auto_allocate($all_hds, undef);
    }
    else {
        fs::partitioning_wizard::main($in, $all_hds, $fstab, [], undef, {}, 'skip_mtab');
    }

    mkdir_p($::prefix) or die "unable to create $::prefix";

    fs::any::write_hds($all_hds, $fstab, undef, sub { on_reboot_needed($in) }, {});
    fs::any::check_hds_boot_and_root($all_hds, $fstab);
    if ($autoinstall::enabled) {
        fs::partitioning::guess_partitions_to_format($fstab);
        $_->{toFormat} = 1 foreach grep { isSwap($_) } @$fstab;
    }
    else {
        fs::partitioning::choose_partitions_to_format($in, $fstab);
    }

    my $total = get_live_system_size();
    my $available = fs::any::getAvailableSpace($fstab, 'skip_mounted');
    die N("Not enough space available (%s available while %s are needed)",
          formatXiB($available), formatXiB($total)) . "\n"
      if $total > $available;

    umount_all($fstab);
    fs::partitioning::format_mount_partitions($in, $all_hds, $fstab);
}

sub remove_unused_packages {
    my ($in, $o_prefix) = @_;
    require pkgs;
    #in remove_unused_packages, we want to get the locale from the currently
    #running system, but we want to remove unused packages from the 
    #system based in $o_prefix, that's why we use an extra arg instead of 
    #directly using $::prefix
    local $::prefix;
    pkgs::remove_unused_locale_packages($in, $in->do_pkgs, $o_prefix);

    # Remove VBox Additions if we are running on real hardware
    my $is_vbox = 0;
    for my $dev (glob("/sys/bus/pci/devices/*")) {
        if ((cat_("$dev/vendor") =~ m/^0x80ee$/i) && (cat_("$dev/device") =~ m/^0xcafe$/i)) {
	    $is_vbox = 1;
	    last;
	}
    }
    if (!$is_vbox) {
        my $wait = $in->wait_message(N("Please wait"), N("Removing unneeded Virtualbox guest additions..."));
        run_program::rooted($o_prefix, 'urpme', '--auto', '--force', 'x11-driver-video-vboxvideo', 'dkms-vboxadditions', 'virtualbox-guest-additions')
			or log::l("Removing unneeded Virtualbox guest additions failed");
        undef $wait;
    }

    # Remove dkms-broadcom-wl if there is no Broadcom PCI device
    # https://bugzilla.rosalinux.ru/show_bug.cgi?id=9463
    my $has_broadcom_wl = 0;
    for my $dev (glob("/sys/bus/pci/devices/*")) {
        if (cat_("$dev/vendor") =~ m/^0x14e4$/i) {
	    $has_broadcom_wl = 1;
	    last;
	}
	}
	if (!$has_broadcom_wl) {
		my $wait = $in->wait_message(N("Please wait"), N("Removing unneeded Broadcom drivers..."));
        run_program::rooted($o_prefix, 'urpme', '--auto', '--force', 'dkms-broadcom-wl')
			or log::l("Removing unneeded dkms-broadcom-wl failed");
        undef $wait;
    }
}

sub prepare_root {
    my ($in, $all_hds) = @_;
    #- create required directories and devices (early to have a consistent root before calling other programs)
    my $_wait = $in->wait_message('', N("Please wait"));
    fs::any::prepare_minimal_root;
}

sub build_copy_command {
    my ($source, $dest, $selinux) = @_;
    # Some might be curious, why not use normal cp? Well, at least I was. So I googled a little, and the most
    # satisfying answer was, with two simultaneous processes the I/O scheduler improves performance.
    # Another question is, whether to use plain or gzipped tar-pipe stream. Results of testing on VirtualBox
    # (time of copying):
    # no compression:    5:56
    # gzip compression:  8:10
    # xz compression:   33:25
    # So, obviously, it's better to leave it uncompressed.
    # tar no compress.:  8:20
    # rsync:             7:50
    "rsync -pogAtlHrDx" . ($selinux ? "X" : "") . " " .
      "--exclude /dev --exclude /proc --exclude /sys --exclude '/run/*' " .
      ($selinux ? "--exclude /boot/efi " : "") .
      "$source/ $dest/ " .
      "2>/tmp/files_copy_error.log"
}

sub sync_logs() {
    cp_af('/var/log', $::prefix . '/var');
}

sub copy_root {
    my ($in, $copy_source, $selinux) = @_;
    (@process_images) = &get_advert_list(); 
	my $image_count = scalar(@process_images);
    my ($wait, $update_progress, $image) = copying_message_with_progress_bar($in, N("Copying in progress"),
		"ROSAOne-advert");
    my $sdf = df($copy_source, 1);
    my $source_used = defined($sdf) ? $sdf->{used} : 0;
    my %dest_devs = ();
    my $dstat = stat($::prefix);
    if (defined($dstat)) {
	$dest_devs{$dstat->dev()} = $::prefix;
    }
    open my $fmnts, '<', "/proc/mounts";
    if ($fmnts) {
	while (<$fmnts>) {
	    chomp;
	    my @entry = split;
	    my $mountpath = $entry[1];
	    if (index($mountpath, $::prefix) == 0) {
		my $dstat = stat($mountpath);
		if (defined($dstat)) {
		    $dest_devs{$dstat->dev()} = $mountpath;
		}
	    }
	}
    }

    my $copy_command = build_copy_command($copy_source, $::prefix, $selinux);
    my $copy_pid = fork();
    if (!defined($copy_pid)) {
	$in->ask_warn(N("Error"), N("Unable to start new process: ") . $!);
	$in->exit(1);
    }
    elsif (!$copy_pid) {
	exec($copy_command) or die "Unable to run rsync to copy files to new root: $!";
    }
    # Here continues the parent process
    my $previous_ratio = 0;
    while (waitpid($copy_pid, WNOHANG) == 0) {
	my $dest_used = 0;
	for (values %dest_devs) {
	    my $ddf = df($_, 1);
	    $dest_used += defined($ddf) ? $ddf->{used} : 0;
	}
	$ratio = $source_used > 0 ? $dest_used / $source_used : 0;
	if ($ratio <= 1 && $ratio > $previous_ratio + 0.005) {
	    $update_progress->('', int($ratio * 100), 100);
	    my $image_index = int($ratio * ($image_count + 1));
	    if ($image_index > int($previous_ratio * ($image_count + 1)) &&
		$image_index - 1 < $image_count) {
		$image->($process_images[$image_index - 1]);
	    }
	}
	$previous_ratio = $ratio;
	sleep(3);
    }
    # 24 - rsync warning: some files vanished before they could be transferred
    my $result = $?;
    if ($result != 0 && ($result >> 8) != 24) {
	undef $wait;
	undef $update_progress;
	if ($result == -1) {
	    $in->ask_warn(N("Error"), N("Unable to run rsync to copy files to new root."));
	} else {
	    $in->ask_warn(N("Error"), N("Copying files (rsync) failed with code %d.", $result >> 8));
	}
	$in->exit(1);
    }
    if ($selinux && -d $copy_source . "/boot/efi") {
	my $efi_copy_command = build_copy_command($copy_source . "/boot/efi", $::prefix . "/boot/efi", 0);
	$result = system($efi_copy_command);
	if ($result != 0) {
	    $in->ask_warn(N("Error"), N("Copying files (rsync) failed with code %d.", $result >> 8));
	    $in->exit(1);
	}
    }
    sync_logs();
}

sub clean_harddrake_hds {
    my ($prefix) = @_;
    #- remove harddisks from harddrake's config file, so that hardddisks
    #- are automatically rediscovered at first boot
    require Storable;
    my $harddrake_file = $prefix . "/etc/sysconfig/harddrake2/previous_hw";
    my $harddrake_conf = eval { Storable::retrieve($harddrake_file) };
    if ($harddrake_conf) {
        delete $harddrake_conf->{HARDDISK};
        Storable::store($harddrake_conf, $harddrake_file);
    }
}


sub complete_install {
    my ($in, $all_hds) = @_;
    my $_wait = $in->wait_message('', N("Please wait"));

    my $real_rpm_dir = "/tmp/rpm/real";
    cp_f(glob($real_rpm_dir . "/*"), $::prefix . "/var/lib/rpm") if -d $real_rpm_dir;

    #- FIXME: maybe factorize with draklive, using draklive --clean-chroot ?
    #- remove unwanted files and packages
#	$in->ask_okcancel('', N("live_user and live_user_desktop"));	

    my $live_user = chomp_(cat_('/etc/draklive-install.d/user'));
    my $live_user_desktop = $live_user && chomp_(run_program::rooted_get_stdout($::prefix, "su - $live_user -c 'xdg-user-dir DESKTOP'"));
    unlink(map { $::prefix . $_ } '/.autofsck',
           chomp_(cat_(glob('/etc/draklive-install.d/remove.d/*'))),
           if_($live_user_desktop,
               $live_user_desktop . '/draklive-copy-wizard.desktop',
               $live_user_desktop . '/draklive-install.desktop'),
           );
           
    foreach (glob('/etc/draklive-install.d/run.d/*')) {
	    run_program::rooted($::prefix, $_);
    }
           
    {
        #- do not allow update-menus to create home directory with invalid perms
        local $ENV{HOME} = '/root';
        system('chroot', $::prefix, 'sh', '-c', 'rpm -e `rpm -qa | grep \'^draklive-install\'`');
    }

    # Update lm_sensors config
    system('sensors-detect', '--auto');
    cp_f('/etc/sysconfig/lm_sensors', $::prefix . '/etc/sysconfig');

    #- copy sysconfig files for first boot
    cp_f(glob('/etc/draklive-install.d/sysconfig/*'), $::prefix . '/etc/sysconfig');

    #- allow mdkonline to be started again
    eval { rm_rf($::prefix . '/etc/skel/.MdkOnline', glob($::prefix . '/home/*/.MdkOnline')) };

    #- unselect live user in kdm
    my $kdm_cfg = common::read_alternative('kdm4-config');
    update_gnomekderc($::prefix . $kdm_cfg,
                      'X-:0-Greeter' => (PreselectUser => 'None', DefaultUser => '')) if -f $kdm_cfg;
    my $autologin = any::get_autologin();
    delete $autologin->{user};
    any::set_autologin($in->do_pkgs, $autologin);

    #- allow to install doc in disk install
    substInFile { undef $_ if /^\%_excludedocs/ } $::prefix . '/etc/rpm/macros';

    fs::write_fstab($all_hds, $::prefix);

    clean_harddrake_hds($::prefix);

    # enable back some disabled services
    require services;
    services::start_service_on_boot($_) foreach chomp_(cat_('/etc/draklive-install.d/services'));


    sync_logs();
}

# Function formats information to be displayed as user-friendly description in the list of available HDDs
sub format_hdd_info {
	my ($hdd_info) = @_;

	# First, the device name is taken
	my $descr = $_->{'file'};

	# If there is additional information it will be appended in parentheses
	if ($_->{'info'} || $_->{'totalsectors'}) {
		# Collect printable bits of information into array (only two at the moment, but who knows
		# what we'll wish to add here in the future)
		my @data = ();
		if ($_->{'totalsectors'}) {
			# Size of the disk in Megabytes
			my $size = $_->{'totalsectors'} / 2048;
			my $unit = N(" Mb\n");
			if ($size > 1536) {
				# If larger than 1,5 Gb, show size in Gigabytes
				$size /= 1024;
				$unit = N(" Gb\n");
			}
			# Reusing existing translation units " Mb\n" and " Gb\n", but strip them of "\n"
			$unit =~ s/\n//g;
			push @data, (int($size + 0.5) . $unit);
		}
		if ($_->{'info'}) {
			# User-friendly name of the disk (manufacturer, model or whatever)
			push @data, $_->{'info'};
		}
		# And finally, join together all those bits of information we were able to collect
		$descr .= ' (' . join(', ', @data) . ')';
	}
	return $descr;
}

# Function for updating the Grub2 configuration file
# Input parameters:
#    $file        - target file name (with path)
#    $set_options - refhash with all the options to set/change (other existing options will not be changed),
#                   sample format is: { 'GRUB_DISABLE_RECOVERY' => { 'type' => 'bool', value => 1 } }
#                   Supported types (unknown types are treated as 'str'):
#                       bool - the value is translated into "true" or "false"
#                       int  - the value is a number
#                       str  - the value is a string which will be double-quoted
#                       list - the value is a list of space-separated directives in form of:
#                              add:param - for adding param to the original string,
#                              del:param - for deleting param from the original string
sub update_grub_options {
	my ($file, $set_options) = @_;

	# First, we read and parse existing file to store the unchanged options
	# (if there is no file, then it will be created with the new options only, so no problem)
	my %current_options = ();
	if (open(GRUB, '<', $file)) {
		my $data;
		read(GRUB, $data, -s(GRUB));
		close(GRUB);
		%current_options = map { split('=', $_, 2); } split(m/\n/, $data);
	}

	# Now update/supplement the collected set of options with the new ones
	foreach (keys(%$set_options)) {
		if ($set_options->{$_}->{'type'} eq 'bool') {
			$current_options{$_} = ($set_options->{$_}->{'value'} ? 'true' : 'false');
		}
		elsif ($set_options->{$_}->{'type'} eq 'int') {
			$current_options{$_} = int($set_options->{$_}->{'value'});
		}
		elsif ($set_options->{$_}->{'type'} eq 'list') {
			# Split value into list of parameters
			foreach my $param (split(m/\s+/, $set_options->{$_}->{'value'})) {
				# Process the string by splitting into list of parameters and then joining them
				next if ($param !~ m/^(add|del):(.*)/);
				my $list_action = $1;
				my $list_item = $2;
				my $value = $current_options{$_};
				# Get rid of quotes around the current value
				$value =~ s/^([\'\"])(.*)\1/$2/;
				my $quote = $1;
				# Split the list of parameters and remove the specified parameter,
				# so that it did not stay in the way
				my @params = grep { $_ ne $list_item } split(m/\s+/, $value);
				if ($list_action eq 'del') {
					# The parameter is removed, just join the rest of them
					$value = $quote . join(' ', @params) . $quote;
				}
				elsif ($list_action eq 'add') {
					# Append the parameter
					$quote ||= '"';   # If string was not quoted, force-quote it, since it contains spaces now
					$value = $quote . join(' ', @params, $list_item) . $quote;
				}
				$current_options{$_} = $value;
			}
		}
		else {
			$current_options{$_} = '"' . $set_options->{$_}->{'value'} . '"';
		}
	}

	# And finally write all the options back into the file
	if (open(GRUB, '>', $file)) {
		print GRUB join("\n", map { "$_=$current_options{$_}" } keys(%current_options));
		close(GRUB);
	}
	else {
		return 0;
	}
	return 1;
}

sub last_error_msg() {
	my $e = $!;
	c::set_tagged_utf8($e);
	return $e;
}

sub setup_bootloader {
    my ($in, $all_hds, $fstab) = @_;

    my $install_device;
    my %options = (
        # Default options. See Grub2 documentation about /etc/default/grub for complete list,
        # but here we use only the most common ones
        'GRUB_TIMEOUT'            => { 'type' => 'int',  value => 5 },
        'GRUB_DISABLE_LINUX_UUID' => { 'type' => 'bool', value => 0 },
        'GRUB_DEFAULT'            => { 'type' => 'str',  value => 'saved' },
        'GRUB_SAVEDEFAULT'        => { 'type' => 'bool', value => 1 },
        'GRUB_DISABLE_OS_PROBER'  => { 'type' => 'bool', value => 0 },
        'GRUB_OS_PROBER_LINKED'   => { 'type' => 'bool', value => 0 }
        # GRUB_DISABLE_RECOVERY
        # GRUB_GFXMODE
    );

    # btrfs treats all files as sparse, which is unsupported for grubenv.
    # If boot partition is btrfs, disable saving last booted entry.
    my $boot_part = fs::get::root_($fstab, 1);
    my $boot_part_btrfs = ($boot_part->{'fs_type'} eq 'btrfs');
    if ($boot_part_btrfs) {
        $options{'GRUB_DEFAULT'}->{'value'} = '0';
        $options{'GRUB_SAVEDEFAULT'}->{'value'} = 0;
    }

    my @boot_devices;
    my %boot_devices_info;

    # Grub2 installation command names
    my $grub2_install;
    my $grub2_mkconfig;

    my @chroot_mnt_points = ('dev', 'proc', 'sys', 'run');
    if (is_efi_mode()) {
        # EFI mode: only installation into /boot/efi is supported (grub2-efi-install ignores the input argument).
        # Format for displaying: device name and mount point.
        $install_device = fs::get::has_mntpoint('/boot/efi', $all_hds);
        if ($install_device) {
            my $install_device_name = '/dev/' . $install_device->{'device'};
            @boot_devices = ( $install_device_name );
            %boot_devices_info = ( $install_device_name => $install_device_name . ' (/boot/efi)' );
        }
        else {
            @boot_devices = ();
            %boot_devices_info = ();
        }
        # Grub installation commands for EFI mode
        $grub2_install = 'grub2-efi-install';
        $grub2_mkconfig = 'grub2-efi-mkconfig';
        # For chroot we also need /sys/firmware/efi/efivars - it's a new systemd target
        push(@chroot_mnt_points, 'sys/firmware/efi/efivars');
    }
    else {
        # Legacy BIOS mode: enumerate non-GPT disks and GPT disks with BIOS Boot Partition.
        # Format for displaying: see format_hdd_info()
        foreach (@{$all_hds->{'hds'}}) {
            if ((ref($_) ne 'partition_table::gpt') || any { isBiosBoot($_) } fs::get::hds_fstab($_)) {
                push(@boot_devices, $_->{'file'});
                $boot_devices_info{$_->{'file'}} = format_hdd_info($_);
            }
        }
        # By default select the first available disk
        $install_device = $boot_devices[0];
        # Grub installation commands for legacy BIOS mode
        $grub2_install = 'grub2-install';
        $grub2_mkconfig = 'grub2-mkconfig';
    }

    if ((scalar(@{$all_hds->{raids}}) > 0) || (scalar(@{$all_hds->{dmcrypts}}) > 0)) {
        # Force-enable raid and dmcrypt support
        $options{'GRUB_CMDLINE_LINUX_DEFAULT'} = { 'type' => 'list', value => 'add:rd.auto=1' };
    }
    if (cat_('/proc/cmdline') =~ m/\bnomodeset\b/) {
        # Booted in basic graphics mode => force VESA graphics in the installed system
        $options{'GRUB_CMDLINE_LINUX_DEFAULT'} = { 'type' => 'list', value => ($options{'GRUB_CMDLINE_LINUX_DEFAULT'} ? $options{'GRUB_CMDLINE_LINUX_DEFAULT'}->{'value'} . ' ' : '') . 'add:nomodeset add:xdriver=vesa' };
    }

    # Trying to install Grub2 again and again until success or excplicit rejection from user
    my $completed = 0;
    my $skip = 0;
    while (!$completed) {
        if ($autoinstall::enabled) {
            # Do not repeat in case of error - no user interaction, would be just endless loop
            $completed = 1;
            $install_device ||= $boot_devices[0];
            $options{'GRUB_TIMEOUT'}->{'value'} = $autoinstall::params{'GRUB_TIMEOUT'};
            $options{'GRUB_DISABLE_LINUX_UUID'}->{'value'} = $autoinstall::params{'GRUB_DISABLE_LINUX_UUID'};
            $options{'GRUB_DEFAULT'}->{'value'} = $autoinstall::params{'GRUB_DEFAULT'};
            $options{'GRUB_SAVEDEFAULT'}->{'value'} = $autoinstall::params{'GRUB_SAVEDEFAULT'};
            $options{'GRUB_DISABLE_OS_PROBER'}->{'value'} = $autoinstall::params{'GRUB_DISABLE_OS_PROBER'};
            $options{'GRUB_OS_PROBER_LINKED'}->{'value'} = $autoinstall::params{'GRUB_OS_PROBER_LINKED'};
        }
        else {
            my $enable_os_prober = !$options{'GRUB_DISABLE_OS_PROBER'}->{'value'};
            # Let the user set all the parameters for installing bootloader
            $skip = !$in->ask_from_(
                {
                    title => N("Bootloader main options"),
                    interactive_help_id => 'setupBootloader',
                    more_buttons => [
                        [
                            N("Skip"),
                            sub {
                                $in->ask_from_real({
                                    title => N("Warning"),
                                    ok => N("Yes"),
                                    cancel => N("No"),
                                    messages => [ N("Without bootloader you cannot boot your new system."), N("If you have another bootloader installed, you will have to update its configuration manually."), '', N("Skip bootloader installation anyway?") ]
                                }, []) && Gtk2->main_quit;
                            },
                            1,
                            1
                        ]
                    ]
                },
                [
                    { label => N("Bootloader"), title => 1 },
                    { label => N("Boot device"), val => \$install_device, list => \@boot_devices, allow_empty_list => 1,
                      ( if_(is_efi_mode(), ( disabled => sub { 1 } )) ),
                      format => sub { $boot_devices_info{$_[0]} } },
                    { label => N("Main options"), title => 1 },
                    { label => N("Delay before booting default image"), val => \$options{'GRUB_TIMEOUT'}->{'value'} },
                    { text => N("Disable UUID"), val => \$options{'GRUB_DISABLE_LINUX_UUID'}->{'value'}, type => 'bool', advanced => 1 },
                    { text => N("Remember last booted entry"), val => \$options{'GRUB_SAVEDEFAULT'}->{'value'}, type => 'bool', advanced => 1, disabled => sub { $boot_part_btrfs } },
                    { text => N("Add secondary systems to Grub boot menu"), val => \$enable_os_prober, type => 'bool', advanced => 1 },
                    { text => N("Use links to grub.cfg instead of submenus"), val => \$options{'GRUB_OS_PROBER_LINKED'}->{'value'}, type => 'bool', advanced => 1, disabled => sub { !$enable_os_prober } },
                    { text => N("Configure initrd as portable"), val => \$portable_initrd, type => 'bool', advanced => 1 },
                ]
            );
            $options{'GRUB_DISABLE_OS_PROBER'}->{'value'} = !$enable_os_prober;
            $options{'GRUB_DEFAULT'}->{'value'} = ($options{'GRUB_SAVEDEFAULT'}->{'value'} ? 'saved' : 0);
        }

        # Update the "host-only" flag for initrd generation
        if ($portable_initrd) {
            substInFile { s/^\s*hostonly\s*=\s*[\"\']?yes[\"\']?/hostonly="no"/ } "$::prefix/etc/dracut.conf.d/50-dracut-rosa.conf";
        }

        # Now let's rock and roll!
        my $_wait = $in->wait_message(N("Bootloader"), ($skip ? N("Please wait while local configuration file is being created...") : N("Please wait while Grub2 is being installed...")));

        # Write down the options
        if (!update_grub_options("$::prefix/etc/default/grub", \%options)) {
            my $err = last_error_msg();
            if ($autoinstall::enabled) {
                log::l("Failed to update Grub2 configuration: $err");
            }
            else {
                $in->ask_warn(N("Warning"), N("Failed to update Grub2 configuration:") . " $err\n" . N("Continuing with default settings."));
            }
        }

        # To install Grub2 we chroot into the freshly installed ROSA and perform installation from there,
        # otherwise strange errors happen.
        # So, first we try to prepare chroot environment by mounting system-related mount points
        unlink('/tmp/draklive-install.log');
        my $success = 1;
        for (@chroot_mnt_points) {
            if (system("mount --bind /$_ $::prefix/$_ 2>&1 | tee -a /tmp/draklive-install.log") != 0) {
                $success = 0;
                last;
            }
        }
        if (!$success) {
            # Something bad happened
            if ($autoinstall::enabled) {
                # In autoinstallation mode log the error and skip the current stage.
                # Of course, the system won't boot, but there's nothing we can do.
                log::l("Failed to prepare chroot for installing Grub2.");
            }
            else {
                # In normal mode, ask user what to do now (exit Grub2 installation or try again)
                $completed = !any::ask_yesorno_showlog($in, N("Error"), ($skip ? N("Failed to prepare chroot for local configuration. Try again?") : N("Failed to prepare chroot for installing Grub2. Try again?")), '/tmp/draklive-install.log', N("Output log"), N("Mount commands output"));
            }
            # (Would like to use redo here, but it would skip the continue block, and I need it to run)
            next;
        }

        # Now install the Grub2 itself and create menu configuration for it.
        my $cmdline = ($skip ? "$grub2_mkconfig -o /boot/grub2/grub.cfg" : "$grub2_install $install_device && update-grub2");
        if (system("chroot $::prefix /bin/sh -c '$cmdline' 2>&1 | tee /tmp/draklive-install.log") != 0) {
            if ($autoinstall::enabled) {
                log::l("Failed to install Grub2.");
            }
            else {
                $completed = !any::ask_yesorno_showlog($in, N("Error"), ($skip ? N("Failed to create local configuration file. Try again?") : N("Failed to install Grub2. Try again?")), '/tmp/draklive-install.log', N("Output log"), N("Grub2 installation output"));
            }
            next;
        }

        # Generate new initrd file
        if (!$skip) {
            undef $_wait;
            $_wait = $in->wait_message(N("Bootloader"), N("Please wait while initrd is prepared"));
            if (system("chroot $::prefix /bin/sh -c 'dracut -f' 2>&1 | tee /tmp/draklive-install.log") != 0) {
                if ($autoinstall::enabled) {
                    log::l("Failed to create initrd.");
                }
                else {
                    $completed = !any::ask_yesorno_showlog($in, N("Error"), N("Failed to create initrd. Try again?"), '/tmp/draklive-install.log', N("Output log"), N("dracut output"));
                }
                next;
            }
        }

        # Everything went fine, exit the loop
        $completed = 1;
    } continue {
        # After each iteration unmount the system-related mount points to avoid multi-mounts
        for (reverse(@chroot_mnt_points)) {
            system("umount $::prefix/$_");
        }
        unlink('/tmp/draklive-install.log');
    }
}

sub clean_live_system_hds() {
    #- clean fstab and harddrake config in the live system
    #- since partitions UUIDs of the installed system have been modified
    #- (useful for persistent live systems)
    local $::prefix = undef;
    clean_harddrake_hds($::prefix);
    my $all_hds = fs::get::empty_all_hds(); #- skip real harddisks
    fs::get_raw_hds('', $all_hds);
    fs::get_info_from_fstab($all_hds);
    fs::write_fstab($all_hds, $::prefix);
    
}

sub finish_installation {
    my ($fstab) = @_;
    # Schedule DVD eject on system shutdown (only if booted from DVD)
    output_with_perm('/lib/systemd/system-shutdown/eject-on-shutdown', 0755, <<EOF);
#!/bin/sh
if mount | grep /run/initramfs/live | grep -q /dev/sr; then
	/usr/bin/eject -m
fi
EOF
    sync_logs();
    #- cleanly umount here, it will avoid fs journals to be corrupted after a hackish reboot
    system('/usr/sbin/clean_live_hds');
    umount_all($fstab);
    clean_live_system_hds();
}

sub display_end_message {
    # Skip if autoinstallation is working
    return if ($autoinstall::enabled);

    my ($in) = @_;
    $::Wizard_finished = 1;
    my $msg = N("Please halt your computer, remove your live system, and restart your computer.");
    if (cat_('/proc/cmdline') =~ m/\bvncinstall\b/) {
        $msg .= "\n\n" . N("Your VNC connection will be dropped. Please reconnect after the computer is restarted.");
    }
    # Only recommend proprietary drivers if not a portable installation
    $msg .= "\n\n" . N("Your system will boot using free video drivers.");
    $msg .= "\n\n" . N("For installing proprietary video drivers, please launch \"Configure Graphics Card\" from menu, or \"XFdrake\" from console.");

    require ugtk2;
    ugtk2->import(':all');
    require mygtk2;
    mygtk2->import('gtknew');
    my $w = ugtk2->new(N("Congratulations"));
    gtkadd($w->{rwindow},
           gtkpack_(gtknew('VBox', border_width => 10),
                    1, gtknew('Label', width => 540, text => $msg, alignment => [0, 0], padding => [25, 15], line_wrap => 1),
                    0, gtknew('HSeparator'),
                    0, gtkpack(create_hbox('end'),
                               gtknew('Button', text => N("Finish"),
                                      clicked => sub { Gtk2->main_quit })
                       ),
           ),
    );
    mygtk2::set_main_window_size($w->{rwindow});
    $w->{real_window}->grab_focus;
    $w->{real_window}->show_all;
    $w->main;
}

###
### duplicate code
###

#- from disdrake::interactive
{
    package diskdrake::interactive;
  sub diskdrake_interactive_Done {
    my ($in, $all_hds) = @_;
    eval { raid::verify($all_hds->{raids}) };
    if (my $err = $@) {
	$::expert or die;
	c::set_tagged_utf8($err);
	$in->ask_okcancel('', [ formatError($err), N("Continue anyway?") ]) or return;
    }
    foreach (@{$all_hds->{hds}}) {
	if (!write_partitions($in, $_, 'skip_check_rebootNeeded')) {
	    return if !$::isStandalone;
	    $in->ask_yesorno(N("Quit without saving"), N("Quit without writing the partition table?"), 1) or return;
	}
    }
    #- skip that fstab/reboot steps
    if (!$::isInstall && 0) { 
	my $new = fs::fstab_to_string($all_hds);
	if ($new ne $all_hds->{current_fstab} && $in->ask_yesorno('', N("Do you want to save /etc/fstab modifications"), 1)) {
	    $all_hds->{current_fstab} = $new;
	    fs::write_fstab($all_hds);
	}
	update_bootloader_for_renumbered_partitions($in, $all_hds);

	if (any { $_->{rebootNeeded} } @{$all_hds->{hds}}) {
	    $in->ask_warn('', N("You need to reboot for the partition table modifications to take place"));
	    tell_wm_and_reboot();
	}
    }
    if (my $part = find { $_->{mntpoint} && !maybeFormatted($_) } fs::get::fstab($all_hds)) {
	$in->ask_okcancel('', N("You should format partition %s.
Otherwise no entry for mount point %s will be written in fstab.
Quit anyway?", $part->{device}, $part->{mntpoint})) or return if $::isStandalone && 0; #- no, please
    }
    1;
  }
}

# forked from interactive::wait_message
sub copying_message {
    my ($o, $title, $image, $message, $b_temp) = @_;

    my $w = $o->wait_messageW($title, N("Copying in progress"), 
		ugtk2::gtknew('VBox', padding => 5, children_tight => [
					$image, $message, ]),1);
    push @tempory::objects, $w if $b_temp;
    my $b = before_leaving { $o->wait_message_endW($w) };

    #- enable access through set
    MDK::Common::Func::add_f4before_leaving(sub { $o->wait_message_nextW($_[1], $w) }, $b, 'set');
    $b;
}

# forked from interactive::gtk::wait_message_with_progress_bar
sub copying_message_with_progress_bar {
    my ($in, $o_titlei, $start_picture) = @_;

    my $progress = Gtk2::ProgressBar->new;
    my $image = ugtk2::gtkcreate_img($start_picture);
    my $w = copying_message($in, $o_title, $image, $progress);
    my $displayed;
    $progress->signal_connect(expose_event => sub { $displayed = 1; 0 });
    $w, sub {
	my ($msg, $current, $total) = @_;
	if ($msg) {
	    $w->set($msg);
	}

	if ($total) {
	    $progress or internal_error('You must first give some text to display');
	    $progress->set_fraction($current / $total);
	    $progress->show;
	    $displayed = 0;
	    mygtk2::flush();
	} else {
	    $progress->hide if !$total;
	}
	},
	sub {
		my ($new_image_file) = @_;
		$image->set_from_file($new_image_file);
    	};
}

#
# Returns array with files, used to show advertising, while copying is performed
# Loop counter is also loaded, and to simplify further processing list with filenames
# is simply multiplied by loop counter (if any)
#
# Loop counter is stored in file LOOP in the same directory. It should contain only
# amount of loops as integer in text format. For example, 2. Loop counter is set to 1
# by default.
# 
sub get_advert_list {
	my $loop_counter = 1;
	my @tmp = ();
	# First we need to form list of the files in the proper directory
	my $advert_dir = "/usr/share/libDrakX/advert/" . (($ENV{'LC_MESSAGES'} =~ m/ru_RU/) ? 'ru/' : 'en/');
	if ( ! -d $advert_dir) { print "DIR not found!!!"; return @tmp } ;
	opendir(ADDIR, $advert_dir) || die "Cannot open $advert_dir!!!";
	while (defined($filename = readdir(ADDIR))) {
		if ($filename =~ /^LOOP$/) {
			# loop counter configuration file has been found! 
			open (L, $advert_dir.$filename);
			$line = <L>;
			close L;
			($loop_counter) = $line =~ /(\d+)/;			
		}
		if ($filename =~ /(png)|(jpg)/) {
			# file with advertising image has been found
			# on this step we simply add it to the temporary array
			push(@tmp,$advert_dir.$filename);
		}
	}
	closedir(ADDIR);	

	# Now we should sort @tmp array to guarantee right sequence when images
	# are presented to user. Images can have arbitrary filenames, but they 
	# should be end by sequence suffix -02.png or -3.jpg for example.  
	@sorted_tmp = sort {
			($v1) = $a =~ /-(\d+)\./;
			($v2) = $b =~ /-(\d+)\./;
			$v1 cmp $v2;
			} @tmp;

	# Final touch - factor list of images by $loop_counter
	my @retArray = ();
	if ($loop_counter == 0) { $loop_counter = 1;}
	for ( $i = 0; $i < $loop_counter; $i++) {
		push (@retArray,@sorted_tmp);
	}
	return @retArray;
}
