#!/usr/bin/perl
use strict;
use warnings;

#  redo-bootloader
#  Copyright (c) Apr 2020 by Terry Neve <terryn94@gmail.com>
#
#  Program to reset the bootloader.
#  Similar to drakboot except that it will run correctly from a LiveCD environment
#  so it can be used to recover a system that is failing to boot.

use lib qw(/usr/lib/libDrakX);

use standalone;
use common;
use interactive;
use fsedit;
use fs;
use fs::type;
use fs::mount;
use bootloader;
use mygtk3;
use ugtk3 qw(:create :helpers :wrappers);
use Gtk3::SimpleList;

sub run_bootwizard {
    my ($in, $all_hds, $fstab, $bootloader) = @_;
    any::setupBootloaderBeforeStandalone($in->do_pkgs, $bootloader, $all_hds, $fstab);
    local $::Wizard_no_previous = 0;
    eval { any::setupBootloaderUntilInstalled($in, $bootloader, $all_hds, $fstab, $ENV{SECURE_LEVEL}) };
    die if $@ && $@ !~ /^wizcancel/;
    if (!$@) {
        $in->ask_okcancel('', N("Bootloader installation completed"));
    }
}

sub do_fixboot {
    my ($in, $all_hds, $fstab, $rootfs) = @_;
    my $bootloader = {};
    
    # mount the chosen partition
    $::prefix = '/mnt/install';
    mkdir_p($::prefix) or die "unable to create $::prefix";
    run_program::run('mount', $rootfs, $::prefix) or die N("mounting partition %s in directory %s failed", $rootfs, $::prefix);
    
    # perform a sanity check
    if (! -f "$::prefix/etc/pclinuxos-release") {
        $in->ask_okcancel('', N("%s does not look like a PCLinuxOS system partition\nContinue anyway?",$rootfs), 0) or $in->exit;
    }
    
    # ensure we've all devices needed for bootloader install
    run_program::run('mount', '--bind', '/dev', "$::prefix/dev");
    run_program::run('mount', '--bind', '/run', "$::prefix/run");
    eval { fs::mount::mount('none', "$::prefix/proc", 'proc') };
    eval { fs::mount::mount('none', "$::prefix/sys", 'sysfs') };
    run_program::run('makedev', $::prefix . '/dev');
    
    # On UEFI we need the ESP mounted too
    if (is_uefi()) {
        my @ESP = grep { isESP($_) && maybeFormatted($_) && !$_->{is_removable} } @$fstab;
        if (@ESP) {
            run_program::run('mount', "/dev/" . $ESP[0]{device}, $::prefix . "/boot/EFI") or die N("mounting ESP partition failed");
        }
    }
    
    run_bootwizard($in, $all_hds, $fstab, $bootloader);
    
    # make sure nothing is left mounted in the new root
    foreach (sort { $b cmp $a } grep { /^$::prefix/ } map { (split)[1] } cat_('/proc/mounts')) {
        system('umount', $_);
    }
}

#
# Main entry point
#
my $in = interactive->vnew('su');
$::isWizard = 1;
$::Wizard_no_previous = 1;

# Get storage configuration
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);

# If we're not running from a LiveCD then just do what drakboot does
if (cat_("/proc/cmdline") !~ /\blivecd=/) {
    my $bootloader = bootloader::read($all_hds);
    run_bootwizard($in, $all_hds, $fstab, $bootloader);
    $in->exit(0);
}

# otherwise Create GUI for user to select root partition
my $title = N("Reset Bootloader");
my $icon = '/usr/share/redo-bootloader/icons/redo-bootloader.png';
$ugtk3::wm_icon = $icon;
my $w = ugtk3->new($title);
$::main_window = $w->{real_window};

my $diskview = Gtk3::SimpleList->new(
        N("Partition           ") => "text",
        N("Type") => "text",
        N("Size") => "text",
        N("Label") => "text",
);

# Populate the partition selection list.  Only include proper Linux partitions
foreach my $pt (grep { isTrueLocalFS($_) } @$fstab) {
    push @{$diskview->{data}}, ["/dev/" . $pt->{device}, $pt->{fs_type}, formatXiB($pt->{size}, 512),
        $pt->{device_LABEL} ? $pt->{device_LABEL} : $pt->{flag}];
}

# Handler for when the fix button is clicked
sub fixboot_clicked {
    my ($selected) = $diskview->get_selected_indices;
    do_fixboot($in, $all_hds, $fstab, $diskview->{data}[$selected][0]);
    $in->exit(0);
}

# Build and display the selection window
gtkadd($w->{window},
    gtknew('VBox', spacing => 5, children => [
        $::isEmbedded ? () : (0, Gtk3::Banner->new($icon, $title)),
        0, gtknew('HBox', children => [
            1, gtknew('Label', alignment => [ 0, 0 ], text_markup => '<b>' . "Select root partition" . '</b>'),
            ]),
        0, gtknew('ScrolledWindow', height => 160, child => $diskview),
        0, gtknew('HButtonBox', spacing => 6, layout => 'end', children_loose => [
            gtknew('Button', text => N("FixBoot"), clicked => sub { fixboot_clicked() }),
            gtknew('Button', text => N("Quit"), clicked => sub { Gtk3->main_quit }),
        ]),
    ]),
);

$w->main;
