#!/usr/bin/php
<?php

$rc = 0;

ini_set('date.timezone', 'Europe/Zurich');

$gMysqlDump  = trim(`which mysqldump`);
$gaBackupPolicy = array('daily', 'weekly', 'monthly', 'quarterly', 'yearly');

// Parse command line

$shortopts  = "";

$longopts  = array(
  "target:"
, "policy:"
, "log:"
, "retention:"
, "backupdir:"
, "mode:"
, "type:"
, "help"
, "simulate"
, 'schema:'
, "archive"
, "archivedir:"
, "config:"
, 'per-schema'
);

$aParameter = getopt($shortopts, $longopts);

if ( isset($aParameter['help']) ) {
  $rc = 0;
  printUsage();
  exit($rc);
}

if ( ! isset($aOptions['config']) ) {
  $aOptions['config'] = '';
}

// Do we have a configuration file?
$gMysqlBman = '';
if ( isset($aParameter['config']) && is_readable($aParameter['config']) ) {
  $gMysqlBman = $aParameter['config'];
  list($rc, $aConfig) = readConfigurationFile($gMysqlBman);
  $aOptions = $aConfig;
}

// Options = Config + Parameter
// Rule: Parameters overwrite Config
foreach ( $aParameter as $key => $value ) {
  $aOptions[$key] = $value;
}

// Set some mandatory defaults
if ( (! isset($aOptions['log'])) || ($aOptions['log'] == '') ) {
  $aOptions['log'] = './mysql_bman.log';
}
if ( (! isset($aOptions['backupdir'])) || ($aOptions['backupdir'] == '') ) {
  $aOptions['backupdir'] = './bck';
}

// Minimum requirements is a log file
if ( ! isset($aOptions['log']) ) {
  // tee does not work here yet!
  $rc = 140;
  print "ERR: No log file specified!\n";
  exit($rc);
}

$gLogFile = $aOptions['log'];
$gBackupDir = $aOptions['backupdir'];
$ret = &checkArguments($aOptions);

if ( $ret != 0 ) {
  $rc = 127;
  printUsage();
  exit($rc);
}

if ( ! array_key_exists('mode', $aOptions) ) {
  $aOptions['mode'] = 'logical';
}

// If type = schema and no schema specified add empty schema option
if ( $aOptions['type'] == 'schema' ) {
  if ( ! isset($aOptions['schema']) ) {
    $aOptions['schema'] = '';
  }
}

// $gCatalog = '';
// $aAuxiliary = '';
list($ret, $aTarget) = parseConnectString($aOptions['target']);
if ( $ret != 0 ) {
  $rc = 109;
  tee("  ERROR wrong connect string (rc=$rc)!");
  exit($rc);
}

if ( $gMysqlDump == '' ) {
  $rc = 110;
  tee("  ERROR: Cannot find mysqldump utility. Please set path accordingly (rc=$rc).");
  exit($rc);
}

if ( isset($aOptions['retention']) ) {
  $aOptions['retention'] = intval($aOptions['retention']);
}

$gHostname = gethostname();

// ---------------------------------------------------------------------
function printUsage()
// ---------------------------------------------------------------------
{
  global $gLogFile, $gBackupDir;

  $script = 'mysql_bman';

  print "
usage: $script --target=connectstring --type=backup_type
               --policy=backup_policy [--retention=n] [--simulate]
               [--help] [--log=logfile] [--backupdir]
               [--mode={ logical | physical }] --per-schema

Options:
  target      Specifies the database (=target) to backup. A target looks
              as follows: user/password@host:port
  type        See below.
  policy      See below.
  retention   Retention in days. All backups which are older than
              retention days will be deleted when backup type cleanup is
              exectuted.
  simulate    If simulate is called no operation is done but only
              simulated.
  help        Prints this help.
  log         Specifies the logfile where results are reported to (default:
              $gLogFile).
  backupdir   Directory where backups are written to (default: $gBackupDir).
  mode        See below. (default: logical)        
  archive     Archive (copy) files after backup to archivedir.
  archivedir  Directory where backups are archived to (default: none).
  config      Configuration file to read parameter from.
  schema      Schemas to backup or not to backup. +a,+b means backup schema a
              and b. -a,-b means backup all schemas except a and b.
  per-schema  Backup is done per schema.

Backup type:
  full        full logical backup (mysqldump)
  binlog      binlog backup
  config      configuration file backup (my.cnf)
  structure   structure backup
  cleanup     cleanup of backup pieces older than n days
  schema      backup of one or more schemas

Backup policy:
  daily       directory to store daily backups
  weekly                \"        weekly backups
  monthly               \"        monthly backups
  quarterly             \"        quarterly backups
  yearly                \"        yearly backups

Backup mode:
  logical     do a logical backup (mysqldump).
  physical    do a physical backup (mysqlbackup/innobackup/xtrabackup)

Examples:

  $script --target=root/secret@127.0.0.1:3306 --type=full --policy=daily

  $script --target=root/secret@127.0.0.1:3306 --type=full --mode=physical --policy=daily

  Without a password:
  $script --target=bman/@192.168.1.42:3307 --type=binlog --policy=daily

  $script --target=root/secret@127.0.0.1:3306 --type=config --policy=weekly

  $script --target=root/secret@127.0.0.1:3306 --type=structure --policy=weekly

  $script --target=root/secret@127.0.0.1:3306 --type=cleanup --policy=daily --retention=30

  Archive backup to other location:
  $script --target=root/secret@127.0.0.1:3306 --type=structure --policy=weekly --archive --archivedir=/mnt/tape

  Schema backup:
  $script --target=root/secret@127.0.0.1:3306 --type=schema --schema=-mysql --policy=daily --archive --archivedir=/mnt/tape

";
}

// ---------------------------------------------------------------------
function readConfigurationFile($pFile)
// ---------------------------------------------------------------------
{

print "Here we are\n";

  $rc = 0;
  $aConfig = array();

  $fh = fopen($pFile, 'r');
  if ( ! $fh ) {
    $rc = 137;
    tee("  ERROR: Cannot open file $pFile (rc=$rc).");
    return array($rc, $aConfig);
  }

  while ( ($buffer = fgets($fh, 4096)) !== false ) {

    // Skip empty line
    if ( preg_match('/^\s*$/', $buffer) ) {
      continue;
    }

    // Skip comment line
    if ( preg_match('/^\s*#.*$/', $buffer) ) {
      continue;
    }

    // Line must have format: bla = bla
    if ( preg_match('/^([^\\s]+)\s*=\s*(.+)$/', $buffer, &$matches) ) {
      $aConfig[$matches[1]] = $matches[2];
    }
    else {
      $rc = 138;
      tee("  ERROR: Wrong line $buffer in configuration file (rc=$rc).");
      exit($rc);
    }
  }
  if ( ! feof($fh) ) {
    $rc = 139;
    tee("  ERROR: Unexpected fgets() fail (rc=$rc).");
    exit($rc);
  }
  fclose($fh);

  return array($rc, $aConfig);
}

// ---------------------------------------------------------------------
function getMasterPosFromDump($lDump)
// ---------------------------------------------------------------------
{
  $rc = 0;

  if( ( $fh = fopen($lDump, 'r') ) === FALSE ) {
    $rc = 150;
    tee("  ERROR: Failed to open file $lDump for reading (rc=$rc)!");
    exit($rc);
  }

  $binlog = '';
  $pos    = 0;

  $cnt = 1;
  while ( $line = fgets($fh) ) {
    if ( $cnt > 30 ) {
      break;
    }

    // -- CHANGE MASTER TO MASTER_LOG_FILE='bin_log.000057', MASTER_LOG_POS=106;

    if ( preg_match("/.*CHANGE MASTER TO MASTER_LOG_FILE='([\w-]*\.\d*)'.*MASTER_LOG_POS=(\d*)/", $line, &$matches) ) {
      $binlog = $matches[1];
      $pos    = $matches[2];
      break;
    }
    $cnt++;
  }
  fclose($fh);

  return array($binlog, $pos);
}

// ---------------------------------------------------------------------
function checkArguments($aOptions)
// ---------------------------------------------------------------------
{
  global $gLogFile, $gBackupDir;

  $rc = 0;
  $ret = 0;

//   var_dump($aOptions);

  if ( array_key_exists('help', $aOptions) ) {
    printUsage();
    exit($rc);
  }

  if ( ! array_key_exists('type', $aOptions) ) {
    $ret = 71;
    tee("Backup type is missing (ret=$ret).");
    return $ret;
  }

  if ( ! array_key_exists('policy', $aOptions) ) {
    $ret = 65;
    tee("Backup policy is missing (ret=$ret).");
    return $ret;
  }

  if ( ! array_key_exists('target', $aOptions) ) {
    $ret = 68;
    tee("Target is missing (ret=$ret).");
    return $ret;
  }

  foreach ( $aOptions as $arg => $val ) {

    if ( $arg == 'type' ) {

      if ( ( $val != 'full' )
        && ( $val != 'binlog' )
        && ( $val != 'config' )
        && ( $val != 'structure' )
        && ( $val != 'cleanup' )
        && ( $val != 'schema' )
         ) {
        $ret = 66;
        tee("Wrong or missing backup type $val. Please choose one of full, binlog, config, structure, cleanup (ret=$ret).");
        return $ret;
      }
    }

    if ( $arg == 'policy' ) {

      if ( ( $val != 'daily' )
        && ( $val != 'weekly' )
        && ( $val != 'monthly' )
        && ( $val != 'quarterly' )
        && ( $val != 'yearly' ) ) {
        $ret = 67;
        tee("Wrong or missing backup policy $val. Please choose one of daily, weekly, monthly, quarterly, yearly (ret=$ret).");
        return $ret;
      }
    }

    if ( $arg == 'retention' ) {

      if ( ! is_numeric($val) ) {
        $ret = 69;
        tee("Value for retention must be numeric but is $val (ret=$ret).");
        return $ret;
      }
    }

    if ( $arg == 'log' ) {
      if ( strval($val) != '' ) {
        $gLogFile = strval($val);
      }
      else {
        tee("  ERROR: Logfile not specified correctly: $val");
        tee("         Using $gLogFile instead.");
      }
    }

    if ( $arg == 'backupdir' ) {
      if ( strval($val) != '' ) {
        $gBackupDir = strval($val);
      }
      else {
        tee("  ERROR: Backupdir not specified correctly: $val");
        tee("         Using $gBackupDir instead.");
      }
    }

    if ( $arg == 'archivedir' ) {
      if ( strval($val) != '' ) {
        $gArchiveDir = strval($val);
      }
      else {
        $ret = 70;
        tee("  ERROR: Archivedir not specified correctly: $val (ret=$ret).");
        return $ret;
      }
    }

    if ( $arg == 'mode' ) {

      if ( ( $val != 'logical' )
        && ( $val != 'physical' ) ) {
        $ret = 81;
        tee("Wrong or missing backup mode $val. Please choose one of logical, physical (ret=$ret).");
        return $ret;
      }
    }

    if ( $arg == 'per-schema' ) {
      if ( $aOptions['type'] != 'schema' ) {
        $ret = 82;
        tee("Per-schema backup is only allowed with --backup-type=schema (ret=$ret).");
        return $ret;
      }
    }

  }   // foreach
  return $ret;
}

// ---------------------------------------------------------------------
function closeConnection($mysqli)
// ---------------------------------------------------------------------
{
  $mysqli->close();
}

// ---------------------------------------------------------------------
function openConnection($aConnection)
// ---------------------------------------------------------------------
{
  $ret = 0;
  $mysqli = new mysqli($aConnection['host'], $aConnection['user'], $aConnection['password'], 'mysql', $aConnection['port'], null);

  // This is the "official" OO way to do it,
  // BUT $connect_error was broken until PHP 5.2.9 and 5.3.0.

  // if ( $mysqli->connect_error ) {
  //   die('Connect Error (' . $mysqli->connect_errno . ') ' . $mysqli->connect_error);
  // }

  // Use this instead of $connect_error if you need to ensure
  // compatibility with PHP versions prior to 5.2.9 and 5.3.0.

  if ( mysqli_connect_error() ) {
    tee(mysqli_connect_error());
    $ret = 64;
    return array($ret, null);
  }

  $mysqli->query('SET NAMES utf8');
  $mysqli->query('SET SESSION SQL_MODE = "TRADITIONAL,ANSI,ONLY_FULL_GROUP_BY"');

  return array($ret, $mysqli);
}

// ----------------------------------------------------------------------
function getTargetConfiguration($aTarget)
// ----------------------------------------------------------------------
{
  $rc = 0;

  $aConf = array();

  list($rc, $mysqli) = openConnection($aTarget);
  if ( $rc != 0 ) {
    $rc = 113;
    tee("  ERROR: Connection error (rc=$rc).");
    exit($rc);
  }

  // This is NOT 5.0 compatible syntax!
  // $sql = sprintf("SELECT @@datadir AS datadir, @@log_bin AS log_bin");

  $sql = sprintf("SHOW GLOBAL VARIABLES WHERE variable_name IN('log_bin', 'datadir', 'version')");

  if ( ! ($result = $mysqli->query($sql)) ) {
    $rc = 112;
    tee("Invalid query: $sql, " . $mysqli->error);
    return $aConf;
  }

  while ( $record = $result->fetch_assoc() ) {

    if ( $record['Variable_name'] == 'datadir' ) {
      $aConf['datadir'] = rtrim($record['Value'], '/');
    }
    elseif ( $record['Variable_name'] == 'version' ) {
      $pattern='/(\d+)\.(\d+).(\d+)/';
      preg_match($pattern, $record['Value'], &$matches);
      $aConf['mr_version'] = sprintf("%02d%02d%02d", $matches[1], $matches[2], $matches[3]);
    }
  }

  closeConnection($mysqli);

  return $aConf;
}

// ----------------------------------------------------------------------
function parseConnectString($pConnectString)
// ----------------------------------------------------------------------
{
  // Connect string style:
  // user/password@host:port

  // Allowed combinations are:
  // user/password@host:port
  // user/@host:port
  // user/@host:

  $aTarget = array();
  $ret = 0;

  if ( preg_match("/(.+)\/(.*)@(.+):(.*)/", $pConnectString, &$matches) ) {

    $aTarget['user']     = $matches[1];
    $aTarget['password'] = $matches[2];
    $aTarget['host']     = $matches[3];
    $aTarget['port']     = $matches[4] == '' ? 3306 : $matches[4];
  }
  else {
    $ret = 6;
    // This is/was a security issue! Removed: $pConnectString
    tee("Connect string does not match pattern: user/password@host:port (ret=$ret).");
  }

  return array($ret, $aTarget);
}

// ----------------------------------------------------------------------
function checkBackupStructure($pBackupDir)
// ----------------------------------------------------------------------
{
  global $gaBackupPolicy;
  $ret = 0;

  if ( file_exists($pBackupDir) === false ) {
    tee("Backup directory $pBackupDir does NOT exist. Creating it...");
    if ( mkdir($pBackupDir, 0755, true) !==  true ) {
      $rc = 123;
      tee("  ERROR: Cannot create $pBackupDir (rc=$rc).");
      exit($rc);
    }
  }

  foreach ( $gaBackupPolicy as $policy ) {

    if ( ! file_exists("$pBackupDir/$policy") ) {
      tee("Policy directory $pBackupDir/$policy does NOT exist. Creating it...");
      if ( mkdir("$pBackupDir/$policy", 0755, true) === false ) {
        $rc = 124;
        tee("  ERROR cannot create directory $pBackupDir/$policy (rc=$rc).");
        exit($rc);
      }
    }
  }

  return $ret;
}

// ----------------------------------------------------------------------
function checkArchiveStructure($pArchiveDir)
// ----------------------------------------------------------------------
{
  global $gaBackupPolicy;
  $ret = 0;

  if ( file_exists($pArchiveDir) === false ) {
    tee("Archive directory $pArchiveDir does NOT exist. Creating it...");
    if ( mkdir($pArchiveDir, 0755, true) !==  true ) {
      $rc = 122;
      tee("  ERROR: Cannot create $pArchiveDir (rc=$rc).");
      exit($rc);
    }
  }

  foreach ( $gaBackupPolicy as $policy ) {

    if ( ! file_exists("$pArchiveDir/$policy") ) {
      tee("Policy directory $pArchiveDir/$policy does NOT exist. Creating it...");
      if ( mkdir("$pArchiveDir/$policy", 0755, true) === false ) {
        $rc = 146;
        tee("  ERROR cannot create directory $pArchiveDir/$policy (rc=$rc)");
        exit($rc);
      }
    }
  }

  return $ret;
}

// ----------------------------------------------------------------------
function tee($s)
// ----------------------------------------------------------------------
{
  global $gLogFile;

  $rc = 0;

  print "$s\n";

  // todo: not really the optimal way to do it
  if ( ! ($fh = fopen($gLogFile, 'a')) ) {
    $rc = 118;
    print "  ERROR: Can't open file $gLogFile (rc=$rc).\n";
    exit($rc);
  }
  fwrite($fh, "$s\n");
  fclose($fh);
}

// ----------------------------------------------------------------------
function doStructureBackup($aTarget, $aOptions)
// ----------------------------------------------------------------------
{
  global $gMysqlDump, $gBackupDir, $gBackupTimestamp;

  $rc = 0;

  $cmd = "$gMysqlDump --user=". $aTarget['user'] . " --host=". $aTarget['host'] . " --port=" . $aTarget['port'] . " --all-databases --no-data";
  $dst = $gBackupDir . '/' . $aOptions['policy'] . '/bck_' . $aOptions['type'] . '_' . $gBackupTimestamp . '.sql';
  tee("  $cmd");
  tee("  to Destination: $dst");

  if ( ! array_key_exists('simulate', $aOptions) ) {
    $cmd = "$cmd --password='" . $aTarget['password'] . "' >$dst";
    passthru($cmd, &$ret);
    if ( $ret != 0 ) {
      $rc = 125;
      tee("  ERROR: Command failed (rc=$rc).");
      exit($rc);
    }

    tee("  Do MD5 checksum of $dst");
    list($ret, $md5) = getMD5sum($dst, $aOptions);
    if ( $ret == 0 ) {
      tee("  md5sum = $md5");
    }
    else {
      $rc = 151;
      tee("  ERROR: Error building MD5 sum of file $dst (rc=$rc).");
      exit($rc);
    }
  }
  return $rc;
}

// ----------------------------------------------------------------------
function getMD5sum($file, $aOptions)
// ----------------------------------------------------------------------
{
  $rc = 0;
  $ret = 0;
  $md5sum = '';
  $cmd = "md5sum --binary $file";
  tee("  $cmd");

  if ( ! array_key_exists('simulate', $aOptions) ) {
    exec("$cmd 2>&1", &$output, &$ret);
    $s = implode("\n", $output);

    if ( $ret != 0 ) {
      $rc = 130;
      tee("  ERROR: Command failed (rc=$rc).");
      exit($rc);
    }
    if ( preg_match("/(\w{32})\s.*$/", $s, &$matches) ) {
      $md5sum = $matches[1];
    }
    else {
      $rc = 131;
      tee("  ERROR: error building md5sum (rc=$rc).");
      exit($rc);
    }
  }
  return array($ret, $md5sum);
}

// ----------------------------------------------------------------------
function doFullLogicalBackup($aTarget, $aOptions)
// ----------------------------------------------------------------------
{
  global $gMysqlDump, $gBackupDir, $gBackupTimestamp, $gTargetConfiguration;

  $rc = 0;

  // # for MyISAM
  // BACKUP_TIMESTAMP=`date '+%Y-%m-%d_%H-%M-%S'`
  // BACKUP_DIR='/mybackupdir'
  // mysqldump --user=root --all-databases --lock-all-tables --master-data=2 --quick \
  // --flush-logs --triggers --routines --events > $BACKUP_DIR/full_dump_$BACKUP_TIMESTAMP.sql

  // This backup would be useless if we have non transactional tables
  // So check for those and if exists, abort

  list($rc, $mysqli) = openConnection($aTarget);
  if ( $rc != 0 ) {
    $rc = 107;
    tee("  ERROR: Connection error (rc=$rc).");
    exit($rc);
  }

  // Check for non transactional tables
  $ret = checkForNonTransactionaltables($mysqli, array());

  // Check if binary logging is enabled
  $lBinaryLogging = checkIfBinaryLoggingIsEnabled($mysqli);
  if ( $lBinaryLogging == '' ) {
    tee("  Binary logging is disabled");
  }

  closeConnection($mysqli);

  // do mysqldump command
  $cmd = $gMysqlDump . " --user=" . $aTarget['user'] . " --host=". $aTarget['host'] . " --port=" . $aTarget['port'] . " --all-databases $lBinaryLogging --quick --single-transaction --flush-logs --triggers --routines";
  // MySQL >= 5.1
  if ( $gTargetConfiguration['mr_version'] >= '050100' ) {
    $cmd .= ' --events';
  }
  $dst = $gBackupDir. '/' . $aOptions['policy'] . '/bck_' . $aOptions['type'] . '_' . $gBackupTimestamp . '.sql';
  // todo: parameter list has to be cleaned-up!
  $ret = doMysqldump($aOptions, $cmd, $dst, $aTarget, $gMysqlDump);

  // Determine binary log file and binary log pos
  if ( ! array_key_exists('simulate', $aOptions) ) {
    // get binary log file/pos
    list($binlog, $pos) = getMasterPosFromDump($dst);
    if ( $binlog == '' ) {
      tee("  Backup does NOT contain any binary log information.");
    }
    else {
      tee("  Binlog file is " . $binlog . " and position is " . $pos);
    }
  }

  // Do checksum of file
  $ret = doChecksumOfFile($aOptions, $dst);

  // Compress file
  $ret = compressFile($aOptions, $dst);

  // Archive file
  if ( array_key_exists('archive', $aOptions) ) {
    $ret = archiveFileToArchivedir($aOptions, $dst . '.gz');
  }
  return $rc;
}

// ----------------------------------------------------------------------
function checkIfBinaryLoggingIsEnabled($mysqli)
// ----------------------------------------------------------------------
{
  $lBinaryLogging = '--master-data=2';

  $sql = 'SHOW MASTER STATUS';
  if ( ! ($result = $mysqli->query($sql)) ) {
    $rc = 155;
    tee("Invalid query: $sql, " . $mysqli->error . " (rc=$rc).");
    exit($rc);
  }

  $record = $result->fetch_assoc();
  if ( $record == NULL ) {
    $lBinaryLogging = '';
  }

  return $lBinaryLogging;
}

// ----------------------------------------------------------------------
function checkForNonTransactionaltables($mysqli, $aSchemas)
// ----------------------------------------------------------------------
{
  $sql = sprintf("SELECT table_schema, engine, COUNT(*) AS count
  FROM information_schema.tables
 WHERE table_type = 'BASE TABLE'
   AND table_schema NOT IN ('mysql', 'information_schema', 'PERFORMANCE_SCHEMA')
   AND engine NOT IN ('InnoDB', 'PBXT')");

  if ( count($aSchemas) > 0 ) {
    $sql .= "   AND engine IN ('" . implode($aSchemas, "', '") . "')\n";
  }
  $sql .= "\n GROUP BY table_schema, engine";

  if ( ! ($result = $mysqli->query($sql)) ) {
    $rc = 149;
    tee("Invalid query: $sql, " . $mysqli->error . " (rc=$rc).");
    exit($rc);
  }

  while ( $record = $result->fetch_assoc() ) {
    $rc = 128;
    tee("  ERROR: We have non-transactional tables. So this backup would be worthless.");
    tee("         In schema " . $record['table_schema'] . ' ' .  $record['count'] . ' table(s) of type ' . $record['engine']);
    tee("         Aborting (rc=$rc).");
    exit($rc);
  }
  return 0;
}

// ----------------------------------------------------------------------
function doMysqldump($aOptions, $cmd, $file, $aTarget, $mysqldump)
// ----------------------------------------------------------------------
{
  tee("  $cmd");
  tee("  to Destination: $file");
  $cmd = "$cmd --password='" . $aTarget['password'] . "' >$file";
  if ( ! array_key_exists('simulate', $aOptions) ) {
    $stdout = exec($cmd, $output, $ret);
    if ( $ret != 0 ) {
      $rc = 121;
      tee("  ERROR: $mysqldump command failed: $stdout (rc=$rc).");
      exit($rc);
    }
  }
  return 0;
}

// ----------------------------------------------------------------------
function doChecksumOfFile($aOptions, $file)
// ----------------------------------------------------------------------
{
  tee("  Do MD5 checksum of uncomressed file $file");
  if ( ! array_key_exists('simulate', $aOptions) ) {
    list($ret, $md5) = getMD5sum($file, $aOptions);
    if ( $ret == 0 ) {
      tee("  md5sum = $md5");
    }
    else {
      $rc = 135;
      tee("  ERROR: Error building MD5 sum of file $dst (rc=$rc).");
      exit($rc);
    }
  }
  else {
    tee("  md5sum = simulation");
  }
  return 0;
}

// ----------------------------------------------------------------------
function compressFile($aOptions, $file)
// ----------------------------------------------------------------------
{
  // todo: pbzip2 and pigz
  // todo: compression rate -9
  $rate = 6;
  $cmd = "gzip -$rate $file";
  tee("  $cmd");
  if ( ! array_key_exists('simulate', $aOptions) ) {
    passthru($cmd, &$ret);
    if ( $ret != 0 ) {
      $rc = 142;
      tee("  ERROR: gzip command failed (ret=$ret, rc=$rc).");
      exit($rc);
    }
  }
  return 0;
}

// ----------------------------------------------------------------------
function archiveFileToArchivedir($aOptions, $file)
// ----------------------------------------------------------------------
{
  # Archive file to archivedir
  tee("  Archiving $file to " . $aOptions['archivedir'] . '/' . $aOptions['policy'] . '/.');
  if ( ! array_key_exists('simulate', $aOptions) ) {
    #$ret = copy($file, $aOptions['archivedir'] . '/' . $aOptions['policy'] . '/');
    $cmd = "cp $file " . $aOptions['archivedir'] . '/' . $aOptions['policy'] . '/';
    system($cmd, &$ret);
    if ( $ret != 0 ) {
      $rc = 143;
      tee("  ERROR: archiving failed (rc=$rc).");
      exit($rc);
    }
  }
  return 0;
}

// ----------------------------------------------------------------------
function doFullPhysicalBackup($aTarget, $aOptions)
// ----------------------------------------------------------------------
{
  global $gMysqlDump, $gBackupDir, $gBackupTimestamp, $gTargetConfiguration;

  $rc = 0;

  $rc = 141;
  tee("This feature is not yet implemented (rc=$rc).\n");
  exit($rc);

  // get information to create backup configuration file

  list($rc, $mysqli) = openConnection($aTarget);
  if ( $rc != 0 ) {
    $rc = 148;
    tee("  ERROR: Connection error (rc=$rc).");
    exit($rc);
  }

  $sql = sprintf("SELECT CONCAT(LOWER(variable_name), ' = ', IF(variable_value = ''
     , @@global.datadir, IF(variable_value = './', @@global.datadir
     , variable_value))) AS line
  FROM information_schema.global_variables
 WHERE variable_name IN ('datadir', 'innodb_data_home_dir'
     , 'innodb_data_file_path'
     , 'innodb_log_group_home_dir', 'innodb_log_files_in_group'
     , 'innodb_log_file_size')
 ORDER BY variable_name");

  if ( ! ($result = $mysqli->query($sql)) ) {
    $rc = 108;
    tee("Invalid query: $sql, " . $mysqli->error . " (rc=$rc).");
    exit($rc);
  }

  // Create backup.conf
  $lBackupConf = "/tmp/backup_" . $gBackupTimestamp . ".cnf";

  if ( ! ($fh = fopen($lBackupConf, 'w')) ) {
    $rc = 144;
    tee("  ERROR: Can't open file $lBackupConf (rc=$rc).");
    exit($rc);
  }

  fwrite($fh, "#\n");
  fwrite($fh, "# $lBackupConf\n");
  fwrite($fh, "#\n");
  fwrite($fh, "[mysqld]\n");
  while ( $record = $result->fetch_assoc() ) {
    fwrite($fh, $record['line'] . "\n");
  }

  fclose($fh);
  closeConnection($mysqli);

  // do physical backup
  $IBBACKUP = "/data/mysql/meb-3.5.2/bin/ibbackup";

  $cmd = "mysqlbackup --user=" . $aTarget['user'] . " --backup-and-apply-log "
       . "--no-timestamp --only-known-file-types "
       . "--ibbackup=$IBBACKUP $lBackupConf "
       . " /backup/mysql_backup_" . $BACKUP_TIMESTAMP . " 2\>\&1 "
       . "| tee /tmp/backup_${BACKUP_TIMESTAMP}.log";
  tee("  $cmd");

  $cmd = "$cmd --password='" .$aTarget['password']. "' >$dst";

  if ( ! array_key_exists('simulate', $aOptions) ) {
    passthru($cmd, &$ret);
    if ( $ret != 0 ) {
      $rc = 145;
      tee("  ERROR: gzip command failed (rc=$rc).");
      exit($rc);
    }
  }

  $cmd="mv /tmp/backup_${BACKUP_TIMESTAMP}.cnf \
          /tmp/backup_${BACKUP_TIMESTAMP}.log \
          /backup/mysql_backup_${BACKUP_TIMESTAMP}";

  // ibbackup: Last MySQL binlog file position 0 61670849, file name ./bin_log.000073

  $cmd = "$gMysqlDump --user=" . $aTarget['user'] . " --host=". $aTarget['host'] . " --port=" . $aTarget['port'] . " --all-databases --single-transaction --master-data=2 --quick --flush-logs --triggers --routines";
  // MySQL >= 5.1
  if ( $gTargetConfiguration['mr_version'] >= '050100' ) {
    $cmd .= ' --events';
  }

  $dst = $gBackupDir. '/' . $aOptions['policy']. '/bck_' . $aOptions['type'] . '_' . $gBackupTimestamp . '.sql';
  tee("  $cmd");
  tee("  to Destination: $dst");
  $cmd = "$cmd --password='" .$aTarget['password']. "' >$dst";
  if ( ! array_key_exists('simulate', $aOptions) ) {
    passthru($cmd, &$ret);
    if ( $ret != 0 ) {
      $rc = 119;
      tee("  ERROR: $gMysqlDump command failed (rc=$rc).");
      exit($rc);
    }

    // get binary log file/pos
    list($binlog, $pos) = getMasterPosFromDump($dst);
    if ( $binlog == '' ) {
      tee("  Backup does NOT contain any binary log information.");
    }
    else {
      tee("  Binlog file is " . $binlog . " and position is " . $pos);
    }

    tee("  Do MD5 checksum of uncomressed file $dst");
    list($ret, $md5) = getMD5sum($dst, $aOptions);
    if ( $ret == 0 ) {
      tee("  md5sum = $md5");
    }
    else {
      $rc = 133;
      tee("  ERROR: Error building MD5 sum of file $dst (rc=$rc).");
      exit($rc);
    }
  }
  //todo: pbzip2 and pigz 
  // todo: compression rate -9
  $rate = 6;
  $cmd = "gzip -$rate $dst";
  tee("  $cmd");
  if ( ! array_key_exists('simulate', $aOptions) ) {
    passthru($cmd, &$ret);
    if ( $ret != 0 ) {
      $rc = 120;
      tee("  ERROR: gzip command failed (rc=$rc).");
      exit($rc);
    }
  }
  return $rc;
}

// ----------------------------------------------------------------------
function doConfigBackup($aTarget, $aOptions)
// ----------------------------------------------------------------------
{
  $rc = 0;

  global $gMysqlDump, $gBackupDir, $gBackupTimestamp, $gTargetConfiguration;

  // Guess my.cnf
  // todo: this is NOT a good aproach!!!

  $src = '';

  $cnf = '/etc/mysql/my.cnf';
  if ( is_readable($cnf) ) {
    $rc = $cnf;
  }

  $src = '/etc/my.cnf';
  if ( is_readable($cnf) ) {
    $rc = $cnf;
  }

  $src = $gTargetConfiguration['datadir'] . '/my.cnf';
  if ( is_readable($cnf) ) {
    $rc = $cnf;
  }

  if ( $src == '' ) {
    $rc = 100;
    tee("  ERROR: No my.cnf found (rc=$rc).");
    exit($rc);
  }

  $dst = $gBackupDir . '/' . $aOptions['policy'] . "/bck_".$aOptions['type']."_$gBackupTimestamp.cnf";
  tee("  Copy $src to $dst");
  if ( ! array_key_exists('simulate', $aOptions) ) {
    if ( copy($src, $dst) !== true ) {
      $rc = 126;
      tee("  ERROR: Cannot copy $src (rc=$rc).");
      exit($rc);
    }

    tee("  Do MD5 checksum of $dst");
    list($ret, $md5) = getMD5sum($dst, $aOptions);
    if ( $ret == 0 ) {
      tee("  md5sum = $md5");
    }
    else {
      $rc = 152;
      tee("  ERROR: Error building MD5 sum of file $dst (rc=$rc).");
      exit($rc);
    }
  }

  # Archive file to archivedir
  if ( array_key_exists('archive', $aOptions) ) {
    tee("  Archiving $dst to " . $aOptions['archivedir'] . '.');
    $cmd = "cp $dst " . $aOptions['archivedir'] . '/' . $aOptions['policy'] . '/';
    tee("  $cmd");
    if ( ! array_key_exists('simulate', $aOptions) ) {
      passthru($cmd, &$ret);
      if ( $ret != 0 ) {
        $rc = 153;
        tee("  ERROR: archiving failed (rc=$rc).");
        exit($rc);
      }
    }
  }
  return $rc;
}

// ----------------------------------------------------------------------
function doCleanup($aTarget, $aOptions)
// ----------------------------------------------------------------------
{
  global $gMysqlDump, $gBackupDir, $gBackupTimestamp;

  $rc = 0;

  if ( $aOptions['retention'] == 0 ) {
    $rc = 103;
    tee("  Missing backup retention policy. Should be a number of days (rc=$rc).");
    printUsage();
    exit($rc);
  }

  if ( ( $aOptions['retention'] < 1 )
    || ( $aOptions['retention'] < 800 ) ) {
    tee("  Your retention policy is OUT of range (1..800).");
    tee("  Rentention policy is NOT numeric. Please choose a number.");
    $ret = 69;
  }

  $deletionDate = date("Y-m-d_H-i-s", (mktime() - ($aOptions['retention'] * 86400)));

  // We should do a cleanup of archive here as well...
  if ( array_key_exists('archive', $aOptions) ) {
    $lDir = $aOptions['archivedir'];
  }
  else {
    $lDir = $gBackupDir;
  }

  tee("  Retention policy is ".$aOptions['retention']." days. Deleting all files which are older than $deletionDate...");
  // ls /data/backup/*/bck_*_[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]*
  tee("  Deleting the following files:");
  $cnt = 0;
  foreach ( glob("$lDir/" . $aOptions['policy'] . "/*") as $file ) {

    $aStat = stat($file);
    if ( $aStat['mtime'] < (mktime() - ($aOptions['retention'] * 86400)) ) {
      tee("  Remove $file");
      if ( ! array_key_exists('simulate', $aOptions) ) {
        unlink($file);
      }
      $cnt++;
    }
  }   // foreach
  if ( $cnt == 0 ) {
    tee("  No files found to delete.");
  }
  return $rc;
}


// ----------------------------------------------------------------------
function doSchemaBackup($aTarget, $aOptions)
// ----------------------------------------------------------------------
{
  global $gMysqlDump, $gBackupDir, $gBackupTimestamp, $gTargetConfiguration;

  $rc = 0;

  // Get schemata
  list($rc, $mysqli) = openConnection($aTarget);
  if ( $rc != 0 ) {
    $rc = 156;
    tee("  ERROR: Connection error (rc=$rc).");
    exit($rc);
  }

  $sql = sprintf("SELECT schema_name
  FROM information_schema.schemata
 WHERE schema_name NOT IN ('information_schema', 'PERFORMANCE_SCHEMA')");

  if ( ! ($result = $mysqli->query($sql)) ) {
    $rc = 157;
    tee("Invalid query: $sql, " . $mysqli->error . " (rc=$rc).");
    exit($rc);
  }

  $aSchema = array();
  while ( $record = $result->fetch_assoc() ) {
    array_push($aSchema, $record['schema_name']);
  }

  // No schema specified then use all schema
  if ( $aOptions['schema'] == '' ) {
    $aOptions['schema'] = '+' . implode(',+', $aSchema);
  }

  // print_r($aOptions);

  // Parse string
  $mode = '';
  $aSchemaToBackup = array();
  foreach ( explode(',', $aOptions['schema']) as $schema ) {

    // subtractive -test
    if ( substr($schema, 0, 1) == '-' ) {
      if ( ($mode == '-') || ($mode == '') ) {
        $mode = '-';
      }
      else {
        $rc = 158;
        tee("  ERROR: Subtractive and additive schema dump cannot be used together: " . $aOptions['schema'] . " (rc=$rc).");
        exit($rc);
      }
    }
    // additive +test or test
    else {
      if ( ($mode == '+') || ($mode == '') ) {
        $mode = '+';
      }
      else {
        $rc = 159;
        tee("  ERROR: Subtractive and additive schema dump cannot be used together: " . $aOptions['schema'] . " (rc=$rc).");
        exit($rc);
      }
    }

    if ( preg_match('/^[+-]?(.*)$/', $schema, &$match) ) {
      array_push($aSchemaToBackup, $match[1]);
    }
    else {
      $rc = 160;
      tee("  ERROR: Unexpected error in $schema. Please report a bug (rc=$rc).");
      exit($rc);
    }
  }

  // check if schemata are available
  foreach ( $aSchemaToBackup as $schema ) {
    if ( ! in_array($schema, $aSchema) ) {
      $rc = 161;
      tee("  ERROR: Schema $schema does not exist in database (rc=$rc).");
      // Do not exit here yet...
    }
  }
  if ( $rc != 0 ) {
    exit($rc);
  }

  // Create final Schema list
  if ( $mode == '+' ) {
    // SchemaToBackup is already correct
  }
  elseif ( $mode == '-' ) {
    $aSchemaToBackup = array_diff($aSchema, $aSchemaToBackup);
  }
  else {
    $rc = 162;
    tee("  ERROR: Unexpected error in $mode. Please report a bug (rc=$rc).");
    exit($rc);
  }
  tee("  Schema to backup: " . implode($aSchemaToBackup, ', '));

  // Do the schema backup now

  list($rc, $mysqli) = openConnection($aTarget);
  if ( $rc != 0 ) {
    $rc = 104;
    tee("  ERROR: Connection error (rc=$rc).");
    exit($rc);
  }

  // Check for non transactional tables
  $ret = checkForNonTransactionalTables($mysqli, $aSchemaToBackup);

  // Check if binary logging is enabled
  $lBinaryLogging = checkIfBinaryLoggingIsEnabled($mysqli);
  if ( $lBinaryLogging == '' ) {
    tee("  Binary logging is disabled");
  }

  closeConnection($mysqli);

  // Do backup per schema
  if ( isset($aOptions['per-schema']) ) {

    foreach ( $aSchemaToBackup as $schema ) {

      // do mysqldump command
      // master_data=n does not make sense in this case
      // Make sure drop/create schema is not included. this is default behaviour
      // and allows to "rename" schemas
      $cmd = $gMysqlDump . " --user=" . $aTarget['user'] . " --host=". $aTarget['host'] . " --port=" . $aTarget['port'] . " --single-transaction --triggers --routines " . $schema;
      // MySQL >= 5.1
      if ( $gTargetConfiguration['mr_version'] >= '050100' ) {
        $cmd .= ' --events';
      }
      $dst = $gBackupDir. '/' . $aOptions['policy'] . '/bck_' . $aOptions['type'] . '_' . $schema . '_' . $gBackupTimestamp . '.sql';
      // todo: parameter list has to be cleaned-up!
      $ret = doMysqldump($aOptions, $cmd, $dst, $aTarget, $gMysqlDump);

      // Do checksum of file
      $ret = doChecksumOfFile($aOptions, $dst);

      // Compress file
      $ret = compressFile($aOptions, $dst);

      // Archive file
      if ( array_key_exists('archive', $aOptions) ) {
        $ret = archiveFileToArchivedir($aOptions, $dst . '.gz');
      }
      tee("");
    }   // foreach
  }
  // Do backup for all schemas together
  else {

    // do mysqldump command
    $cmd = $gMysqlDump . " --user=" . $aTarget['user'] . " --host=". $aTarget['host'] . " --port=" . $aTarget['port'] . " $lBinaryLogging --single-transaction --flush-logs --triggers --routines --databases " . implode($aSchemaToBackup, ' ');
    // MySQL >= 5.1
    if ( $gTargetConfiguration['mr_version'] >= '050100' ) {
      $cmd .= ' --events';
    }
    $dst = $gBackupDir. '/' . $aOptions['policy'] . '/bck_' . $aOptions['type'] . '_' . $gBackupTimestamp . '.sql';
    // todo: parameter list has to be cleaned-up!
    $ret = doMysqldump($aOptions, $cmd, $dst, $aTarget, $gMysqlDump);

    // Determine binary log file and binary log pos
    if ( ! array_key_exists('simulate', $aOptions) ) {
      // get binary log file/pos
      list($binlog, $pos) = getMasterPosFromDump($dst);
      if ( $binlog == '' ) {
        tee("  Backup does NOT contain any binary log information.");
      }
      else {
        tee("  Binlog file is " . $binlog . " and position is " . $pos);
      }
    }

    // Do checksum of file
    $ret = doChecksumOfFile($aOptions, $dst);

    // Compress file
    $ret = compressFile($aOptions, $dst);

    // Archive file
    if ( array_key_exists('archive', $aOptions) ) {
      $ret = archiveFileToArchivedir($aOptions, $dst . '.gz');
    }
  }

  return $rc;
}

// ----------------------------------------------------------------------
function doBinlogBackup($aTarget, $aOptions)
// ----------------------------------------------------------------------
{
  global $gMysqlDump, $gBackupDir, $gBackupTimestamp, $gTargetConfiguration;

  $rc = 0;

  list($rc, $mysqli) = openConnection($aTarget);
  if ( $rc != 0 ) {
    $rc = 115;
    tee("  ERROR: open datbase failed (rc=$rc).");
    exit($rc);
  }

  // check if binary logging is enabled!!!
  $sql = sprintf("SHOW MASTER STATUS");

  if ( ! ($result = $mysqli->query($sql)) ) {
    $rc = 105;
    tee("  ERROR: Invalid query: $sql, " . $mysqli->error . " (rc=$rc).");
    exit($rc);
  }

  if ( $record = $result->fetch_assoc() ) {
    $lFile = $record['File'];
  }
  else {
    $rc = 116;
    tee("  ERROR: Binary logging seems to be not enabled on your database (rc=$rc).");
    exit($rc);
  }

  // Guess the binlog.index file as $datadir/binlog.index
  // This is the file where all known binary logs should be listed

  if ( preg_match("/^(.*)\.\d{4,6}$/", $lFile, &$matches) ) {
    $lBinlogIndexFile = $gTargetConfiguration['datadir'] . '/' . $matches[1] . '.index';
  }
  else {
    $rc = 101;
    tee("  ERROR: We cannot figure out the binlog.index file. Please report this as an error (rc=$rc).");
    exit($rc);
  }

  // Read the bin_log.index file
  if( ( $fh = fopen($lBinlogIndexFile, 'r') ) === FALSE ) {
    $rc = 114;
    tee("  ERROR: Failed to open file $lBinlogIndexFile for reading!");
    tee("         Is binary logging enabled on your database (rc=$rc)?");
    exit($rc);
  }

  $aBinlogList = array();
  while ( $line = trim(fgets($fh)) ) {

    // we have local files
    if ( substr($line, 0, 2) == './' ) {
      array_push($aBinlogList, $gTargetConfiguration['datadir'] . ltrim($line, '.'));
    }
  }
  fclose($fh);

  // Do a flush logs here
  $sql3 = sprintf("FLUSH /*!50503 BINARY */ LOGS");
  tee("  $sql3");
  if ( ! array_key_exists('simulate', $aOptions) ) {
    $mysqli->query($sql3);
  }

  // Get the binary logs from SHOW BINARY LOGS to copy and purge
  $sql = sprintf("SHOW BINARY LOGS");

  if ( ! ($result = $mysqli->query($sql)) ) {
    $rc = 111;
    tee("Invalid query: $sql, " . $mysqli->error);
    tee("  ERROR: with $sql (rc=$rc)");
    exit($rc);
  }

  // do binary log backup file per file
  while ( $record = $result->fetch_assoc() ) {

    // find binary log in the BinlogList of bin_log.index to get the full path
    foreach ( $aBinlogList as $binlog ) {

      if ( preg_match("/" . $record['Log_name'] ."$/", $binlog, &$matches) ) {

        $src = $binlog;
        $dst = $gBackupDir . '/' . $aOptions['policy'] . "/" . $record['Log_name'];

        tee("  Copy $src to $dst");
        if ( ! array_key_exists('simulate', $aOptions) ) {
          if ( copy($src, $dst) === false ) {
            $rc = 102;
            tee("  ERROR: Copying files... (rc=$rc)");
            exit($rc);
          }
        }

        tee("  Do MD5 checksum of $dst");
        list($ret, $md5) = getMD5sum($dst, $aOptions);
        if ( $ret == 0 ) {
          tee("  md5sum = $md5");
        }
        else {
          $rc = 154;
          tee("  ERROR: Error building MD5 sum of file $dst (rc=$rc).");
          exit($rc);
        }

        # Archive file to archivedir
        if ( array_key_exists('archive', $aOptions) ) {
          tee("  Archiving $dst to " . $aOptions['archivedir'] . '.');
          $cmd = "cp $dst " . $aOptions['archivedir'] . '/' . $aOptions['policy'] . '/';
          tee("  $cmd");
          if ( ! array_key_exists('simulate', $aOptions) ) {
            passthru($cmd, &$ret);
            if ( $ret != 0 ) {
              $rc = 132;
              tee("  ERROR: archiving failed (rc=$rc).");
              exit($rc);
            }
          }
        }

        // Be carefull with this step if you have slaves!
        // This purges binary logs BEFORE xx

        if ( preg_match("/^(.*)\.(\d{4,6})$/", $record['Log_name'], &$matches) ) {
          $lLogPlusOne = sprintf("%s.%06d", $matches[1], $matches[2]+1);
        }
        else {
          $rc = 117;
          tee("  ERROR: Problem parsing the binary logfile name. Please report this as an error.");
          tee("         " . $record['Log_name'] . " (rc=$rc).");
          exit($rc);
        }

        $sql2 = sprintf("PURGE BINARY LOGS TO '%s'", $lLogPlusOne);
        tee("  $sql2");
        if ( ! array_key_exists('simulate', $aOptions) ) {
          $mysqli->query($sql2);
        }
      }
    }
  }

  closeConnection($mysqli);
  return $rc;
}

// ----------------------------------------------------------------------
// MAIN
// ----------------------------------------------------------------------

if ($gMysqlBman != '' ) {
  tee("\nConfiguration from " . $gMysqlBman);
  // Display parameter from config file
  $fh = fopen($gMysqlBman, 'r');
  if ( $fh ) {
    while ( ($buffer = fgets($fh, 4096)) !== false ) {
      if ( ! preg_match('/^$/', $buffer)
        && ! preg_match('/^\s*#.*$/', $buffer)
        ) {
        tee('  ' . trim($buffer));
      }
    }
    if ( ! feof($fh) ) {
      $rc = 136;
      tee("  ERROR: Unexpected fgets() fail (rc=$rc).");
      exit($rc);
    }
    fclose($fh);
  }
}
else {
  tee("\nNo configuration file.");
}

// Display parameter from command line options
tee("Configuration from command line options");
foreach ( $aParameter as $key => $value ) {
  tee(sprintf("  %-10s = %s", $key, $value));
}

// Display resulting configuration
tee("Resulting options");
foreach ( $aOptions as $key => $value ) {
  tee(sprintf("  %-10s = %s", $key, $value));
}

tee("Logging to " . $gLogFile);
tee("Backupdir is " . $gBackupDir);
tee("Hostname is " . $gHostname);
if ( array_key_exists('simulate', $aOptions) ) {
  tee("This is a SIMULATION only!");
}
$gBackupTimestamp = date("Y-m-d_H-i-s");
tee("\nStart " . $aOptions['type'] . ' ' . $aOptions['policy'] . " backup at $gBackupTimestamp");

$gTargetConfiguration = getTargetConfiguration($aTarget);

if ( checkBackupStructure($gBackupDir) != 0 ) {
  $rc = 129;
  tee("  ERROR: Problems creating backup structure (rc=$rc).");
  exit($rc);
}

if ( array_key_exists('archive', $aOptions) ) {
  if ( ! isset($aOptions['archivedir']) || ($aOptions['archivedir'] == '' ) ) {
    $rc = 134;
    tee("  ERROR: Archivedir is not specified (rc=$rc).");
    exit($rc);
  }
}

if ( array_key_exists('archive', $aOptions) && (checkArchiveStructure($aOptions['archivedir']) != 0) ) {
  $rc = 147;
  tee("  ERROR: Problems creating archive structure (rc=$rc).");
  exit($rc);
}

if ( $aOptions['type'] == 'full' ) {
  if ( $aOptions['mode'] == 'logical' ) {
    $rc = doFullLogicalBackup($aTarget, $aOptions);
  }
  elseif ( $aOptions['mode'] == 'physical' ) {
    $rc = doFullPhysicalBackup($aTarget, $aOptions);
  }
}
elseif ( $aOptions['type'] == 'structure' ) {
  $rc = doStructureBackup($aTarget, $aOptions);
}
elseif ( $aOptions['type'] == 'binlog' ) {
  $rc = doBinlogBackup($aTarget, $aOptions);
}
elseif ( $aOptions['type'] == 'config' ) {
  $rc = doConfigBackup($aTarget, $aOptions);
}
elseif ( $aOptions['type'] == 'cleanup' ) {
  $rc = doCleanup($aTarget, $aOptions);
}
elseif ( $aOptions['type'] == 'schema' ) {
  $rc = doSchemaBackup($aTarget, $aOptions);
}
else {
  $rc = 163;
  tee("  ERROR: Unexpected error. This should never happen (" . $aOptions['type'] . ")! Please report a bug! (rc=$rc).");
  exit($rc);
}

$lEndTs = date("Y-m-d_H-i-s");
tee("End " . $aOptions['type'] . " " . $aOptions['policy'] . " backup at $lEndTs (rc=$rc)");
exit($rc);

?>
