#!/usr/bin/php
<?php

$dirname=dirname(__FILE__);
$is_debug = in_array('--debug', $argv);
$is_force = in_array('--force', $argv);
if (in_array('--help', $argv))
{
    echo "Usage: sync_master_db [--debug] [--help] [--force]\n";
    echo "--debug: print debug messages\n";
    echo "--force: force replica even if not needed\n";
    echo "--help: print this help\n";
    exit(0);
}


$host = "localhost";
$user = "euro3g";
$pass = "euro3g";
$db   = "eurotest";

$LOGFILE="log/replica.log";

chdir('/opt/euro-beta');

define('LOCKFILE_DUP', '/tmp/duplicate_master_db.lock');
define('LOCKFILE_CLONE', '/tmp/clone_db.lock');

$locked=[];
function lock($lockfilename)
{
    global $locked;
    $lockHandle = fopen($lockfilename, 'c+');
    if (!$lockHandle) {
        mylog( "Unable to create or open lock file: $lockfilename");
        exit(1);
    }
    if (!flock($lockHandle, LOCK_EX | LOCK_NB)) {
        mylog( "Another process is already running. ($lockfilename)");
        fclose($lockHandle);
        exit(2);
    }
    $locked[$lockfilename]=$lockHandle;
    return $lockHandle;
}

function unlock($lockfilename)
{
    global $locked;
    if (isset($locked[$lockfilename])) 
    {
        $lockHandle = $locked[$lockfilename];
        flock($lockHandle, LOCK_UN);
        fclose($lockHandle);
        unlink($lockfilename);
        unset($locked[$lockfilename]);
    }
}

function debug($text)
{
    global $is_debug;
    if ($is_debug) {
        echo $text . "\n";
    }
}

function mylog($text)
{
    global $LOGFILE;
    file_put_contents($LOGFILE, date("Y-m-d H:i:s") . " " . $text . "\n", FILE_APPEND);
    debug($text);
}

function logdie($text)
{
    mylog($text);
    die($text . "\n");
}

function get_config($varname, $ip=0)
{
    $HOST="";
    if ($ip!=0)
    {
        $HOST="EURO3G_FORCED_HOST_ID=$ip ";
    }
    $cmd="{$HOST}PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin systools/get_eurodb_config_val $varname | tr -d '\\n'";
    debug("Executing: $cmd");
    $result=`$cmd`;
    debug("Got config $varname" . ($result === '' ? " (not set)" : " = $result"));
    return $result;
}

function get_config_number($varname, $ip=0)
{
    $result = get_config($varname, $ip);
    if ($result === '') {
        return 0;
    }
    if (!is_numeric($result)) {
        return 0;
    }
    return (int)$result;
}
function get_config_as_master($varname)
{
    $master=get_master_last_nibble();
    $result=get_config($varname, $master);
    return $result;
}

function get_config_number_as_master($varname)
{
    $master=get_master_last_nibble();
    $result=get_config_number($varname, $master);
    debug("Got config as master ip=$master $varname" . ($result ==0 ? " (not set)" : " = $result"));
    return $result;
}

function set_config($type, $varname, $value, $ip=0)
{
    $HOST="";
    if ($ip!=0)
    {
        $HOST="EURO3G_FORCED_HOST_ID=$ip ";
    }
    debug("Setting config $varname = $value (type=$type)");
    return `{$HOST}PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin systools/set_eurodb_config_val '$varname' '$type' '$value'`;
}

function getHost()
{
    /* eurodb.ini:
Driver=QMYSQL3
Database=eurotest
Host=127.0.0.1
UserName=euro3g
Password=jecIV
*/

    $configFile = '/opt/euro-beta/eurodb.ini';
    if (!file_exists($configFile)) {
        die("File eurodb.ini non trovato: $configFile");
    }

    $config = parse_ini_file($configFile);
    if (!$config || !isset($config['Host'])) {
        die("Impossibile leggere il file di configurazione o 'Host' non trovato.");
    }

    return  $config['Host'];
}

function is_local_db()
{
    $host = getHost();
    return $host === '127.0.0.1' || $host === 'localhost';
}

function get_master_last_nibble()
{
    if (is_local_db()) {
        return 0;
    }
    $host = getHost();
    $parts = explode('.', $host);
    if (count($parts) !== 4) {
        return 0;
    }
    return (int)$parts[3];
}

/* chiamato come script esterno
function clone_db($conn, &$locktime) 
{
    global $db;

    // Query per ottenere le tabelle
    $result = $conn->query("SHOW TABLES");

    $tables=[];
    if ($result) {
        while ($row = $result->fetch_array()) 
        {
            $tables[] = $row[0];
        }
    }

    // Clonazione delle tabelle
    // creiamo un ramdisk, copy to ramdisk sara' molto piu' veloce che nel harddisk
    $ramdir="/tmp/clone_db_ramdisk";
    system("mkdir -p $ramdir");
    system("umount $ramdir 2>/dev/null"); // umount eventuale ramdisk pre esistente
    system("mount -t tmpfs -o size=2G tmpfs $ramdir"); // grande quanto basta :-)

    // Creazione di una directory temporanea con un nome univoco
    $tempDir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('clone_', true);

    if (!mkdir($tempDir, 0700, true)) {
        logdie("Errore nella creazione della directory temporanea: $tempDir");
    }

    system("nice cat /var/lib/mysql/$db/* >/dev/null"); // read all tables to have it in cache

    $startlock = round(microtime(true) * 1000);

    // lock all tables
    $query="FLUSH TABLES `" . implode("`,`", $tables) . "` WITH READ LOCK";
    debug($query);
    $conn->query($query);

    system("cp -ar /var/lib/mysql/$db $ramdir");

    // Unlock all tables
    $query = "UNLOCK TABLES";
    debug($query);
    $conn->query($query);

    $locktime = round(microtime(true) * 1000) - $startlock;

    system("cp -ar $ramdir/$db $tempDir"); // move to the disk
    system("umount $ramdir");
    system("chown -R euro3g:euro3g $tempDir");
    system("chmod -R 600 $tempDir/$db/*");


    $unixtime=time();

    if (!is_dir("/tmp/replica")) 
    {
        if (file_exists("/tmp/replica")) {
            system("rm -rf /tmp/replica");
        }
        mkdir("/tmp/replica", 0700);
        system("chown euro3g:euro3g /tmp/replica");
    }
    $newDir = "/tmp/replica/$unixtime";

    if (!rename("$tempDir/$db", $newDir)) {
        logdie("Errore nel rinominare la directory temporanea: $tempDir a $newDir");
    }
    rmdir($tempDir);
    return $unixtime;
}
*/

function clear_old_replicas()
{
    if (!is_dir("/tmp/replica")) 
    {
        return;
    }
    $to_remove=[];
    $files = scandir("/tmp/replica");
    $now=time();
    foreach ($files as $file) 
    {
        if ($file === '.' || $file === '..') {
            continue;
        }
        $path = "/tmp/replica/$file";
        $timestamp = (int)explode('.', $file)[0];
        $oldness=$now - $timestamp;
        $to_remove[] = ["name" => $path, "age" => $oldness];
    }
    // sort by age ascending
    usort($to_remove, function($a, $b) {
        return $a['age'] - $b['age'];
    });

    // keep last 60 minutes but at least two
    $count=0;
    foreach ($to_remove as $item) 
    {
        $count++;
        if ($item['age'] < 3600 || $count<=2) 
        {
            continue;
        }
        system("rm -rf " . $item['name']);
    }
}

function rsync($source_host, $source_path, $dest_path)
{
    $start = round(microtime(true) * 1000);
    $USER="euro3g";
    $KEY="/root/.ssh/id_dsa_rsync";
    $RSYNC_PARMS="--delete --archive --update --exclude='.*'";
    $SSH_CMD="ssh -i $KEY -o StrictHostKeyChecking=no";
    if (!preg_match('/OpenSSH_[3-6]/', `ssh -V 2>&1`))
    {
        $SSH_CMD.=" -o HostKeyAlgorithms=+ssh-dss -o KexAlgorithms=+diffie-hellman-group1-sha1 -o PubkeyAcceptedKeyTypes=+ssh-dss -c aes256-cbc";
    }      
    
    $cmd="rsync -e '$SSH_CMD' $RSYNC_PARMS '$USER@$source_host:$source_path' '$dest_path'";
    mylog("Eseguo: $cmd");
    system($cmd, $ret);
    if ($ret != 0) 
    {
        logdie("Errore nell'esecuzione di rsync, codice di ritorno: $ret");
    }
    $milis = round(microtime(true) * 1000) - $start;
    mylog("Rsync eseguito con successo in $milis millisecondi");

}

function ssh($host, $command, $pipe)
{
    $USER="euro3g";
    $KEY="/root/.ssh/id_dsa_rsync";
    $SSH_CMD="ssh -i $KEY -o StrictHostKeyChecking=no";
    if (!preg_match('/OpenSSH_[3-6]/', `ssh -V 2>&1`))
    {
        $SSH_CMD.=" -o HostKeyAlgorithms=+ssh-dss -o KexAlgorithms=+diffie-hellman-group1-sha1 -o PubkeyAcceptedKeyTypes=+ssh-dss -c aes256-cbc";
    }      
    if ($pipe) {
        $pipe=" | $pipe";
    } else {
        $pipe="";
    }
    $cmd="$SSH_CMD $USER@$host '$command'$pipe";
#    mylog("Eseguo: $cmd");
    $cmd_escaped = str_replace("'", "'\\''", $cmd);
    $full_cmd="bash -o pipefail -c '$cmd_escaped'";
    system($full_cmd, $ret); 
    if ($ret != 0) 
    {
        mylog("Errore nell'esecuzione di ssh, codice di ritorno: $ret");
        mylog("Comando eseguito: $full_cmd");
        exit(1);
    }
#    mylog("SSH eseguito con successo");

}

function replica_master()
{
    global $dirname;
    global $is_force;
    global $host, $user, $pass, $db;

    sleep(15);
    lock(LOCKFILE_DUP);
    clear_old_replicas();
    $period=get_config("ReplicaDBPeriod", 255);
    if (!$is_force && (!$period || $period < 1)) 
    {
        mylog("Replica disabilitata (ReplicaDBPeriod=$period), esco");
        return;
    }
    debug("Replica abilitata, periodo=$period minuti");
    $last_replica=get_config("ReplicaTimestamp");
    if (!$last_replica || !file_exists("/tmp/replica/$last_replica.sql"))
    {
        $last_replica=0;
    }
    $now=time();
    if (!$is_force && ($now - $last_replica) < $period*60) 
    {
        mylog("Replica eseguita " . round(($now - $last_replica)/60) . " minuti fa, attendo $period minuti");
        return;
    }
    
    // Connessione
    $conn = new mysqli($host, $user, $pass, $db);
    if ($conn->connect_error) {
        logdie("Connessione fallita: " . $conn->connect_error);
    }

    // is there some request from clients?
    $result = $conn->query("SELECT COUNT(*) FROM config WHERE var_name RLIKE 'ReplicaRequest'");
    if ($result) 
    {
        $row = $result->fetch_array();
        $count = (int)$row[0];
        if (!$is_force && $count==0) 
        {
//            mylog("Nessuna richiesta di replica dai client, esco");
            debug("Nessuna richiesta di replica dai client, esco");
            $conn->close();
            return;
        }
    }
    $starttime = round(microtime(true) * 1000);
    

# new code using backup_db

    $tempfile = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('replica_', true);
    exec( "$dirname/systools/backup_db --message replica $db --nozip --nodb --no-progress --outfile $tempfile", $output, $res);
    if ($res != 0) 
    {
        logdie("Errore nell'esecuzione di clone_db, codice di ritorno: $res");
    }
#    foreach ($output as $line) {
#        mylog($line);
#    }


# original code using clone_db
#
#    $tempdir = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('replica_', true);
#    $ret=null;
#    exec("$dirname/systools/clone_db --host $host --user $user --password $pass --database $db --destination $tempdir --wait 2>/dev/null", $result, $ret);
#    if ($ret != 0) 
#    {
#        logdie("Errore nell'esecuzione di clone_db, codice di ritorno: $ret");
#    }
#    foreach ($result as $line) {
#        mylog($line);
#    }

    $timestamp=time();
    @mkdir("/tmp/replica", 0700);
#    rename($tempdir, "/tmp/replica/$timestamp");
    rename("$tempfile.sql", "/tmp/replica/$timestamp.sql");
    system("chown -R euro3g:euro3g /tmp/replica");
    set_config("string", "ReplicaTimestamp", $timestamp);
    $conn->query("DELETE FROM config WHERE var_name = 'ReplicaRequest'");
    $elapsed=round(microtime(true) * 1000) - $starttime;
    mylog("Replica eseguita con successo, timestamp=$timestamp, elapsed $elapsed ms");
    $conn->close();
}

function purge_mysql_log()
{
	$RETAIN_MIN=60;
	`
		find /var/log/mysql -type f -mmin +$RETAIN_MIN -iname 'mariadb-bin.*' -exec rm -f {} \; ;
		find /var/log/mysql -type f -mmin +$RETAIN_MIN -iname 'mysql-bin.*' -exec rm -f {} \; 	;
		ls -1 /var/log/mysql/mysql-bin* 2>/dev/null | sort > /var/log/mysql/mariadb-bin.index;
		ls -1 /var/log/mysql/mariadb-bin* 2>/dev/null | sort | grep -v 'mariadb-bin.index'  >> /var/log/mysql/mariadb-bin.index;
		chown mysql.adm /var/log/mysql/mariadb-bin.index;
		cat /dev/null >/var/log/mysql/mysql.log;
	`;
}


function replica_client()
{
    global $dirname;
    global $db;
    global $is_force;

    $starttime = round(microtime(true) * 1000);
    $period=get_config("ReplicaDBPeriod", 255);
    if ($period=='' ) 
    {
        mylog("Replica client: ReplicaDBPeriod non impostato, set to 30 min");
        set_config("int", "ReplicaDBPeriod", 30, 255);
    }

    $master=get_master_last_nibble();
    if ($master==0) 
    {
        mylog("Replica client: il database e' locale, esco");
        return;
    }
    set_config("bool", "ReplicaRequest", 1);
    $replicaImported=get_config_number("ReplicaImported");
    $replicaAvailable=get_config_number_as_master("ReplicaTimestamp");
    if ($replicaImported >= $replicaAvailable && !$is_force) 
    {
        mylog("Replica client: nessuna nuova replica disponibile, imported=$replicaImported, available=$replicaAvailable");
        return;
    }
    $duplock=lock(LOCKFILE_DUP);
    mylog("Replica client: nuova replica disponibile, la importo $replicaAvailable");

    # old code with clone_db
/*    
    $tmpdir="/tmp/replica_tmp";
    system("rm -rf $tmpdir");
    mkdir($tmpdir);
    system("cp -ar --reflink=auto /var/lib/mysql/$db/* $tmpdir");
    rsync(getHost(), "/tmp/replica/$replicaAvailable/", $tmpdir);
    system("chown -R mysql:mysql $tmpdir");
    // replace the db
    $tmp2="/tmp/db_old";
    if (is_dir($tmp2) || is_file($tmp2)) {
        system("rm -rf $tmp2");
    }
    system("service mysql stop");
    system("mv -f /var/lib/mysql/$db $tmp2");
    system("mv -f $tmpdir /var/lib/mysql/$db" );
    system("service mysql start");
    system("rm -rf $tmp2");
*/
    // new code with backup_db

		purge_mysql_log();
    lock(LOCKFILE_CLONE);

    exec("echo service mysql start | at now >/dev/null 2>&1"); # nel caso non fosse partito   // deve essere lanciato tramite AT per non ereditare lock
    system("rm -rf /var/lib/mysql/eurotest_clone");
    // import the sql file to a temporary db
    $pipe=[
//        "grep -Evi '^ *drop database |^ *create database |^ *use '", // consuma troppe risorse, comunque non serve in quanto server manda salvattaggio senza questi commandi
        "$dirname/systools/patch_sql",
        "( echo 'DROP DATABASE IF EXISTS `eurotest_clone`;\n CREATE DATABASE `eurotest_clone`;\n USE `eurotest_clone`'; cat )",
        "nice mysql -hlocalhost -ueuro3g -peuro3g"
    ];

    ssh(getHost(),"cat /tmp/replica/$replicaAvailable.sql", implode(" | ", $pipe) );

    // rename database to the real one
    $tmpdir= sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('replica_tmp_', true);
    $commands=[
        "timeout 120 service mysql stop",
        "mv -f /var/lib/mysql/$db $tmpdir",
        "mv /var/lib/mysql/eurotest_clone /var/lib/mysql/$db",
        "rm -rf $tmpdir",
        "echo service mysql start | at now >/dev/null 2>&1"  // deve essere lanciato tramite AT per non ereditare lock
    ];
    exec(implode(" && ", $commands), $output, $ret);
    unlock(LOCKFILE_CLONE);
    if ($ret != 0) 
    {
        foreach ($output as $line) {
            mylog($line);
        }
        logdie("Errore nell'importazione della replica, codice di ritorno: $ret");
    }

    mylog("Replica client: importazione replica $replicaAvailable completata, elapsed " . round((microtime(true) * 1000-$starttime)) . " ms");
    set_config("string", "ReplicaImported", $replicaAvailable);
    unlock(LOCKFILE_DUP);
}



if (is_local_db()) 
{
    replica_master();
}
else if (get_config("SpinKeyClient"))
{
    replica_client();
}

?>

