#!/usr/bin/env perl

# Use mandatory external modules
use strict;
use Cwd;
use File::Basename;
use Data::Dumper;
use POSIX;
use Config;
use Time::HiRes;
use DBI;

# Determine installation dir name
our $SELF_DIR = dirname(dirname(dirname(Cwd::abs_path(__FILE__))));

# Include parts of the system
require $SELF_DIR . '/lib/config.pm';
require $SELF_DIR . '/lib/log.pm';

# Read config file and status
our $config = ReadConfig("mmm_lvm.conf");

my $res = CreateLvmSnapshot();

print "$res\n";
exit(0);

#-----------------------------------------------------------------
sub CreateLvmSnapshot() {
    my $this = $config->{this};
    my $host = $config->{host}->{$this};
    if (!$host) {
        return "ERROR: Invalid 'this' value: '$this'!";
    }
    
    # Create mount directory
    my $dump_dir = $host->{lvm_mount_dir};
    system("mkdir -p $dump_dir");
    unless (-d $dump_dir && -w $dump_dir && -r $dump_dir && -x $dump_dir) {
        return "ERROR: Directory $dump_dir has invalid permissions (it must be readable/writable)" 
    }

    # Check mount dir
    if (scalar(glob("$dump_dir/*"))) {
        return "ERROR: LVM mount dir is not empty!";
    }
    
    # Connect to Database
    my $dbh = ConnectToDatabase();
    unless ($dbh) {
        return "ERROR: Can't connect to database! Error = " . $DBI::errstr;
    }
    
    # Lock tables
    my $res = LockTables($dbh);
    unless ($res) {
        return "ERROR: Can't lock tables! Error = " . $DBI::errstr;
    }

    # Create position file
    my $pos_info = {};
    $pos_info ->{host} = $config->{this};
    $res = GetPosInfo($dbh, $pos_info);
    unless ($res =~ /^OK/) {
        UnlockTables($dbh);
        return "ERROR: Can't get position info: $res";
    }

    # Create and mount snapshot
    my $snapshot_res = CreateSnapshot();
    unless ($snapshot_res =~ /^OK/) {
        UnlockTables($dbh);
        return "ERROR: Can't create or mount snapshot: $res";
    }
    
    # Unlock tables
    UnlockTables($dbh);

    # Change dir to snapshot and create _mmm directory
    chdir($dump_dir);
    system("mkdir -p _mmm");
    
    SavePosInfo($pos_info, "_mmm/status.txt");
    $res = system("cp $host->{my_cnf} _mmm/");
    if ($res) {
        return "ERROR: Can't copy mysql config file to backup!";
    }

    return "OK: Snapshot created!";
}

#-----------------------------------------------------------------
sub ConnectToDatabase() {
    my $this = $config->{this};
    my $host_info = $config->{host}->{$this};
    
    # get this connection info
    my $host = $host_info->{mysql_host};
    my $port = $host_info->{mysql_port};
    my $user = $host_info->{mysql_user};
    my $pass = $host_info->{mysql_password};
    
    # connect to server
    my $dsn = "DBI:mysql:host=$host;port=$port";
    return DBI->connect($dsn, $user, $pass, { PrintError => 0 });
}

#-----------------------------------------------------------------
sub LockTables($) {
    my $dbh = shift;
    print "Flush tables...\n";
    my $res = $dbh->do("flush tables with read lock");
    print "Res: '$res'\n";

#    print "Sleeping 10 sec...";
#    sleep(15);
#    print "Done\n";

    print "Sync()...";
    system("sync");
    print "Done\n";
    print "Sleeping 1 sec...";
    sleep(1);
    print "Done\n";
    print "Sync()...";
    system("sync");
    print "Done\n";
    return $res;
}
#-----------------------------------------------------------------
sub UnlockTables($) {
    my $dbh = shift;
    return $dbh->do("unlock tables");
}

#-----------------------------------------------------------------
sub GetPosInfo($$) {
    my $dbh = shift;
    my $pos_info = shift;
    
    # Get master status info
    my $sth = $dbh->prepare("SHOW MASTER STATUS");
    unless ($sth && $sth->execute) {
        return "ERROR: Can't get master status information! Error: " . $DBI::errstr;
    }
    $pos_info->{master} = $sth->fetchrow_hashref;
    $sth->finish;

    # Get slave status info
    $sth = $dbh->prepare("SHOW SLAVE STATUS");
    unless ($sth && $sth->execute) {
        return "ERROR: Can't get slave status information! Error: " . $DBI::errstr;
    }
    $pos_info->{slave} = $sth->fetchrow_hashref;
    $sth->finish;

    return "OK: Got status info!";
}

#-----------------------------------------------------------------
sub SavePosInfo($$) {
    my $pos_info = shift;
    my $file = shift;
    
    open(F, ">$file") || return "Error: Can't create pos file: $!";
    print F Dumper($pos_info);
    close(F);
    
    return "OK";
}

#-----------------------------------------------------------------
sub CreateSnapshot() {
    my $this = $config->{this};
    my $host = $config->{host}->{$this};

    my @command = (
        $config->{bin_lvcreate}, '--snapshot', 
        '--size', $host->{lvm_snapshot_size}, 
        '--name', 'mmm_snapshot', 
        join('/', "/dev" , $host->{lvm_volume_group}, $host->{lvm_data_volume})
    );
    
    my $lvm_res = system(@command);
    print "LVM res = '$lvm_res'\n";
    
    if ($lvm_res) {
        return "ERROR: Can't create snapshot: $!";
    }

    my $mount_opts = $host->{lvm_mount_opts};
    $mount_opts = "-o rw" unless ($mount_opts);

    my $res = system("mount " . $mount_opts . " /dev/" . $host->{lvm_volume_group} . "/mmm_snapshot " . $host->{lvm_mount_dir});
    if ($res) {
        return "ERROR: Can't mount snapshot: $!\n";
    }
    
    return "OK";
}
