text: /*
    +----------------------------------------------------------------------+
    | PHP Version 5                                                        |
    +----------------------------------------------------------------------+
    | Copyright (c) 1997-2007 The PHP Group                                |
    +----------------------------------------------------------------------+
    | This source file is subject to version 3.0 of the PHP license,       |
    | that is bundled with this package in the file LICENSE, and is        |
    | available through the world-wide-web at the following url:           |
    | http://www.php.net/license/3_0.txt.                                  |
    | If you did not receive a copy of the PHP license and are unable to   |
    | obtain it through the world-wide-web, please send a note to          |
    | license@php.net so we can mail you a copy immediately.               |
    +----------------------------------------------------------------------+
    | Authors: Antony Dovgal <tony2001@phpclub.net>                        |
    |          Mikael Johansson <mikael AT synd DOT info>                  |
    +----------------------------------------------------------------------+
  */
  
  /* $Id: memcache.c 329835 2013-03-19 22:39:10Z hradtke $ */
  
  #ifdef HAVE_CONFIG_H
  #include "config.h"
  #endif
  
  #include "php.h"
  #include "php_ini.h"
  #include "ext/standard/info.h"
  #include "ext/standard/php_string.h"
  #include "php_memcache.h"
  
  /* True global resources - no need for thread safety here */
  static int le_memcache_pool, le_memcache_server;
  static zend_class_entry *memcache_pool_ce;
  static zend_class_entry *memcache_ce;
  
  ZEND_EXTERN_MODULE_GLOBALS(memcache)
  
  /* {{{ memcache_functions[]
   */
  zend_function_entry memcache_functions[] = {
  	PHP_FE(memcache_connect,				NULL)
  	PHP_FE(memcache_pconnect,				NULL)
  	PHP_FE(memcache_add_server,				NULL)
  	PHP_FE(memcache_set_server_params,		NULL)
  	PHP_FE(memcache_set_failure_callback,	NULL)
  	PHP_FE(memcache_get_server_status,		NULL)
  	PHP_FE(memcache_get_version,			NULL)
  	PHP_FE(memcache_add,					NULL)
  	PHP_FE(memcache_set,					NULL)
  	PHP_FE(memcache_replace,				NULL)
  	PHP_FE(memcache_cas,					NULL)
  	PHP_FE(memcache_append,					NULL)
  	PHP_FE(memcache_prepend,				NULL)
  	PHP_FE(memcache_get,					NULL)
  	PHP_FE(memcache_delete,					NULL)
  	PHP_FE(memcache_debug,					NULL)
  	PHP_FE(memcache_get_stats,				NULL)
  	PHP_FE(memcache_get_extended_stats,		NULL)
  	PHP_FE(memcache_set_compress_threshold,	NULL)
  	PHP_FE(memcache_increment,				NULL)
  	PHP_FE(memcache_decrement,				NULL)
  	PHP_FE(memcache_close,					NULL)
  	PHP_FE(memcache_flush,					NULL)
  	{NULL, NULL, NULL}
  };
  
  static zend_function_entry php_memcache_pool_class_functions[] = {
  	PHP_NAMED_FE(connect,				zif_memcache_pool_connect,			NULL)
  	PHP_NAMED_FE(addserver,				zif_memcache_pool_addserver,		NULL)
  	PHP_FALIAS(setserverparams,			memcache_set_server_params,			NULL)
  	PHP_FALIAS(setfailurecallback,		memcache_set_failure_callback,		NULL)
  	PHP_FALIAS(getserverstatus,			memcache_get_server_status,			NULL)
  	PHP_NAMED_FE(findserver,			zif_memcache_pool_findserver,		NULL)
  	PHP_FALIAS(getversion,				memcache_get_version,				NULL)
  	PHP_FALIAS(add,						memcache_add,						NULL)
  	PHP_FALIAS(set,						memcache_set,						NULL)
  	PHP_FALIAS(replace,					memcache_replace,					NULL)
  	PHP_FALIAS(cas,						memcache_cas,						NULL)
  	PHP_FALIAS(append,					memcache_append,					NULL)
  	PHP_FALIAS(prepend,					memcache_prepend,					NULL)
  	PHP_FALIAS(get,						memcache_get,						NULL)
  	PHP_FALIAS(delete,					memcache_delete,					NULL)
  	PHP_FALIAS(getstats,				memcache_get_stats,					NULL)
  	PHP_FALIAS(getextendedstats,		memcache_get_extended_stats,		NULL)
  	PHP_FALIAS(setcompressthreshold,	memcache_set_compress_threshold,	NULL)
  	PHP_FALIAS(increment,				memcache_increment,					NULL)
  	PHP_FALIAS(decrement,				memcache_decrement,					NULL)
  	PHP_FALIAS(close,					memcache_close,						NULL)
  	PHP_FALIAS(flush,					memcache_flush,						NULL)
  	{NULL, NULL, NULL}
  };
  
  static zend_function_entry php_memcache_class_functions[] = {
  	PHP_FALIAS(connect,					memcache_connect,					NULL)
  	PHP_FALIAS(pconnect,				memcache_pconnect,					NULL)
  	PHP_FALIAS(addserver,				memcache_add_server,				NULL)
  	{NULL, NULL, NULL}
  };
  
  /* }}} */
  
  /* {{{ memcache_module_entry
   */
  zend_module_entry memcache_module_entry = {
  #if ZEND_MODULE_API_NO >= 20010901
  	STANDARD_MODULE_HEADER,
  #endif
  	"memcache",
  	memcache_functions,
  	PHP_MINIT(memcache),
  	PHP_MSHUTDOWN(memcache),
  	NULL,
  	NULL,
  	PHP_MINFO(memcache),
  #if ZEND_MODULE_API_NO >= 20010901
  	PHP_MEMCACHE_VERSION,
  #endif
  	STANDARD_MODULE_PROPERTIES
  };
  /* }}} */
  
  #ifdef COMPILE_DL_MEMCACHE
  ZEND_GET_MODULE(memcache)
  #endif
  
  static PHP_INI_MH(OnUpdateChunkSize) /* {{{ */
  {
  	long int lval;
  
  	lval = ZEND_STRTOL(ZSTR_VAL(new_value), NULL, 10);
  	if (lval <= 0) {
  		php_error_docref(NULL TSRMLS_CC, E_WARNING, "memcache.chunk_size must be a positive integer ('%s' given)", new_value);
  		return FAILURE;
  	}
  
  	return OnUpdateLong(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage TSRMLS_CC);
  }
  /* }}} */
  
  static PHP_INI_MH(OnUpdateFailoverAttempts) /* {{{ */
  {
  	long int lval;
  
  	lval = ZEND_STRTOL(ZSTR_VAL(new_value), NULL, 10);
  	if (lval <= 0) {
  		php_error_docref(NULL TSRMLS_CC, E_WARNING, "memcache.max_failover_attempts must be a positive integer ('%s' given)", new_value);
  		return FAILURE;
  	}
  
  	return OnUpdateLong(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage TSRMLS_CC);
  }
  /* }}} */
  
  static PHP_INI_MH(OnUpdateProtocol) /* {{{ */
  {
  	if (!strcasecmp(ZSTR_VAL(new_value), "ascii")) {
  		MEMCACHE_G(protocol) = MMC_ASCII_PROTOCOL;
  	}
  	else if (!strcasecmp(ZSTR_VAL(new_value), "binary")) {
  		MEMCACHE_G(protocol) = MMC_BINARY_PROTOCOL;
  	}
  	else {
  		php_error_docref(NULL TSRMLS_CC, E_WARNING, "memcache.protocol must be in set {ascii, binary} ('%s' given)", new_value);
  		return FAILURE;
  	}
  
  	return SUCCESS;
  }
  /* }}} */
  
  static PHP_INI_MH(OnUpdateHashStrategy) /* {{{ */
  {
  	if (!strcasecmp(ZSTR_VAL(new_value), "standard")) {
  		MEMCACHE_G(hash_strategy) = MMC_STANDARD_HASH;
  	}
  	else if (!strcasecmp(ZSTR_VAL(new_value), "consistent")) {
  		MEMCACHE_G(hash_strategy) = MMC_CONSISTENT_HASH;
  	}
  	else {
  		php_error_docref(NULL TSRMLS_CC, E_WARNING, "memcache.hash_strategy must be in set {standard, consistent} ('%s' given)", new_value);
  		return FAILURE;
  	}
  
  	return SUCCESS;
  }
  /* }}} */
  
  static PHP_INI_MH(OnUpdateHashFunction) /* {{{ */
  {
  	if (!strcasecmp(ZSTR_VAL(new_value), "crc32")) {
  		MEMCACHE_G(hash_function) = MMC_HASH_CRC32;
  	}
  	else if (!strcasecmp(ZSTR_VAL(new_value), "fnv")) {
  		MEMCACHE_G(hash_function) = MMC_HASH_FNV1A;
  	}
  	else {
  		php_error_docref(NULL TSRMLS_CC, E_WARNING, "memcache.hash_function must be in set {crc32, fnv} ('%s' given)", new_value);
  		return FAILURE;
  	}
  
  	return SUCCESS;
  }
  /* }}} */
  
  static PHP_INI_MH(OnUpdateRedundancy) /* {{{ */
  {
  	long int lval;
  
  	lval = ZEND_STRTOL(ZSTR_VAL(new_value), NULL, 10);
  	if (lval <= 0) {
  		php_error_docref(NULL TSRMLS_CC, E_WARNING, "memcache.redundancy must be a positive integer ('%s' given)", new_value);
  		return FAILURE;
  	}
  
  	return OnUpdateLong(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage TSRMLS_CC);
  }
  /* }}} */
  
  static PHP_INI_MH(OnUpdateCompressThreshold) /* {{{ */
  {
  	long int lval;
  
  	lval = ZEND_STRTOL(ZSTR_VAL(new_value), NULL, 10);
  	if (lval < 0) {
  		php_error_docref(NULL TSRMLS_CC, E_WARNING, "memcache.compress_threshold must be a positive integer ('%s' given)", new_value);
  		return FAILURE;
  	}
  
  	return OnUpdateLong(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage TSRMLS_CC);
  }
  /* }}} */
  
  static PHP_INI_MH(OnUpdateLockTimeout) /* {{{ */
  {
  	long int lval;
  
  	lval = ZEND_STRTOL(ZSTR_VAL(new_value), NULL, 10);
  	if (lval <= 0) {
  		php_error_docref(NULL TSRMLS_CC, E_WARNING, "memcache.lock_timeout must be a positive integer ('%s' given)", new_value);
  		return FAILURE;
  	}
  
  	return OnUpdateLong(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage TSRMLS_CC);
  }
  /* }}} */
  
  /* {{{ PHP_INI */
  PHP_INI_BEGIN()
  	STD_PHP_INI_ENTRY("memcache.allow_failover",		"1",			PHP_INI_ALL, OnUpdateLong,			allow_failover,	zend_memcache_globals,	memcache_globals)
  	STD_PHP_INI_ENTRY("memcache.max_failover_attempts",	"20",			PHP_INI_ALL, OnUpdateFailoverAttempts,		max_failover_attempts,	zend_memcache_globals,	memcache_globals)
  	STD_PHP_INI_ENTRY("memcache.default_port",			"11211",		PHP_INI_ALL, OnUpdateLong,			default_port,	zend_memcache_globals,	memcache_globals)
  	STD_PHP_INI_ENTRY("memcache.chunk_size",			"32768",		PHP_INI_ALL, OnUpdateChunkSize,		chunk_size,		zend_memcache_globals,	memcache_globals)
  	STD_PHP_INI_ENTRY("memcache.protocol",				"ascii",		PHP_INI_ALL, OnUpdateProtocol,		protocol,		zend_memcache_globals,	memcache_globals)
  	STD_PHP_INI_ENTRY("memcache.hash_strategy",			"consistent",	PHP_INI_ALL, OnUpdateHashStrategy,	hash_strategy,	zend_memcache_globals,	memcache_globals)
  	STD_PHP_INI_ENTRY("memcache.hash_function",			"crc32",		PHP_INI_ALL, OnUpdateHashFunction,	hash_function,	zend_memcache_globals,	memcache_globals)
  	STD_PHP_INI_ENTRY("memcache.redundancy",			"1",			PHP_INI_ALL, OnUpdateRedundancy,	redundancy,			zend_memcache_globals,	memcache_globals)
  	STD_PHP_INI_ENTRY("memcache.session_redundancy",	"2",			PHP_INI_ALL, OnUpdateRedundancy,	session_redundancy,	zend_memcache_globals,	memcache_globals)
  	STD_PHP_INI_ENTRY("memcache.compress_threshold",	"20000",		PHP_INI_ALL, OnUpdateCompressThreshold,	compress_threshold,	zend_memcache_globals,	memcache_globals)
  	STD_PHP_INI_ENTRY("memcache.lock_timeout",			"15",			PHP_INI_ALL, OnUpdateLockTimeout,		lock_timeout,		zend_memcache_globals,	memcache_globals)
  PHP_INI_END()
  /* }}} */
  
  /* {{{ internal function protos */
  static void _mmc_pool_list_dtor(zend_resource * TSRMLS_DC);
  static void _mmc_server_list_dtor(zend_resource * TSRMLS_DC);
  static void php_mmc_set_failure_callback(mmc_pool_t *, zval *, zval * TSRMLS_DC);
  static void php_mmc_failure_callback(mmc_pool_t *, mmc_t *, void * TSRMLS_DC);
  /* }}} */
  
  /* {{{ php_memcache_init_globals()
  */
  static void php_memcache_init_globals(zend_memcache_globals *memcache_globals_p TSRMLS_DC)
  {
  	MEMCACHE_G(hash_strategy)	  = MMC_STANDARD_HASH;
  	MEMCACHE_G(hash_function)	  = MMC_HASH_CRC32;
  }
  /* }}} */
  
  /* {{{ PHP_MINIT_FUNCTION
   */
  PHP_MINIT_FUNCTION(memcache)
  {
  	zend_class_entry ce;
  
  	INIT_CLASS_ENTRY(ce, "MemcachePool", php_memcache_pool_class_functions);
  	memcache_pool_ce = zend_register_internal_class(&ce TSRMLS_CC);
  
  	INIT_CLASS_ENTRY(ce, "Memcache", php_memcache_class_functions);
  	memcache_ce = zend_register_internal_class_ex(&ce, memcache_pool_ce);
  
  	le_memcache_pool = zend_register_list_destructors_ex(_mmc_pool_list_dtor, NULL, "memcache connection", module_number);
  	le_memcache_server = zend_register_list_destructors_ex(NULL, _mmc_server_list_dtor, "persistent memcache connection", module_number);
  
  #ifdef ZTS
  	ts_allocate_id(&memcache_globals_id, sizeof(zend_memcache_globals), (ts_allocate_ctor) php_memcache_init_globals, NULL);
  #else
  	php_memcache_init_globals(&memcache_globals TSRMLS_CC);
  #endif
  
  	REGISTER_LONG_CONSTANT("MEMCACHE_COMPRESSED", MMC_COMPRESSED, CONST_CS | CONST_PERSISTENT);
      REGISTER_LONG_CONSTANT("MEMCACHE_USER1", MMC_RESERVED_APPLICATIONDEFINEDFLAG_12, CONST_CS | CONST_PERSISTENT);
      REGISTER_LONG_CONSTANT("MEMCACHE_USER2", MMC_RESERVED_APPLICATIONDEFINEDFLAG_13, CONST_CS | CONST_PERSISTENT);
      REGISTER_LONG_CONSTANT("MEMCACHE_USER3", MMC_RESERVED_APPLICATIONDEFINEDFLAG_14, CONST_CS | CONST_PERSISTENT);
      REGISTER_LONG_CONSTANT("MEMCACHE_USER4", MMC_RESERVED_APPLICATIONDEFINEDFLAG_15, CONST_CS | CONST_PERSISTENT);
  	REGISTER_INI_ENTRIES();
  
  #if HAVE_MEMCACHE_SESSION
  	REGISTER_LONG_CONSTANT("MEMCACHE_HAVE_SESSION", 1, CONST_CS | CONST_PERSISTENT);
  	php_session_register_module(ps_memcache_ptr);
  #else
  	REGISTER_LONG_CONSTANT("MEMCACHE_HAVE_SESSION", 0, CONST_CS | CONST_PERSISTENT);
  #endif
  
  	return SUCCESS;
  }
  /* }}} */
  
  /* {{{ PHP_MSHUTDOWN_FUNCTION
   */
  PHP_MSHUTDOWN_FUNCTION(memcache)
  {
  	UNREGISTER_INI_ENTRIES();
  	return SUCCESS;
  }
  /* }}} */
  
  /* {{{ PHP_MINFO_FUNCTION
   */
  PHP_MINFO_FUNCTION(memcache)
  {
  	php_info_print_table_start();
  	php_info_print_table_header(2, "memcache support", "enabled");
  	php_info_print_table_row(2, "Version", PHP_MEMCACHE_VERSION);
  	php_info_print_table_row(2, "Revision", "$Revision: 329835 $");
  	php_info_print_table_end();
  
  	DISPLAY_INI_ENTRIES();
  }
  /* }}} */
  
  /* ------------------
     internal functions
     ------------------ */
  
  static void _mmc_pool_list_dtor(zend_resource *rsrc TSRMLS_DC) /* {{{ */
  {
  	mmc_pool_t *pool = (mmc_pool_t *)rsrc->ptr;
  
  	if (pool->failure_callback_param) {
  		zval_ptr_dtor((zval *)&pool->failure_callback_param);
  		pool->failure_callback_param = NULL;
  	}
  
  	mmc_pool_free(pool TSRMLS_CC);
  }
  /* }}} */
  
  static void _mmc_server_list_dtor(zend_resource *rsrc TSRMLS_DC) /* {{{ */
  {
  	mmc_server_free((mmc_t *)rsrc->ptr TSRMLS_CC);
  }
  /* }}} */
  
  static int mmc_get_pool(zval *id, mmc_pool_t **pool TSRMLS_DC) /* {{{ */
  {
  	zval *connection;
  	int resource_type;
  
  	if (Z_TYPE_P(id) != IS_OBJECT || zend_hash_find(Z_OBJPROP_P(id), zend_string_init("connection", sizeof("connection")-1, 0)) == NULL) {
  		php_error_docref(NULL TSRMLS_CC, E_WARNING, "No servers added to memcache connection");
  		return 0;
  	}
  
  	*pool = (mmc_pool_t *) Z_RES_P(connection)->ptr;
  	if (!*pool || resource_type != le_memcache_pool) {
  		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid MemcachePool->connection member variable");
  		return 0;
  	}
  
  	return Z_LVAL_PP(connection);
  }
  /* }}} */
  
  int mmc_stored_handler(mmc_t *mmc, mmc_request_t *request, int response, const char *message, unsigned int message_len, void *param TSRMLS_DC) /*
  	handles SET/ADD/REPLACE response, param is a zval pointer to store result into {{{ */
  {
  	zval *result = (zval *)param;
  
  	if (response == MMC_OK) {
  		if (Z_TYPE_P(result) == IS_NULL) {
  			ZVAL_TRUE(result);
  		}
  
  		return MMC_REQUEST_DONE;
  	}
  
  	/* return FALSE or catch memory errors without failover */
  	if (response == MMC_RESPONSE_EXISTS || response == MMC_RESPONSE_OUT_OF_MEMORY || response == MMC_RESPONSE_TOO_LARGE 
  			|| response == MMC_RESPONSE_CLIENT_ERROR) {
  		ZVAL_FALSE(result);
  
  		if (response != MMC_RESPONSE_EXISTS) {
  			/* trigger notice but no need for failover */
  			php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Server %s (tcp %d, udp %d) failed with: %s (%d)",
  				mmc->host, mmc->tcp.port, mmc->udp.port, message, response);
  		}
  
  		return MMC_REQUEST_DONE;
  	}
  
  	return mmc_request_failure(mmc, request->io, message, message_len, 0 TSRMLS_CC);
  }
  /* }}} */
  
  static void php_mmc_store(INTERNAL_FUNCTION_PARAMETERS, int op) /* {{{ */
  {
  	mmc_pool_t *pool;
  	mmc_request_t *request;
  	zval *keys, *value = 0, *mmc_object = getThis();
  	long flags = 0, exptime = 0, cas = 0;
  
  	if (mmc_object == NULL) {
  		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Oz|zlll", &mmc_object, memcache_pool_ce, &keys, &value, &flags, &exptime, &cas) == FAILURE) {
  			return;
  		}
  	}
  	else {
  		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|zlll", &keys, &value, &flags, &exptime, &cas) == FAILURE) {
  			return;
  		}
  	}
  
  	if (!mmc_get_pool(mmc_object, &pool TSRMLS_CC) || !pool->num_servers) {
  		RETURN_FALSE;
  	}
  
  	RETVAL_NULL();
  
  	if (Z_TYPE_P(keys) == IS_ARRAY) {
  		zend_string *key;
  		char keytmp[MAX_LENGTH_OF_LONG + 1];
  		unsigned int key_len;
  		unsigned long index;
  		int key_type;
  		ulong num_key;
  
  		zval *arrval;
  		HashPosition pos;
  		zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(keys), &pos);
  
  		//while (zend_hash_get_current_data_ex(Z_ARRVAL_P(keys), (void *)&arrval, &pos) == SUCCESS) {
  		ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(keys), num_key, key, arrval) {
  			key_type = zend_hash_get_current_key_ex(Z_ARRVAL_P(keys), &key, &index, &pos);
  			zend_hash_move_forward_ex(Z_ARRVAL_P(keys), &pos);
  
  			switch (key_type) {
  				case HASH_KEY_IS_STRING:
  					key_len--;
  					break;
  
  				case HASH_KEY_IS_LONG:
  					key_len = sprintf(keytmp, "%lu", index);
  					key = zend_string_init(keytmp, key_len, 0);
  					break;
  
  				default:
  					php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid key");
  					continue;
  			}
  
  			/* allocate request */
  			request = mmc_pool_request(pool, MMC_PROTO_TCP,
  				mmc_stored_handler, return_value, mmc_pool_failover_handler, NULL TSRMLS_CC);
  
  			if (mmc_prepare_key_ex(ZSTR_VAL(key), key_len, request->key, &(request->key_len)) != MMC_OK) {
  				php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid key");
  				mmc_pool_release(pool, request);
  				continue;
  			}
  
  			/* assemble command */
  			if (pool->protocol->store(pool, request, op, request->key, request->key_len, flags, exptime, cas, arrval TSRMLS_CC) != MMC_OK) {
  				mmc_pool_release(pool, request);
  				continue;
  			}
  
  			/* schedule request */
  			if (mmc_pool_schedule_key(pool, request->key, request->key_len, request, MEMCACHE_G(redundancy) TSRMLS_CC) != MMC_OK) {
  				continue;
  			}
  
  			/* begin sending requests immediatly */
  			mmc_pool_select(pool TSRMLS_CC);
  		} ZEND_HASH_FOREACH_END();
  	}
  	else if (value) {
  		/* allocate request */
  		request = mmc_pool_request(pool, MMC_PROTO_TCP, mmc_stored_handler, return_value, mmc_pool_failover_handler, NULL TSRMLS_CC);
  
  		if (mmc_prepare_key(keys, request->key, &(request->key_len)) != MMC_OK) {
  			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid key");
  			mmc_pool_release(pool, request);
  			RETURN_FALSE;
  		}
  
  		/* assemble command */
  		if (pool->protocol->store(pool, request, op, request->key, request->key_len, flags, exptime, cas, value TSRMLS_CC) != MMC_OK) {
  			mmc_pool_release(pool, request);
  			RETURN_FALSE;
  		}
  
  		/* schedule request */
  		if (mmc_pool_schedule_key(pool, request->key, request->key_len, request, MEMCACHE_G(redundancy) TSRMLS_CC) != MMC_OK) {
  			RETURN_FALSE;
  		}
  	}
  	else {
  		WRONG_PARAM_COUNT;
  	}
  
  	/* execute all requests */
  	mmc_pool_run(pool TSRMLS_CC);
  
  	if (Z_TYPE_P(return_value) == IS_NULL) {
  		RETVAL_FALSE;
  	}
  }
  /* }}} */
  
  int mmc_numeric_response_handler(mmc_t *mmc, mmc_request_t *request, int response, const char *message, unsigned int message_len, void *param TSRMLS_DC) /*
  	handles a mutate response line, param is a zval pointer to store result into {{{ */
  {
  	zval *result = (zval *)param;
  
  	if (response == MMC_OK) {
  		if (Z_TYPE_P(result) == IS_ARRAY) {
  			add_assoc_bool_ex(result, request->key, request->key_len + 1, 1);
  		}
  		else if (Z_TYPE_P(result) == IS_NULL) {
  			/* switch only from null to true, not from false to true */
  			ZVAL_TRUE(result);
  		}
  
  		return MMC_REQUEST_DONE;
  	}
  
  	if (response == MMC_RESPONSE_NOT_FOUND || response == MMC_RESPONSE_CLIENT_ERROR) {
  		if (Z_TYPE_P(result) == IS_ARRAY) {
  			add_assoc_bool_ex(result, request->key, request->key_len + 1, 0);
  		}
  		else {
  			ZVAL_FALSE(result);
  		}
  
  		if (response != MMC_RESPONSE_NOT_FOUND) {
  			php_error_docref(NULL TSRMLS_CC, 
  					E_NOTICE, "Server %s (tcp %d, udp %d) failed with: %s (%d)",
  					mmc->host, mmc->tcp.port, 
  					mmc->udp.port, message, response);
  		}
  
  		return MMC_REQUEST_DONE;
  	}
  
  	return mmc_request_failure(mmc, request->io, message, message_len, 0 TSRMLS_CC);
  }
  /* }}} */
  
  static void php_mmc_numeric(INTERNAL_FUNCTION_PARAMETERS, int deleted, int invert) /*
  	sends one or several commands which have a single optional numeric parameter (incr, decr, delete) {{{ */
  {
  	mmc_pool_t *pool;
  	zval *mmc_object = getThis();
  
  	zval *keys;
  	long value = 1, defval = 0, exptime = 0;
  	mmc_request_t *request;
  	void *value_handler_param[3];
  	int defval_used = 0;
  
  	if (mmc_object == NULL) {
  		if (deleted) {
  			if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Oz|l", &mmc_object, memcache_pool_ce, &keys, &value) == FAILURE) {
  				return;
  			}
  		}
  		else {
  			if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Oz|lll", &mmc_object, memcache_pool_ce, &keys, &value, &defval, &exptime) == FAILURE) {
  				return;
  			}
  
  			defval_used = ZEND_NUM_ARGS() >= 4;
  		}
  	}
  	else {
  		if (deleted) {
  			if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|l", &keys, &value) == FAILURE) {
  				return;
  			}
  		}
  		else {
  			if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|lll", &keys, &value, &defval, &exptime) == FAILURE) {
  				return;
  			}
  
  			defval_used = ZEND_NUM_ARGS() >= 3;
  		}
  	}
  
  	if (!mmc_get_pool(mmc_object, &pool TSRMLS_CC) || !pool->num_servers) {
  		RETURN_FALSE;
  	}
  
  	value_handler_param[0] = return_value;
  	value_handler_param[1] = NULL;
  	value_handler_param[2] = NULL;
  
  	if (Z_TYPE_P(keys) == IS_ARRAY) {
  		zval **key;
  		HashPosition pos;
  
  		if (deleted) {
  			/* changed to true/false by mmc_numeric_response_handler */
  			RETVAL_NULL();
  		}
  		else {
  			/* populated with responses by mmc_numeric_response_handler and mmc_value_handler_multi */
  			array_init(return_value);
  		}
  
  		zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(keys), &pos);
  		while (zend_hash_get_current_data_ex(Z_ARRVAL_P(keys), (void **)&key, &pos) == SUCCESS) {
  			zend_hash_move_forward_ex(Z_ARRVAL_P(keys), &pos);
  
  			/* allocate request */
  			request = mmc_pool_request(
  				pool, MMC_PROTO_TCP, mmc_numeric_response_handler, return_value,
  				mmc_pool_failover_handler, NULL TSRMLS_CC);
  
  			request->value_handler = mmc_value_handler_multi;
  			request->value_handler_param = value_handler_param;
  
  			if (mmc_prepare_key(*key, request->key, &(request->key_len)) != MMC_OK) {
  				mmc_pool_release(pool, request);
  				php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid key");
  				continue;
  			}
  
  			if (deleted) {
  				pool->protocol->delete(request, request->key, request->key_len, exptime);
  			}
  			else {
  				pool->protocol->mutate(request, *key, request->key, request->key_len, invert ? -value : value, defval, defval_used, exptime);
  			}
  
  			/* schedule request */
  			if (mmc_pool_schedule_key(pool, request->key, request->key_len, request, MEMCACHE_G(redundancy) TSRMLS_CC) != MMC_OK) {
  				continue;
  			}
  
  			/* begin sending requests immediatly */
  			mmc_pool_select(pool TSRMLS_CC);
  		}
  	}
  	else {
  		/* changed to true/false by mmc_numeric_response_handler or set to a value
  		 * by mmc_value_handler_single if incr/decr returns one */
  		RETVAL_NULL();
  
  		/* allocate request */
  		request = mmc_pool_request(pool, MMC_PROTO_TCP,
  			mmc_numeric_response_handler, return_value, mmc_pool_failover_handler, NULL TSRMLS_CC);
  
  		request->value_handler = mmc_value_handler_single;
  		request->value_handler_param = value_handler_param;
  
  		if (mmc_prepare_key(keys, request->key, &(request->key_len)) != MMC_OK) {
  			mmc_pool_release(pool, request);
  			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid key");
  			RETURN_FALSE;
  		}
  
  		if (deleted) {
  			pool->protocol->delete(request, request->key, request->key_len, exptime);
  		}
  		else {
  			pool->protocol->mutate(request, keys, request->key, request->key_len, invert ? -value : value, defval, defval_used, exptime);
  		}
  
  		/* schedule request */
  		if (mmc_pool_schedule_key(pool, request->key, request->key_len, request, MEMCACHE_G(redundancy) TSRMLS_CC) != MMC_OK) {
  			RETURN_FALSE;
  		}
  	}
  
  	/* execute all requests */
  	mmc_pool_run(pool TSRMLS_CC);
  }
  /* }}} */
  
  mmc_t *mmc_find_persistent(const char *host, int host_len, unsigned short port, unsigned short udp_port, double timeout, int retry_interval TSRMLS_DC) /* {{{ */
  {
  	mmc_t *mmc;
  	zend_resource *le;
  	char *key;
  	int key_len;
  
  	key_len = spprintf(&key, 0, "memcache:server:%s:%u:%u", host, port, udp_port);
  
  	if (zend_hash_find(&EG(persistent_list), Z_STR_P(key)) == NULL) {
  		zend_resource new_le;
  
  		mmc = mmc_server_new(host, host_len, port, udp_port, 1, timeout, retry_interval TSRMLS_CC);
  		new_le.type = le_memcache_server;
  		new_le.ptr  = mmc;
  
  		/* register new persistent connection */
  		if (zend_hash_update(&EG(persistent_list), key, key_len+1, (void *)&new_le, sizeof(zend_resource), NULL) == FAILURE) {
  			mmc_server_free(mmc TSRMLS_CC);
  			mmc = NULL;
  		} else {
  #if (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION == 3)
  			zend_list_insert(mmc, le_memcache_server);
  #else
  			zend_list_insert(mmc, le_memcache_server TSRMLS_CC);
  #endif
  		}
  	}
  	else if (le->type != le_memcache_server || le->ptr == NULL) {
  		zend_resource new_le;
  		zend_hash_del(&EG(persistent_list), key, key_len+1);
  
  		mmc = mmc_server_new(host, host_len, port, udp_port, 1, timeout, retry_interval TSRMLS_CC);
  		new_le.type = le_memcache_server;
  		new_le.ptr  = mmc;
  
  		/* register new persistent connection */
  		if (zend_hash_update(&EG(persistent_list), key, key_len+1, (void *)&new_le, sizeof(zend_resource), NULL) == FAILURE) {
  			mmc_server_free(mmc TSRMLS_CC);
  			mmc = NULL;
  		}
  		else {
  #if (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION == 3)
  			zend_list_insert(mmc, le_memcache_server);
  #else
  			zend_list_insert(mmc, le_memcache_server TSRMLS_CC);
  #endif
  		}
  	}
  	else {
  		mmc = (mmc_t *)le->ptr;
  		mmc->timeout = double_to_timeval(timeout);
  		mmc->tcp.retry_interval = retry_interval;
  
  		/* attempt to reconnect this node before failover in case connection has gone away */
  		if (mmc->tcp.status == MMC_STATUS_CONNECTED) {
  			mmc->tcp.status = MMC_STATUS_UNKNOWN;
  		}
  		if (mmc->udp.status == MMC_STATUS_CONNECTED) {
  			mmc->udp.status = MMC_STATUS_UNKNOWN;
  		}
  	}
  
  	efree(key);
  	return mmc;
  }
  /* }}} */
  
  static mmc_t *php_mmc_pool_addserver(
  	zval *mmc_object, const char *host, int host_len, long tcp_port, long udp_port, long weight,
  	zend_bool persistent, double timeout, long retry_interval, zend_bool status, mmc_pool_t **pool_result TSRMLS_DC) /* {{{ */
  {
  	zval **connection;
  	mmc_pool_t *pool;
  	mmc_t *mmc;
  	int list_id, resource_type;
  
  	if (weight < 1) {
  		php_error_docref(NULL TSRMLS_CC, E_WARNING, "weight must be a positive integer");
  		return NULL;
  	}
  
  	/* initialize pool if need be */
  	if (zend_hash_find(Z_OBJPROP_P(mmc_object), zend_string_init("connection", sizeof("connection")-1, 0)) == NULL) {
  		pool = mmc_pool_new(TSRMLS_C);
  		pool->failure_callback = &php_mmc_failure_callback;
  #if (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION == 3)
  		list_id = zend_list_insert(pool, le_memcache_pool);
  #else
  		list_id = zend_list_insert(pool, le_memcache_pool TSRMLS_CC);
  #endif
  		add_property_resource(mmc_object, "connection", list_id);
  	}
  	else {
  		pool = (mmc_pool_t *)zend_list_find(Z_LVAL_PP(connection), &resource_type);
  		if (!pool || resource_type != le_memcache_pool) {
  			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unknown connection identifier");
  			return NULL;
  		}
  	}
  
  	/* binary protocol isn't support over UDP yet */
  	if (udp_port && pool->protocol == &mmc_binary_protocol) {
  		php_error_docref(NULL TSRMLS_CC, E_NOTICE, "binary protocol isn't support over UDP, defaulting to TCP");
  		udp_port = 0;
  	}
  
  	/* lazy initialization of server struct */
  	if (persistent && status) {
  		mmc = mmc_find_persistent(host, host_len, tcp_port, udp_port, timeout, retry_interval TSRMLS_CC);
  	}
  	else {
  		mmc = mmc_server_new(host, host_len, tcp_port, udp_port, 0, timeout, retry_interval TSRMLS_CC);
  	}
  
  	/* add server in failed mode */
  	if (!status) {
  		mmc->tcp.status = MMC_STATUS_FAILED;
  		mmc->udp.status = MMC_STATUS_FAILED;
  	}
  
  	mmc_pool_add(pool, mmc, weight);
  
  	if (pool_result != NULL) {
  		*pool_result = pool;
  	}
  
  	return mmc;
  }
  /* }}} */
  
  static void php_mmc_connect(INTERNAL_FUNCTION_PARAMETERS, zend_bool persistent) /* {{{ */
  {
  	zval *mmc_object = getThis();
  	mmc_pool_t *pool;
  	mmc_t *mmc;
  
  	char *host;
  	int host_len;
  	long tcp_port = MEMCACHE_G(default_port);
  	double timeout = MMC_DEFAULT_TIMEOUT;
  
  	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|ld", &host, &host_len, &tcp_port, &timeout) == FAILURE) {
  		return;
  	}
  
  	/* initialize pool and object if need be */
  	if (!mmc_object) {
  		int list_id;
  		mmc_pool_t *pool = mmc_pool_new(TSRMLS_C);
  		pool->failure_callback = &php_mmc_failure_callback;
  #if (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION == 3)
  		list_id = zend_list_insert(pool, le_memcache_pool);
  #else
  		list_id = zend_list_insert(pool, le_memcache_pool TSRMLS_CC);
  #endif
  		mmc_object = return_value;
  		object_init_ex(mmc_object, memcache_ce);
  		add_property_resource(mmc_object, "connection", list_id);
  	}
  	else {
  		RETVAL_TRUE;
  	}
  
  	mmc = php_mmc_pool_addserver(mmc_object, host, host_len, tcp_port, 0, 1, persistent, timeout, MMC_DEFAULT_RETRY, 1, NULL TSRMLS_CC);
  	if (mmc == NULL) {
  		RETURN_FALSE;
  	}
  
  	/* force a reconnect attempt if stream EOF */
  	if (mmc->tcp.stream != NULL && php_stream_eof(mmc->tcp.stream)) {
  		mmc_server_disconnect(mmc, &(mmc->tcp) TSRMLS_CC);
  	}
  
  	if (!mmc_get_pool(mmc_object, &pool TSRMLS_CC)) {
  		RETURN_FALSE;
  	}
  
  	/* force a tcp connect (if not persistently connected) */
  	if (mmc_pool_open(pool, mmc, &(mmc->tcp), 0 TSRMLS_CC) != MMC_OK) {
  		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can't connect to %s:%d, %s (%d)", host, mmc->tcp.port, mmc->error ? mmc->error : "Unknown error", mmc->errnum);
  		RETURN_FALSE;
  	}
  }
  /* }}} */
  
  /*
   * STAT 6:chunk_size 64
   */
  static int mmc_stats_parse_stat(char *start, char *end, zval *result TSRMLS_DC)  /* {{{ */
  {
  	char *space, *colon, *key;
  	long index = 0;
  
  	if (Z_TYPE_P(result) != IS_ARRAY) {
  		array_init(result);
  	}
  
  	/* find space delimiting key and value */
  	if ((space = php_memnstr(start, " ", 1, end)) == NULL) {
  		return 0;
  	}
  
  	/* find colon delimiting subkeys */
  	if ((colon = php_memnstr(start, ":", 1, space - 1)) != NULL) {
  		zval *element, **elem;
  		key = estrndup(start, colon - start);
  
  		/* find existing or create subkey array in result */
  		if ((is_numeric_string(key, colon - start, &index, NULL, 0) &&
  			zend_hash_index_find(Z_ARRVAL_P(result), index, (void **)&elem) != FAILURE) ||
  			zend_hash_find(Z_ARRVAL_P(result), key, colon - start + 1, (void **)&elem) != FAILURE) {
  			element = *elem;
  		}
  		else {
  			MAKE_STD_ZVAL(element);
  			array_init(element);
  			add_assoc_zval_ex(result, key, colon - start + 1, element);
  		}
  
  		efree(key);
  		return mmc_stats_parse_stat(colon + 1, end, element TSRMLS_CC);
  	}
  
  	/* no more subkeys, add value under last subkey */
  	key = estrndup(start, space - start);
  	add_assoc_stringl_ex(result, key, space - start + 1, space + 1, end - space, 1);
  	efree(key);
  
  	return 1;
  }
  /* }}} */
  
  /*
   * ITEM test_key [3 b; 1157099416 s]
   */
  static int mmc_stats_parse_item(char *start, char *end, zval *result TSRMLS_DC)  /* {{{ */
  {
  	char *space, *value, *value_end, *key;
  	zval *element;
  
  	if (Z_TYPE_P(result) != IS_ARRAY) {
  		array_init(result);
  	}
  
  	/* find space delimiting key and value */
  	if ((space = php_memnstr(start, " ", 1, end)) == NULL) {
  		return 0;
  	}
  
  	MAKE_STD_ZVAL(element);
  	array_init(element);
  
  	/* parse each contained value */
  	for (value = php_memnstr(space, "[", 1, end); value != NULL && value <= end; value = php_memnstr(value + 1, ";", 1, end)) {
  		do {
  			value++;
  		} while (*value == ' ' && value <= end);
  
  		if (value <= end && (value_end = php_memnstr(value, " ", 1, end)) != NULL && value_end <= end) {
  			add_next_index_stringl(element, value, value_end - value, 1);
  		}
  	}
  
  	/* add parsed values under key */
  	key = estrndup(start, space - start);
  	add_assoc_zval_ex(result, key, space - start + 1, element);
  	efree(key);
  
  	return 1;
  }
  /* }}} */
  
  static int mmc_stats_parse_generic(char *start, char *end, zval *result TSRMLS_DC)  /* {{{ */
  {
  	char *space, *key;
  
  	if (Z_TYPE_P(result) != IS_ARRAY) {
  		array_init(result);
  	}
  
  	if (start < end) {
  		if ((space = php_memnstr(start, " ", 1, end)) != NULL) {
  			key = estrndup(start, space - start);
  			add_assoc_stringl_ex(result, key, space - start + 1, space + 1, end - space, 1);
  			efree(key);
  		}
  		else {
  			add_next_index_stringl(result, start, end - start, 1);
  		}
  	}
  	else {
  		return 0;
  	}
  
  	return 1;
  }
  /* }}} */
  
  static void php_mmc_failure_callback(mmc_pool_t *pool, mmc_t *mmc, void *param TSRMLS_DC) /* {{{ */
  {
  	zval **callback;
  
  	/* check for userspace callback */
  	if (param != NULL && zend_hash_find(Z_OBJPROP_P((zval *)param), zend_string_init("_failureCallback", sizeof("_failureCallback")-1, 0)) != NULL && Z_TYPE_PP(callback) != IS_NULL) {
  		if (zend_is_callable(*callback, 0, NULL)) {
  			zval *retval = NULL;
  			zval *host, *tcp_port, *udp_port, *error, *errnum;
  			zval **params[5];
  
  			params[0] = &host;
  			params[1] = &tcp_port;
  			params[2] = &udp_port;
  			params[3] = &error;
  			params[4] = &errnum;
  
  			MAKE_STD_ZVAL(host);
  			MAKE_STD_ZVAL(tcp_port); MAKE_STD_ZVAL(udp_port);
  			MAKE_STD_ZVAL(error); MAKE_STD_ZVAL(errnum);
  
  			ZVAL_STRING(host, mmc->host, 1);
  			ZVAL_LONG(tcp_port, mmc->tcp.port); ZVAL_LONG(udp_port, mmc->udp.port);
  
  			if (mmc->error != NULL) {
  				ZVAL_STRING(error, mmc->error, 1);
  			}
  			else {
  				ZVAL_NULL(error);
  			}
  			ZVAL_LONG(errnum, mmc->errnum);
  
  			call_user_function_ex(EG(function_table), NULL, *callback, &retval, 5, params, 0, NULL TSRMLS_CC);
  
  			zval_ptr_dtor(&host);
  			zval_ptr_dtor(&tcp_port); zval_ptr_dtor(&udp_port);
  			zval_ptr_dtor(&error); zval_ptr_dtor(&errnum);
  
  			if (retval != NULL) {
  				zval_ptr_dtor(&retval);
  			}
  		}
  		else {
  			php_mmc_set_failure_callback(pool, (zval *)param, NULL TSRMLS_CC);
  			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid failure callback");
  		}
  	}
  	else {
  		php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Server %s (tcp %d, udp %d) failed with: %s (%d)",
  			mmc->host, mmc->tcp.port, mmc->udp.port, mmc->error, mmc->errnum);
  	}
  }
  /* }}} */
  
  static void php_mmc_set_failure_callback(mmc_pool_t *pool, zval *mmc_object, zval *callback TSRMLS_DC)  /* {{{ */
  {
  	// Decrease refcount of old mmc_object
  	if (pool->failure_callback_param) {
  		zval_ptr_dtor((zval **)&pool->failure_callback_param);
  	}
  
  	if (callback != NULL) {
  		zval *callback_tmp;
  		ALLOC_ZVAL(callback_tmp);
  
  		*callback_tmp = *callback;
  		zval_copy_ctor(callback_tmp);
  		INIT_PZVAL(callback_tmp);
  
  		add_property_zval(mmc_object, "_failureCallback", callback_tmp);
  		pool->failure_callback_param = mmc_object;
  		zval_add_ref(&mmc_object);
  
  		INIT_PZVAL(callback_tmp);
  	}
  	else {
  		add_property_null(mmc_object, "_failureCallback");
  		pool->failure_callback_param = NULL;
  	}
  }
  /* }}} */
  
  /* ----------------
     module functions
     ---------------- */
  
  /* {{{ proto bool MemcachePool::connect(string host [, int tcp_port [, int udp_port [, bool persistent [, int weight [, double timeout [, int retry_interval] ] ] ] ] ])
     Connects to server and returns a Memcache object */
  PHP_NAMED_FUNCTION(zif_memcache_pool_connect)
  {
  	zval *mmc_object = getThis();
  	mmc_pool_t *pool;
  	mmc_t *mmc;
  
  	char *host;
  	int host_len;
  	long tcp_port = MEMCACHE_G(default_port), udp_port = 0, weight = 1, retry_interval = MMC_DEFAULT_RETRY;
  	double timeout = MMC_DEFAULT_TIMEOUT;
  	zend_bool persistent = 1;
  
  	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|llbldl",
  		&host, &host_len, &tcp_port, &udp_port, &persistent, &weight, &timeout, &retry_interval) == FAILURE) {
  		return;
  	}
  
  	mmc = php_mmc_pool_addserver(mmc_object, host, host_len, tcp_port, udp_port, weight, persistent, timeout, retry_interval, 1, NULL TSRMLS_CC);
  	if (mmc == NULL) {
  		RETURN_FALSE;
  	}
  
  	/* force a reconnect attempt if stream EOF */
  	if (mmc->tcp.stream != NULL && php_stream_eof(mmc->tcp.stream)) {
  		mmc_server_disconnect(mmc, &(mmc->tcp) TSRMLS_CC);
  	}
  
  	if (!mmc_get_pool(mmc_object, &pool TSRMLS_CC)) {
  		RETURN_FALSE;
  	}
  
  	/* force a tcp connect (if not persistently connected) */
  	if (mmc_pool_open(pool, mmc, &(mmc->tcp), 0 TSRMLS_CC) != MMC_OK) {
  		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can't connect to %s:%d, %s (%d)", host, mmc->tcp.port, mmc->error ? mmc->error : "Unknown error", mmc->errnum);
  		RETURN_FALSE;
  	}
  
  	RETURN_TRUE;
  }
  /* }}} */
  
  /* {{{ proto object memcache_connect(string host [, int port [, double timeout ] ])
     Connects to server and returns a Memcache object */
  PHP_FUNCTION(memcache_connect)
  {
  	php_mmc_connect(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
  }
  /* }}} */
  
  /* {{{ proto object memcache_pconnect(string host [, int port [, double timeout ] ])
     Connects to server and returns a Memcache object */
  PHP_FUNCTION(memcache_pconnect)
  {
  	php_mmc_connect(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
  }
  /* }}} */
  
  /* {{{ proto bool MemcachePool::addServer(string host [, int tcp_port [, int udp_port [, bool persistent [, int weight [, double timeout [, int retry_interval [, bool status] ] ] ] ])
     Adds a server to the pool */
  PHP_NAMED_FUNCTION(zif_memcache_pool_addserver)
  {
  	zval *mmc_object = getThis();
  	mmc_t *mmc;
  
  	char *host;
  	int host_len;
  	long tcp_port = MEMCACHE_G(default_port), udp_port = 0, weight = 1, retry_interval = MMC_DEFAULT_RETRY;
  	double timeout = MMC_DEFAULT_TIMEOUT;
  	zend_bool persistent = 1, status = 1;
  
  	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|llbldlb",
  		&host, &host_len, &tcp_port, &udp_port, &persistent, &weight, &timeout, &retry_interval, &status) == FAILURE) {
  		return;
  	}
  
  	mmc = php_mmc_pool_addserver(mmc_object, host, host_len, tcp_port, udp_port, weight, persistent, timeout, retry_interval, status, NULL TSRMLS_CC);
  	if (mmc == NULL) {
  		RETURN_FALSE;
  	}
  
  	RETURN_TRUE;
  }
  /* }}} */
  
  /* {{{ proto string MemcachePool::findServer(string key)
  	Returns the server corresponding to a key
  */
  PHP_NAMED_FUNCTION(zif_memcache_pool_findserver)
  {
  	zval *mmc_object = getThis();
  	mmc_pool_t *pool;
  	mmc_t *mmc;
  	
  	zval *zkey;
  	char key[MMC_MAX_KEY_LEN + 1];
  	unsigned int key_len;
  	char *hostname;
  
  	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zkey) == FAILURE) {
  		return;
  	}
  	
  	if (!mmc_get_pool(mmc_object, &pool TSRMLS_CC) || !pool->num_servers) {
  		RETURN_FALSE;
  	}
  
  	if (mmc_prepare_key(zkey, key, &key_len) != MMC_OK) {
  		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid key");
  		RETURN_FALSE;
  	}
  	
  	mmc = mmc_pool_find(pool, key, key_len TSRMLS_CC);
  	spprintf(&hostname, 0, "%s:%d", mmc->host, mmc->tcp.port);	
  	RETURN_STRING(hostname, 0);
  }
  /* }}} */
  
  /* {{{ proto bool memcache_add_server(string host [, int port [, bool persistent [, int weight [, double timeout [, int retry_interval [, bool status [, callback failure_callback ] ] ] ] ] ] ])
     Adds a connection to the pool. The order in which this function is called is significant */
  PHP_FUNCTION(memcache_add_server)
  {
  	zval *mmc_object = getThis(), *failure_callback = NULL;
  	mmc_pool_t *pool;
  	mmc_t *mmc;
  
  	char *host;
  	int host_len;
  	long tcp_port = MEMCACHE_G(default_port), weight = 1, retry_interval = MMC_DEFAULT_RETRY;
  	double timeout = MMC_DEFAULT_TIMEOUT;
  	zend_bool persistent = 1, status = 1;
  
  	if (mmc_object) {
  		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|lbldlbz",
  			&host, &host_len, &tcp_port, &persistent, &weight, &timeout, &retry_interval, &status, &failure_callback) == FAILURE) {
  			return;
  		}
  	}
  	else {
  		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Os|lbldlbz", &mmc_object, memcache_ce,
  			&host, &host_len, &tcp_port, &persistent, &weight, &timeout, &retry_interval, &status, &failure_callback) == FAILURE) {
  			return;
  		}
  	}
  
  	if (failure_callback != NULL && Z_TYPE_P(failure_callback) != IS_NULL) {
  		if (!zend_is_callable(failure_callback, 0, NULL)) {
  			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid failure callback");
  			RETURN_FALSE;
  		}
  	}
  
  	mmc = php_mmc_pool_addserver(mmc_object, host, host_len, tcp_port, 0, weight, persistent, timeout, retry_interval, status, &pool TSRMLS_CC);
  	if (mmc == NULL) {
  		RETURN_FALSE;
  	}
  
  	if (failure_callback != NULL && Z_TYPE_P(failure_callback) != IS_NULL) {
  		php_mmc_set_failure_callback(pool, mmc_object, failure_callback TSRMLS_CC);
  	}
  
  	RETURN_TRUE;
  }
  /* }}} */
  
  /* {{{ proto bool memcache_set_server_params( string host [, int port [, double timeout [, int retry_interval [, bool status [, callback failure_callback ] ] ] ] ])
     Changes server parameters at runtime */
  PHP_FUNCTION(memcache_set_server_params)
  {
  	zval *mmc_object = getThis(), *failure_callback = NULL;
  	mmc_pool_t *pool;
  	mmc_t *mmc = NULL;
  	long tcp_port = MEMCACHE_G(default_port), retry_interval = MMC_DEFAULT_RETRY;
  	double timeout = MMC_DEFAULT_TIMEOUT;
  	zend_bool status = 1;
  	int host_len, i;
  	char *host;
  
  	if (mmc_object) {
  		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|ldlbz",
  			&host, &host_len, &tcp_port, &timeout, &retry_interval, &status, &failure_callback) == FAILURE) {
  			return;
  		}
  	}
  	else {
  		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Os|ldlbz", &mmc_object, memcache_pool_ce,
  			&host, &host_len, &tcp_port, &timeout, &retry_interval, &status, &failure_callback) == FAILURE) {
  			return;
  		}
  	}
  
  	if (!mmc_get_pool(mmc_object, &pool TSRMLS_CC)) {
  		RETURN_FALSE;
  	}
  
  	for (i=0; i<pool->num_servers; i++) {
  		if (!strcmp(pool->servers[i]->host, host) && pool->servers[i]->tcp.port == tcp_port) {
  			mmc = pool->servers[i];
  			break;
  		}
  	}
  
  	if (!mmc) {
  		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Server not found in pool");
  		RETURN_FALSE;
  	}
  
  	if (failure_callback != NULL && Z_TYPE_P(failure_callback) != IS_NULL) {
  		if (!zend_is_callable(failure_callback, 0, NULL)) {
  			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid failure callback");
  			RETURN_FALSE;
  		}
  	}
  
  	mmc->timeout = double_to_timeval(timeout);
  	mmc->tcp.retry_interval = retry_interval;
  
  	/* store the smallest timeout for any server */
  	if (timeval_to_double(mmc->timeout) < timeval_to_double(pool->timeout)) {
  		pool->timeout = mmc->timeout;
  	}
  
  	if (!status) {
  		mmc->tcp.status = MMC_STATUS_FAILED;
  		mmc->udp.status = MMC_STATUS_FAILED;
  	}
  	else {
  		if (mmc->tcp.status == MMC_STATUS_FAILED) {
  			mmc->tcp.status = MMC_STATUS_DISCONNECTED;
  		}
  		if (mmc->udp.status == MMC_STATUS_FAILED) {
  			mmc->udp.status = MMC_STATUS_DISCONNECTED;
  		}
  	}
  
  	if (failure_callback != NULL) {
  		if (Z_TYPE_P(failure_callback) != IS_NULL) {
  			php_mmc_set_failure_callback(pool, mmc_object, failure_callback TSRMLS_CC);
  		}
  		else {
  			php_mmc_set_failure_callback(pool, mmc_object, NULL TSRMLS_CC);
  		}
  	}
  
  	RETURN_TRUE;
  }
  /* }}} */
  
  /* {{{ proto bool memcache_set_failure_callback( callback failure_callback )
     Changes the failover callback */
  PHP_FUNCTION(memcache_set_failure_callback)
  {
  	zval *mmc_object = getThis(), *failure_callback;
  	mmc_pool_t *pool;
  
  	if (mmc_object) {
  		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z",
  			&failure_callback) == FAILURE) {
  			return;
  		}
  	}
  	else {
  		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Oz", &mmc_object, memcache_pool_ce,
  			&failure_callback) == FAILURE) {
  			return;
  		}
  	}
  
  	if (!mmc_get_pool(mmc_object, &pool TSRMLS_CC)) {
  		RETURN_FALSE;
  	}
  
  	if (Z_TYPE_P(failure_callback) != IS_NULL) {
  		if (!zend_is_callable(failure_callback, 0, NULL)) {
  			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid failure callback");
  			RETURN_FALSE;
  		}
  	}
  
  	if (Z_TYPE_P(failure_callback) != IS_NULL) {
  		php_mmc_set_failure_callback(pool, mmc_object, failure_callback TSRMLS_CC);
  	}
  	else {
  		php_mmc_set_failure_callback(pool, mmc_object, NULL TSRMLS_CC);
  	}
  
  	RETURN_TRUE;
  }
  /* }}} */
  
  /* {{{ proto int memcache_get_server_status( string host [, int port ])
     Returns server status (0 if server is failed, otherwise non-zero) */
  PHP_FUNCTION(memcache_get_server_status)
  {
  	zval *mmc_object = getThis();
  	mmc_pool_t *pool;
  	mmc_t *mmc = NULL;
  	long tcp_port = MEMCACHE_G(default_port);
  	int host_len, i;
  	char *host;
  
  	if (mmc_object) {
  		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &host, &host_len, &tcp_port) == FAILURE) {
  			return;
  		}
  	}
  	else {
  		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Os|l", &mmc_object, memcache_pool_ce, &host, &host_len, &tcp_port) == FAILURE) {
  			return;
  		}
  	}
  
  	if (!mmc_get_pool(mmc_object, &pool TSRMLS_CC)) {
  		RETURN_FALSE;
  	}
  
  	for (i=0; i<pool->num_servers; i++) {
  		if (!strcmp(pool->servers[i]->host, host) && pool->servers[i]->tcp.port == tcp_port) {
  			mmc = pool->servers[i];
  			break;
  		}
  	}
  
  	if (!mmc) {
  		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Server not found in pool");
  		RETURN_FALSE;
  	}
  
  	RETURN_LONG(mmc->tcp.status > MMC_STATUS_FAILED ? 1 : 0);
  }
  /* }}} */
  
  static int mmc_version_handler(mmc_t *mmc, mmc_request_t *request, int response, const char *message, unsigned int message_len, void *param TSRMLS_DC) /*
  	parses the VERSION response line, param is a zval pointer to store version into {{{ */
  {
  	if (response != MMC_RESPONSE_ERROR) {
  		char *version = emalloc(message_len + 1);
  
  		if (sscanf(message, "VERSION %s", version) == 1) {
  			ZVAL_STRING((zval *)param, version, 0);
  		}
  		else {
  			efree(version);
  			ZVAL_STRINGL((zval *)param, (char *)message, message_len, 1);
  		}
  
  		return MMC_REQUEST_DONE;
  	}
  
  	return mmc_request_failure(mmc, request->io, message, message_len, 0 TSRMLS_CC);
  }
  /* }}} */
  
  /* {{{ proto string memcache_get_version( object memcache )
     Returns server's version */
  PHP_FUNCTION(memcache_get_version)
  {
  	mmc_pool_t *pool;
  	zval *mmc_object = getThis();
  	int i;
  	mmc_request_t *request;
  
  	if (mmc_object == NULL) {
  		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &mmc_object, memcache_pool_ce) == FAILURE) {
  			return;
  		}
  	}
  
  	if (!mmc_get_pool(mmc_object, &pool TSRMLS_CC) || !pool->num_servers) {
  		RETURN_FALSE;
  	}
  
  	RETVAL_FALSE;
  	for (i=0; i<pool->num_servers; i++) {
  		/* run command and check for valid return value */
  		request = mmc_pool_request(pool, MMC_PROTO_TCP, mmc_version_handler, return_value, NULL, NULL TSRMLS_CC);
  		pool->protocol->version(request);
  
  		if (mmc_pool_schedule(pool, pool->servers[i], request TSRMLS_CC) == MMC_OK) {
  			mmc_pool_run(pool TSRMLS_CC);
  
  			if (Z_TYPE_P(return_value) == IS_STRING) {
  				break;
  			}
  		}
  	}
  }
  /* }}} */
  
  /* {{{ proto bool memcache_add(object memcache, mixed key [, mixed var [, int flag [, int exptime ] ] ])
     Adds new item. Item with such key should not exist. */
  PHP_FUNCTION(memcache_add)
  {
  	php_mmc_store(INTERNAL_FUNCTION_PARAM_PASSTHRU, MMC_OP_ADD);
  }
  /* }}} */
  
  /* {{{ proto bool memcache_set(object memcache, mixed key [, mixed var [, int flag [, int exptime ] ] ])
     Sets the value of an item. Item may exist or not */
  PHP_FUNCTION(memcache_set)
  {
  	php_mmc_store(INTERNAL_FUNCTION_PARAM_PASSTHRU, MMC_OP_SET);
  }
  /* }}} */
  
  /* {{{ proto bool memcache_replace(object memcache, mixed key [, mixed var [, int flag [, int exptime ] ] )
     Replaces existing item. Returns false if item doesn't exist */
  PHP_FUNCTION(memcache_replace)
  {
  	php_mmc_store(INTERNAL_FUNCTION_PARAM_PASSTHRU, MMC_OP_REPLACE);
  }
  /* }}} */
  
  /* {{{ proto bool memcache_cas(object memcache, mixed key [, mixed var [, int flag [, int exptime [, long cas ] ] ] ])
     Sets the value of an item if the CAS value is the same (Compare-And-Swap)  */
  PHP_FUNCTION(memcache_cas)
  {
  	php_mmc_store(INTERNAL_FUNCTION_PARAM_PASSTHRU, MMC_OP_CAS);
  }
  /* }}} */
  
  /* {{{ proto bool memcache_prepend(object memcache, mixed key [, mixed var [, int flag [, int exptime ] ] ])
     Appends a value to the stored value, value must exist */
  PHP_FUNCTION(memcache_append)
  {
  	php_mmc_store(INTERNAL_FUNCTION_PARAM_PASSTHRU, MMC_OP_APPEND);
  }
  /* }}} */
  
  /* {{{ proto bool memcache_prepend(object memcache, mixed key [, mixed var [, int flag [, int exptime ] ] ])
     Prepends a value to the stored value, value must exist */
  PHP_FUNCTION(memcache_prepend)
  {
  	php_mmc_store(INTERNAL_FUNCTION_PARAM_PASSTHRU, MMC_OP_PREPEND);
  }
  /* }}} */
  
  int mmc_value_handler_multi(
  	const char *key, unsigned int key_len, zval *value,
  	unsigned int flags, unsigned long cas, void *param TSRMLS_DC) /*
  	receives a multiple values, param is a zval** array to store value and flags in {{{ */
  {
  	zval **result = (zval **)param;
  
  	/* add value to result */
  	if (Z_TYPE_P(result[0]) != IS_ARRAY) {
  		array_init(result[0]);
  	}
  	add_assoc_zval_ex(result[0], (char *)key, key_len + 1, value);
  
  	/* add flags to result */
  	if (result[1] != NULL) {
  		if (Z_TYPE_P(result[1]) != IS_ARRAY) {
  			array_init(result[1]);
  		}
  		add_assoc_long_ex(result[1], (char *)key, key_len + 1, flags);
  	}
  
  	/* add CAS value to result */
  	if (result[2] != NULL) {
  		if (Z_TYPE_P(result[2]) != IS_ARRAY) {
  			array_init(result[2]);
  		}
  		add_assoc_long_ex(result[2], (char *)key, key_len + 1, cas);
  	}
  
  	return MMC_REQUEST_DONE;
  }
  /* }}} */
  
  int mmc_value_handler_single(
  	const char *key, unsigned int key_len, zval *value,
  	unsigned int flags, unsigned long cas, void *param TSRMLS_DC) /*
  	receives a single value, param is a zval pointer to store value to {{{ */
  {
  	zval **result = (zval **)param;
  	ZVAL_ZVAL(result[0], value, 1, 1);
  
  	if (result[1] != NULL) {
  		ZVAL_LONG(result[1], flags);
  	}
  
  	if (result[2] != NULL) {
  		ZVAL_LONG(result[2], cas);
  	}
  
  	return MMC_REQUEST_DONE;
  }
  /* }}} */
  
  static int mmc_value_failover_handler(mmc_pool_t *pool, mmc_t *mmc, mmc_request_t *request, void *param TSRMLS_DC) /*
  	uses keys and return value to reschedule requests to other servers, param is a zval ** pointer {{{ */
  {
  	zval **key, *keys = ((zval **)param)[0], **value_handler_param = (zval **)((void **)param)[1];
  	HashPosition pos;
  
  	if (!MEMCACHE_G(allow_failover) || request->failed_servers.len >= MEMCACHE_G(max_failover_attempts)) {
  		mmc_pool_release(pool, request);
  		return MMC_REQUEST_FAILURE;
  	}
  
  	zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(keys), &pos);
  
  	while (zend_hash_get_current_data_ex(Z_ARRVAL_P(keys), (void **)&key, &pos) == SUCCESS) {
  		zend_hash_move_forward_ex(Z_ARRVAL_P(keys), &pos);
  
  		/* re-schedule key if it does not exist in return value array */
  		if (Z_TYPE_P(value_handler_param[0]) != IS_ARRAY ||
  			!zend_hash_exists(Z_ARRVAL_P(value_handler_param[0]), Z_STRVAL_PP(key), Z_STRLEN_PP(key) + 1))
  		{
  			mmc_pool_schedule_get(pool, MMC_PROTO_UDP,
  				value_handler_param[2] != NULL ? MMC_OP_GETS : MMC_OP_GET, *key,
  				request->value_handler, request->value_handler_param,
  				request->failover_handler, request->failover_handler_param, request TSRMLS_CC);
  		}
  	}
  
  	mmc_pool_release(pool, request);
  	return MMC_OK;
  }
  /* }}}*/
  
  /* {{{ proto mixed memcache_get( object memcache, mixed key [, mixed &flags [, mixed &cas ] ] )
     Returns value of existing item or false */
  PHP_FUNCTION(memcache_get)
  {
  	mmc_pool_t *pool;
  	zval *keys, *flags = NULL, *cas = NULL, *mmc_object = getThis();
  	void *value_handler_param[3], *failover_handler_param[2];
  
  	if (mmc_object == NULL) {
  		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Oz|zz", &mmc_object, memcache_pool_ce, &keys, &flags, &cas) == FAILURE) {
  			return;
  		}
  	}
  	else {
  		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|zz", &keys, &flags, &cas) == FAILURE) {
  			return;
  		}
  	}
  
  	if (!mmc_get_pool(mmc_object, &pool TSRMLS_CC) || !pool->num_servers) {
  		RETURN_FALSE;
  	}
  
  	value_handler_param[0] = return_value;
  	value_handler_param[1] = flags;
  	value_handler_param[2] = cas;
  
  	if (Z_TYPE_P(keys) == IS_ARRAY) {
  		zval *key, *val;
  		ulong num_key;
  		HashPosition pos;
  
  		/* return empty array if no keys found */
  		array_init(return_value);
  
  		failover_handler_param[0] = keys;
  		failover_handler_param[1] = value_handler_param;
  
  		zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(keys), &pos);
  		//while (zend_hash_get_current_data_ex(Z_ARRVAL_P(keys), (void **)&key, &pos) == SUCCESS) {
  		ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(keys), num_key, key, val) {
  			zend_hash_move_forward_ex(Z_ARRVAL_P(keys), &pos);
  
  			/* schedule request */
  			mmc_pool_schedule_get(pool, MMC_PROTO_UDP,
  				cas != NULL ? MMC_OP_GETS : MMC_OP_GET, *key,
  				mmc_value_handler_multi, value_handler_param,
  				mmc_value_failover_handler, failover_handler_param, NULL TSRMLS_CC);
  		} ZEND_HASH_FOREACH_END();
  	}
  	else {
  		mmc_request_t *request;
  
  		/* return false if key isn't found */
  		ZVAL_FALSE(return_value);
  
  		/* allocate request */
  		request = mmc_pool_request_get(
  			pool, MMC_PROTO_UDP,
  			mmc_value_handler_single, value_handler_param,
  			mmc_pool_failover_handler, NULL TSRMLS_CC);
  
  		if (mmc_prepare_key(keys, request->key, &(request->key_len)) != MMC_OK) {
  			mmc_pool_release(pool, request);
  			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid key");
  			return;
  		}
  
  		pool->protocol->get(request, cas != NULL ? MMC_OP_GETS : MMC_OP_GET, keys, request->key, request->key_len);
  
  		/* schedule request */
  		if (mmc_pool_schedule_key(pool, request->key, request->key_len, request, 1 TSRMLS_CC) != MMC_OK) {
  			return;
  		}
  	}
  
  	/* execute all requests */
  	mmc_pool_run(pool TSRMLS_CC);
  }
  /* }}} */
  
  static int mmc_stats_handler(mmc_t *mmc, mmc_request_t *request, int response, const char *message, unsigned int message_len, void *param TSRMLS_DC) /*
  	parses the stats response line, param is a zval pointer to store stats into {{{ */
  {
  	if (response != MMC_RESPONSE_ERROR)
  	{
  		char *line = (char *)message;
  
  		if(!message_len) {
  			return MMC_REQUEST_DONE;
  		}
  
  		if (mmc_str_left(line, "RESET", message_len, sizeof("RESET")-1)) {
  			ZVAL_TRUE((zval *)param);
  			return MMC_REQUEST_DONE;
  		}
  		else if (mmc_str_left(line, "STAT ", message_len, sizeof("STAT ")-1)) {
  			if (mmc_stats_parse_stat(line + sizeof("STAT ")-1, line + message_len - 1, (zval *)param TSRMLS_CC)) {
  				return MMC_REQUEST_AGAIN;
  			}
  		}
  		else if (mmc_str_left(line, "ITEM ", message_len, sizeof("ITEM ")-1)) {
  			if (mmc_stats_parse_item(line + sizeof("ITEM ")-1, line + message_len - 1, (zval *)param TSRMLS_CC)) {
  				return MMC_REQUEST_AGAIN;
  			}
  		}
  		else if (mmc_str_left(line, "END", message_len, sizeof("END")-1)) {
  			return MMC_REQUEST_DONE;
  		}
  		else if (mmc_stats_parse_generic(line, line + message_len, (zval *)param TSRMLS_CC)) {
  			return MMC_REQUEST_AGAIN;
  		}
  
  		zval_dtor((zval *)param);
  		ZVAL_FALSE((zval *)param);
  		return MMC_REQUEST_FAILURE;
  	}
  
  	return mmc_request_failure(mmc, request->io, message, message_len, 0 TSRMLS_CC);
  }
  /* }}} */
  
  static int mmc_stats_checktype(const char *type) { /* {{{ */
  	return type == NULL ||
  		!strcmp(type, "reset") ||
  		!strcmp(type, "malloc") ||
  		!strcmp(type, "slabs") ||
  		!strcmp(type, "cachedump") ||
  		!strcmp(type, "items") ||
  		!strcmp(type, "sizes");
  }
  /* }}} */
  
  /* {{{ proto array memcache_get_stats( object memcache [, string type [, int slabid [, int limit ] ] ])
     Returns server's statistics */
  PHP_FUNCTION(memcache_get_stats)
  {
  	mmc_pool_t *pool;
  	zval *mmc_object = getThis();
  
  	char *type = NULL;
  	int i, type_len = 0;
  	long slabid = 0, limit = MMC_DEFAULT_CACHEDUMP_LIMIT;
  	mmc_request_t *request;
  
  	if (mmc_object == NULL) {
  		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O|sll", &mmc_object, memcache_pool_ce, &type, &type_len, &slabid, &limit) == FAILURE) {
  			return;
  		}
  	}
  	else {
  		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|sll", &type, &type_len, &slabid, &limit) == FAILURE) {
  			return;
  		}
  	}
  
  	if (!mmc_get_pool(mmc_object, &pool TSRMLS_CC) || !pool->num_servers) {
  		RETURN_FALSE;
  	}
  
  	if (!mmc_stats_checktype(type)) {
  		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid stats type");
  		RETURN_FALSE;
  	}
  
  	ZVAL_FALSE(return_value);
  
  	for (i=0; i<pool->num_servers; i++) {
  		request = mmc_pool_request(pool, MMC_PROTO_TCP, mmc_stats_handler, return_value, NULL, NULL TSRMLS_CC);
  		pool->protocol->stats(request, type, slabid, limit);
  
  		/* run command and check for valid return value */
  		if (mmc_pool_schedule(pool, pool->servers[i], request TSRMLS_CC) == MMC_OK) {
  			mmc_pool_run(pool TSRMLS_CC);
  
  			if ((Z_TYPE_P(return_value) != IS_TRUE && Z_TYPE_P(return_value) != IS_FALSE) || Z_BVAL_P(return_value)) {
  				break;
  			}
  		}
  	}
  
  	/* execute all requests */
  	mmc_pool_run(pool TSRMLS_CC);
  }
  /* }}} */
  
  /* {{{ proto array memcache_get_extended_stats( object memcache [, string type [, int slabid [, int limit ] ] ])
     Returns statistics for each server in the pool */
  PHP_FUNCTION(memcache_get_extended_stats)
  {
  	mmc_pool_t *pool;
  	zval *mmc_object = getThis(), *stats;
  
  	char *host, *type = NULL;
  	int i, host_len, type_len = 0;
  	long slabid = 0, limit = MMC_DEFAULT_CACHEDUMP_LIMIT;
  	mmc_request_t *request;
  
  	if (mmc_object == NULL) {
  		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O|sll", &mmc_object, memcache_pool_ce, &type, &type_len, &slabid, &limit) == FAILURE) {
  			return;
  		}
  	}
  	else {
  		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|sll", &type, &type_len, &slabid, &limit) == FAILURE) {
  			return;
  		}
  	}
  
  	if (!mmc_get_pool(mmc_object, &pool TSRMLS_CC) || !pool->num_servers) {
  		RETURN_FALSE;
  	}
  
  	if (!mmc_stats_checktype(type)) {
  		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid stats type");
  		RETURN_FALSE;
  	}
  
  	array_init(return_value);
  
  	for (i=0; i<pool->num_servers; i++) {
  		MAKE_STD_ZVAL(stats);
  		ZVAL_FALSE(stats);
  
  		host_len = spprintf(&host, 0, "%s:%u", pool->servers[i]->host, pool->servers[i]->tcp.port);
  		add_assoc_zval_ex(return_value, host, host_len + 1, stats);
  		efree(host);
  
  		request = mmc_pool_request(pool, MMC_PROTO_TCP, mmc_stats_handler, stats, NULL, NULL TSRMLS_CC);
  		pool->protocol->stats(request, type, slabid, limit);
  
  		if (mmc_pool_schedule(pool, pool->servers[i], request TSRMLS_CC) == MMC_OK) {
  			mmc_pool_run(pool TSRMLS_CC);			
  		}
  	}
  
  	/* execute all requests */
  	mmc_pool_run(pool TSRMLS_CC);
  }
  /* }}} */
  
  /* {{{ proto array memcache_set_compress_threshold( object memcache, int threshold [, float min_savings ] )
     Set automatic compress threshold */
  PHP_FUNCTION(memcache_set_compress_threshold)
  {
  	mmc_pool_t *pool;
  	zval *mmc_object = getThis();
  	long threshold;
  	double min_savings = MMC_DEFAULT_SAVINGS;
  
  	if (mmc_object == NULL) {
  		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Ol|d", &mmc_object, memcache_pool_ce, &threshold, &min_savings) == FAILURE) {
  			return;
  		}
  	}
  	else {
  		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l|d", &threshold, &min_savings) == FAILURE) {
  			return;
  		}
  	}
  
  	if (!mmc_get_pool(mmc_object, &pool TSRMLS_CC)) {
  		RETURN_FALSE;
  	}
  
  	if (threshold < 0) {
  		php_error_docref(NULL TSRMLS_CC, E_WARNING, "threshold must be a positive integer");
  		RETURN_FALSE;
  	}
  	pool->compress_threshold = threshold;
  
  	if (min_savings != MMC_DEFAULT_SAVINGS) {
  		if (min_savings < 0 || min_savings > 1) {
  			php_error_docref(NULL TSRMLS_CC, E_WARNING, "min_savings must be a float in the 0..1 range");
  			RETURN_FALSE;
  		}
  		pool->min_compress_savings = min_savings;
  	}
  	else {
  		pool->min_compress_savings = MMC_DEFAULT_SAVINGS;
  	}
  
  	RETURN_TRUE;
  }
  /* }}} */
  
  /* {{{ proto bool memcache_delete(object memcache, mixed key [, int exptime ])
     Deletes existing item */
  PHP_FUNCTION(memcache_delete)
  {
  	php_mmc_numeric(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1, 0);
  }
  /* }}} */
  
  /* {{{ proto mixed memcache_increment(object memcache, mixed key [, int value [, int defval [, int exptime ] ] ])
     Increments existing variable */
  PHP_FUNCTION(memcache_increment)
  {
  	php_mmc_numeric(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, 0);
  }
  /* }}} */
  
  /* {{{ proto mixed memcache_decrement(object memcache, mixed key [, int value [, int defval [, int exptime ] ] ])
     Decrements existing variable */
  PHP_FUNCTION(memcache_decrement)
  {
  	php_mmc_numeric(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, 1);
  }
  /* }}} */
  
  /* {{{ proto bool memcache_close( object memcache )
     Closes connection to memcached */
  PHP_FUNCTION(memcache_close)
  {
  	mmc_pool_t *pool;
  	zval *mmc_object = getThis();
  
  	if (mmc_object == NULL) {
  		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &mmc_object, memcache_pool_ce) == FAILURE) {
  			return;
  		}
  	}
  
  	if (!mmc_get_pool(mmc_object, &pool TSRMLS_CC)) {
  		RETURN_FALSE;
  	}
  
  	mmc_pool_close(pool TSRMLS_CC);
  	RETURN_TRUE;
  }
  /* }}} */
  
  static int mmc_flush_handler(mmc_t *mmc, mmc_request_t *request, int response, const char *message, unsigned int message_len, void *param TSRMLS_DC) /*
  	parses the OK response line, param is an int pointer to increment on success {{{ */
  {
  	if (response == MMC_OK) {
  		(*((int *)param))++;
  		return MMC_REQUEST_DONE;
  	}
  
  	if (response == MMC_RESPONSE_CLIENT_ERROR) {
  		ZVAL_FALSE((zval *)param);
  		php_error_docref(NULL TSRMLS_CC, E_NOTICE, 
  				"Server %s (tcp %d, udp %d) failed with: %s (%d)",
  				mmc->host, mmc->tcp.port, 
  				mmc->udp.port, message, response);
  
  		return MMC_REQUEST_DONE;
  	}
  
  	return mmc_request_failure(mmc, request->io, message, message_len, 0 TSRMLS_CC);
  }
  /* }}} */
  
  /* {{{ proto bool memcache_flush( object memcache [, int delay ] )
     Flushes cache, optionally at after the specified delay */
  PHP_FUNCTION(memcache_flush)
  {
  	mmc_pool_t *pool;
  	zval *mmc_object = getThis();
  
  	mmc_request_t *request;
  	unsigned int i, responses = 0;
  	long delay = 0;
  
  	if (mmc_object == NULL) {
  		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O|l", &mmc_object, memcache_pool_ce, &delay) == FAILURE) {
  			return;
  		}
  	}
  	else {
  		if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &delay) == FAILURE) {
  			return;
  		}
  	}
  
  	if (!mmc_get_pool(mmc_object, &pool TSRMLS_CC)) {
  		RETURN_FALSE;
  	}
  
  	for (i=0; i<pool->num_servers; i++) {
  		request = mmc_pool_request(pool, MMC_PROTO_TCP, mmc_flush_handler, &responses, NULL, NULL TSRMLS_CC);
  		pool->protocol->flush(request, delay);
  
  		if (mmc_pool_schedule(pool, pool->servers[i], request TSRMLS_CC) == MMC_OK) {
  			/* begin sending requests immediatly */
  			mmc_pool_select(pool TSRMLS_CC);
  		}
  	}
  
  	/* execute all requests */
  	mmc_pool_run(pool TSRMLS_CC);
  
  	if (responses < pool->num_servers) {
  		RETURN_FALSE;
  	}
  	RETURN_TRUE;
  }
  /* }}} */
  
  /* {{{ proto bool memcache_debug( bool onoff ) */
  PHP_FUNCTION(memcache_debug)
  {
  	php_error_docref(NULL TSRMLS_CC, E_WARNING, "memcache_debug() is deprecated, please use a debugger (like Eclipse + CDT)");
  }
  /* }}} */
  
  /*
   * Local variables:
   * tab-width: 4
   * c-basic-offset: 4
   * End:
   * vim600: noet sw=4 ts=4 fdm=marker
   * vim<600: noet sw=4 ts=4
   */
  