#!/usr/bin/perl -w

use Bootloader::Tools;
use Getopt::Long;
use Pod::Usage;
use File::Temp;



=head1 NAME

multi-update-setup - prepare the system for split brain update

=head1 SYNOPSIS

multi-update-setup [operation] [options]

operation is one of --partition, --clone --bootloader or --complete.

valid options are --help, --verbose.

=head1 DESCRIPTION

B<multi-update-setup> will let you setup your system so that you can use the
split-brain update method,

=head1 OPERATIONS

=over 8

=item B<--partition>

creates additional partition for the other copy of the system. Use this operation
if you need to do further updates to the disk (e.g. create a data partition)

=item B<--clone>

clones the system to the prepared partition. Use after calling --partition.

=item B<--bootloader>

update the bootloader configuration on both systems. Use after calling --clone

=item B<--complete>

creates the Split Brain set-up via a single command.


=back

=head1 PARAMETER

=over 8

=item B<--help>

Print a brief help message and exits.

=item B<--verbose>

Print more detailed messages

=back

=cut


#internal variables storing system info
$im1_root = $im2_root = "";
$im1_grub_boot = $im2_grub_boot = "";
$disk_device = "";
$im1_root_part = $im2_root_part = "";

#options
my %oper;
my ($opt_verbose, $opt_help)
    = (0,0);

sub debug {
    $opt_verbose || return;
    my $msg = shift;
    print "$msg\n";
}

sub initialize {
    print "Reading system information...";
    debug ("");
    my $mp = Bootloader::Tools::ReadMountPoints();
    $im1_root = $mp->{"/"};
    if ($im1_root =~ m/^(.*)-part([0-9]+)$/) {
	$disk_device = "$1";
	my $im1_part_nr = $2;
	my $im2_part_nr = $im1_part_nr + 1;
	$im2_root = "$disk_device-part$im2_part_nr";
	$im1_root_part = "-part$im1_part_nr";
	$im2_root_part = "-part$im2_part_nr";
    }
    elsif ($im1_root =~ m/^(.*[0-9]+)p([0-9]+)$/) {
	$disk_device = "$1";
	my $im1_part_nr = $2;
	my $im2_part_nr = $im1_part_nr + 1;
	$im2_root = $disk_device . "p" . $im2_part_nr;
	if ($disk_device =~ m/^.*\/([^\/]+)$/)
	{
	    $im1_root_part = "$1$im1_part_nr";
	    $im2_root_part = "$1$im2_part_nr";
	}
	else
	{
	    die "\nCannot initialize the disk information.\n";
	}
    }
    elsif ($im1_root =~ m/^([^0-9]+)([0-9]+)$/) {
	$disk_device = "$1";
	my $im1_part_nr = $2;
	my $im2_part_nr = $im1_part_nr + 1;
	$im2_root = "$disk_device$im2_part_nr";
	if ($disk_device =~ m/^.*\/([^\/]+)$/)
	{
	    $im1_root_part = "$1$im1_part_nr";
	    $im2_root_part = "$1$im2_part_nr";
	}
	else
	{
	    die "\nCannot initialize the disk information.\n";
	}
    }
    debug ("This root partition: $im1_root");
    debug ("Another system partition: $im2_root");
    debug ("The disk: $disk_device");
    debug ("Partition to fstab: $im1_root_part $im2_root_part");
    $im2_root ne "" || die "\nInitializatoin failed.\n";
# FIXME: assumes partnr 1 + 2
    $im1_grub_boot = "(hd0,0)";
    $im2_grub_boot = "(hd0,1)";
    print "Done.\n";
}

sub partition {
    print "Partitioning $disk_device...";
    debug ("");
    open (FILE, "parted -s $disk_device unit cyl print |") ||
        die ("\nCannot read partition table\n");
    my $start = 0;
    my $end = 0;
    while (my $line = <FILE>) {
	if ($line =~ /^[ \t]?([0-9]+)[ \t]+([0-9]+)cyl[ \t]+([0-9]+)cyl.*/) {
	    $start = $2;
	    $end = $3;
	    last;
	}
    }
    close FILE;
    $end > 0 ||	die "\nFailed to detect the partition";
    debug ("Partition start: $start, end: $end");
    my $size = $end - $start;
    $start = $end + 1;
    $end = $start + $size;
    my $cmd = "parted -s $disk_device unit cyl mkpart primary ext3 $start $end";
    debug ("Running command: $cmd");
    system ("$cmd >/dev/null 2>/dev/null") == 0 || die "\nCannot create the partition for other system.\n";
    print "Done.\n";
}

sub mkfs {
    print "Creating filesystem on $im2_root...";
    debug ("");
    sleep (2);
    my $cmd = "/sbin/mkfs.ext3 -q $im2_root";
    debug ("Running command: $cmd");
    system ("$cmd") == 0 || die "\nCannot create filesystem on $im2_root.\n";
    print "Done.\n";
}

sub copy_system {
    print "Copying system to $im2_root...";
    debug ("");
    my $target = shift;
    my $cmd = "mount $im2_root $target; cp -a --one-file-system / $target";
    debug ("Running command: $cmd");
    system ($cmd) == 0 || die "\nCannot copy system to $im2_root.\n";
    print "Done.\n";
}

sub mount {
    print "Mounting target system...";
    debug ("");
    my $target = shift;
    my $root = shift;
    if ($root) {
	my $cmd = "mount $im2_root $target";
	debug ("Running command: $cmd");
	system ($cmd) == 0 || die "Cannot mount $im2_root.\n";
    }
    my $cmd = "mount --bind /sys $target/sys; mount --bind /dev $target/dev; mount --bind /proc $target/proc";
    debug ("Running command: $cmd");
    system ($cmd) == 0 || die "\nCannot mount target system.\n";
    print "Done.\n";
}

sub update_target_fstab {
    print "Updating target system /etc/fstab...";
    debug ("");
    my $target = shift;
    my $root1 = 
    my $cmd = "sed 's/$im1_root_part/$im2_root_part/g' </etc/fstab > $target/etc/fstab";
    debug ("Running command: $cmd");
    system ($cmd) == 0 || die "\nCannot modify target fstab.\n";
    print "Done.\n";
}

sub update_bootloader {
    print "Updating bootloader configuration...";
    debug ("");
    my $target = shift;
    my $cmd = "sed 's/$im1_grub_boot/$im2_grub_boot/g' </boot/grub/menu.lst | sed 's/$im1_root_part/$im2_root_part/g' > $target/boot/grub/menu.lst";
    debug ("Running command: $cmd");
    system ($cmd) == 0 || die "\nCannot modify bootloader configuration.\n";

    $cmd = "sed 's/$im1_grub_boot/$im2_grub_boot/g' </etc/grub.conf | sed  's/(hd0)/$im2_grub_boot/g'>$target/etc/grub.conf";
    debug ("Running command: $cmd");
    system ($cmd) == 0 || die "\nCannot modify bootloader configuration.\n";

    $cmd = "sed 's/(hd0)/$im1_grub_boot/g' </etc/grub.conf >/etc/grub.conf.tmp && mv /etc/grub.conf.tmp /etc/grub.conf";
    debug ("Running command: $cmd");
    system ($cmd) == 0 || die "\nCannot modify bootloader configuration.\n";
    print "Done.\n";
}

sub crosslink_bootloaders {
    print "Cross-linking system bootloaders...";
    debug ("");
    my $target = shift;
    my $cmd = "cat >>/boot/grub/menu.lst <<EOF
title $im2_root
    chainloader $im2_grub_boot+1
EOF
";
    debug ("Running command: $cmd");
    system ($cmd) == 0 || die "\nCannot cross-link bootloaders.\n";

    $cmd = "cat >>$target/boot/grub/menu.lst <<EOF
title $im1_root
    chainloader $im1_grub_boot+1
EOF
";
    debug ("Running command: $cmd");
    system ($cmd) == 0 || die "\nCannot cross-link bootloaders.\n";
    print "Done.\n";
}

sub init_bootloader {
    print "Initializing bootloader...";
    debug ("");
    my $target = shift;
    my $cmd = "chroot $target /sbin/mkinitrd; chroot $target bash -c 'cat /etc/grub.conf | grub --batch --device-map=/boot/grub/device.map'";
    debug ("Running command: $cmd");
    system ($cmd) == 0 || die "\nCannot initialize bootloader.\n";
    $cmd = "/sbin/mkinitrd; cat /etc/grub.conf | grub --batch --device-map=/boot/grub/device.map";
    debug ("Running command: $cmd");
    system ($cmd) == 0 || die "\nCannot initialize bootloader.\n";
    $cmd = "dd if=/usr/lib/boot/master-boot-code of=$disk_device bs=440 count=1";
    debug ("Running command: $cmd");
    system ($cmd) == 0 || die "\nCannot initialize bootloader.\n";
    print "Done.\n";
}

sub clean {
    print "Cleaning up...";
    debug ("");
    my $target = shift;
    my $cmd = "umount $target/sys 2>/dev/null; umount $target/proc 2>/dev/null; umount $target/dev 2>/dev/null; umount $target 2>/dev/null; rmdir $target 2>/dev/null;";
    debug ("Running command: $cmd");
    system ($cmd) == 0 || die "\nClean-up failed\n";
    print "Done.\n";
}

my $tmpdir = File::Temp::mkdtemp( "/tmp/split-brain-XXXXXX" );

GetOptions (\%oper,
    'partition|p'      ,
    'clone|l'    ,
    'bootloader|b'    ,
    'complete|c'   ,
    'help|h'     => \$opt_help,
    'verbose|v' => \$opt_verbose)
    or pod2usage(2);
pod2usage(1) if $opt_help;

pod2usage("Specify exactly one operation, either 'partition', 'clone', 'bootloader' or 'complete'")
    unless scalar keys(%oper) == 1;


initialize();
if (defined $oper{"partition"}) {
    print "Preparing disk partitioning...\n";
    partition();
    exit 0;
}
elsif (defined $oper{"clone"}) {
    print "Cloning system to prepared partition...\n";
    mkfs();
    copy_system($tmpdir);
    update_target_fstab($tmpdir);
}
elsif (defined $oper{"bootloader"}) {
    print "Updating bootloader configuration...\n";
    mount($tmpdir, 1);
    update_bootloader($tmpdir);
    crosslink_bootloaders($tmpdir);
    init_bootloader($tmpdir);
}
elsif (defined $oper{"complete"}) {
    print "Performing full set-up...\n";
    partition();# destructive
    mkfs();#destructive
    copy_system($tmpdir);#destructive
    update_target_fstab($tmpdir);#destructive
    mount($tmpdir, 0);
    update_bootloader($tmpdir);#destructive
    crosslink_bootloaders($tmpdir);#destructive
    init_bootloader($tmpdir);#destructive
}
clean($tmpdir);

exit 0;



