#!/usr/bin/env php
<?php


// Check that the memcache library is enabled
if(!class_exists('Memcache')) {
	echo("Error: the memcache library appears to be unavailable.\n");
	echo("\n");
	echo("This is most likely because PHP doesn't load it for the command line\n");
	echo("version. You probably need to enable it somehow.\n");
	echo("\n");
	if(is_dir('/etc/php5/cli/conf.d')) {
		echo("It is possible that running the following command as root will fix it:\n");
		echo(" echo 'extension=memcache.so' >/etc/php5/cli/conf.d/memcache.ini\n");
	}

	exit(1);
}

// This is the base directory of the SimpleSAMLphp installation
$baseDir = dirname(dirname(__FILE__));

// Add library autoloader
require_once($baseDir . '/lib/_autoload.php');

// Initialize the configuration
$configdir = SimpleSAML\Utils\Config::getConfigDir();
SimpleSAML_Configuration::setConfigDir($configdir);

// Things we should warn the user about
$warnServerDown = 0;
$warnBigSlab = 0;

// We use the stats interface to determine which servers exists
$stats = SimpleSAML_Memcache::getRawStats();

$keys = array();
foreach($stats as $group) {
	foreach($group as $server => $state) {

		if($state === FALSE) {
			echo("WARNING: Server " . $server . " is down.\n");
			$warnServerDown++;
			continue;
		}

		$items = $state['curr_items'];
		echo("Server " . $server . " has " . $items . " items.\n");
		$serverKeys = getServerKeys($server);
		$keys = array_merge($keys, $serverKeys);
	}
}

echo("Total number of keys: " . count($keys) . "\n");
$keys = array_unique($keys);
echo("Total number of unique keys: " . count($keys) . "\n");

echo("Starting synchronization.\n");

$skipped = 0;
$sync = 0;
foreach($keys as $key) {
	$res = SimpleSAML_Memcache::get($key);
	if($res === NULL) {
		$skipped += 1;
	} else {
		$sync += 1;
	}
}


echo("Synchronization done.\n");
echo($sync . " keys in sync.\n");
if($skipped > 0) {
	echo($skipped . " keys skipped.\n");
	echo("Keys are skipped because they are either expired, or are of a type unknown\n");
	echo("to SimpleSAMLphp.\n");
}

if($warnServerDown > 0) {
	echo("WARNING: " . $warnServerDown . " server(s) down. Not all servers are synchronized.\n");
}

if($warnBigSlab > 0) {
	echo("WARNING: " . $warnBigSlab . " slab(s) may have contained more keys than we were told about.\n");
}

/**
 * Fetch all keys available in an server.
 *
 * @param $server  The server, as a string with <hostname>:<port>.
 * @return  An array with all the keys available on the server.
 */
function getServerKeys($server) {
	$server = explode(':', $server);
	$host = $server[0];
	$port = (int)$server[1];

	echo("Connecting to: " . $host . ":" . $port . "\n");
	$socket = fsockopen($host, $port);
	echo("Connected. Finding keys.\n");

	if(fwrite($socket, "stats slabs\r\n") === FALSE) {
		echo("Error requesting slab dump from server.\n");
		exit(1);
	}

	// Read list of slabs
	$slabs = array();
	while( ($line = fgets($socket)) !== FALSE) {
		$line = rtrim($line);
		if($line === 'END') {
			break;
		}

		if(preg_match('/^STAT (\d+):/', $line, $matches)) {
			$slab = (int)$matches[1];
			if(!in_array($slab, $slabs, TRUE)) {
				$slabs[] = $slab;
			}
		}
	}

	// Dump keys in slabs
	$keys = array();
	foreach($slabs as $slab) {

		if(fwrite($socket, "stats cachedump " . $slab . " 1000000\r\n") === FALSE) {
			echo("Error requesting cache dump from server.\n");
			exit(1);
		}

		/* We keep track of the result size, to be able to warn the user if it is
		 * so big that keys may have been lost.
		 */
		$resultSize = 0;

		while( ($line = fgets($socket)) !== FALSE) {
			$resultSize += strlen($line);

			$line = rtrim($line);
			if($line === 'END') {
				break;
			}

			if(preg_match('/^ITEM (.*) \[\d+ b; \d+ s\]/', $line, $matches)) {
				$keys[] = $matches[1];
			} else {
				echo("Unknown result from cache dump: " . $line . "\n");
			}
		}
		if($resultSize > 1900000 || count($keys) >= 1000000) {
			echo("WARNING: Slab " . $slab . " on server " . $host . ":" . $port .
				" may have contained more keys than we were told about.\n");
			$GLOBALS['warnBigSlab'] += 1;
		}
	}

	echo("Found " . count($keys) . " key(s).\n");
	fclose($socket);

	return $keys;
}
