diff --git a/php_memcached.c b/php_memcached.c index 6f4ae3b..23e9bde 100644 --- a/php_memcached.c +++ b/php_memcached.c @@ -49,6 +49,12 @@ # include "ext/msgpack/php_msgpack.h" #endif +#ifdef ZTS +#define MEMC_G(v) TSRMG(php_memcached_globals_id, zend_php_memcached_globals *, memc_ini.v) +#else +#define MEMC_G(v) (php_memcached_globals.memc_ini.v) +#endif + /**************************************** Custom options @@ -106,7 +112,7 @@ "get" operation flags ****************************************/ #define MEMC_GET_PRESERVE_ORDER (1<<0) - +#define MEMC_GET_EXTENDED (2<<0) /**************************************** Helper macros @@ -136,6 +142,47 @@ #define RETURN_FROM_GET RETURN_FALSE +typedef memcached_return (*memc_store_fn)( + memcached_st *, + const char *key, size_t key_len, + const char *payload, size_t payload_len, + time_t expiration, uint32_t flags); + +typedef memcached_return (*memc_store_by_key_fn)( + memcached_st *, + const char *server_key, size_t server_key_len, + const char *key, size_t key_len, + const char *payload, size_t payload_len, + time_t expiration, uint32_t flags); + +static memc_store_fn php_memc_store_funcs[] = { + &memcached_set, + &memcached_add, + &memcached_replace, + &memcached_prepend, + &memcached_append +}; + +void s_call_store_func(int op, memcached_st *memc, const char *key, size_t key_len, const char *payload, size_t payload_len, time_t expiration, uint32_t flags) +{ + (*php_memc_store_funcs[op])(memc, key, key_len,payload, payload_len, expiration, flags); +} + + +static memc_store_by_key_fn php_memc_store_by_keys_funcs[] = { + &memcached_set_by_key, + &memcached_add_by_key, + &memcached_replace_by_key, + &memcached_prepend_by_key, + &memcached_append_by_key +}; + +void s_call_store_by_key_func(int op, memcached_st *memc, const char *server_key, size_t server_key_len, const char *key, size_t key_len, const char *payload, size_t payload_len, time_t expiration, uint32_t flags) +{ + (*php_memc_store_by_keys_funcs[op])(memc, server_key, server_key_len, key, key_len,payload, payload_len, expiration, flags); +} + + /**************************************** Structures and definitions ****************************************/ @@ -157,7 +204,6 @@ typedef struct { zend_long set_udf_flags; } *obj; - zval last_udf_flags; zend_bool is_persistent; zend_bool is_pristine; int rescode; @@ -220,14 +266,6 @@ struct callbackContext static zend_class_entry *spl_ce_RuntimeException = NULL; #endif -#if (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION < 3) -const zend_fcall_info empty_fcall_info = { 0, NULL, NULL, NULL, NULL, 0, NULL, NULL, 0 }; -#undef ZEND_BEGIN_ARG_INFO_EX -#define ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, return_reference, required_num_args) \ - static zend_arg_info name[] = { \ - { NULL, 0, NULL, 0, 0, 0, pass_rest_by_reference, return_reference, required_num_args }, -#endif - ZEND_DECLARE_MODULE_GLOBALS(php_memcached) #ifdef COMPILE_DL_MEMCACHED @@ -275,44 +313,54 @@ static PHP_INI_MH(OnUpdateSerializer) return OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); } +#define MEMC_INI_ENTRY(key, default_value, update_fn, gkey) \ + STD_PHP_INI_ENTRY("memcached.##key", default_value, PHP_INI_ALL, update_fn, memc_ini.gkey, zend_php_memcached_globals, php_memcached_globals) + +#define MEMC_SESSION_INI_ENTRY(key, default_value, update_fn, gkey) \ + STD_PHP_INI_ENTRY("memcached.session.##key", default_value, PHP_INI_ALL, update_fn, session_ini.gkey, zend_php_memcached_globals, php_memcached_globals) + + /* {{{ INI entries */ PHP_INI_BEGIN() + #ifdef HAVE_MEMCACHED_SESSION - STD_PHP_INI_ENTRY("memcached.sess_locking", "1", PHP_INI_ALL, OnUpdateBool, sess_locking_enabled, zend_php_memcached_globals, php_memcached_globals) - STD_PHP_INI_ENTRY("memcached.sess_consistent_hash", "0", PHP_INI_ALL, OnUpdateBool, sess_consistent_hash_enabled, zend_php_memcached_globals, php_memcached_globals) - STD_PHP_INI_ENTRY("memcached.sess_binary", "0", PHP_INI_ALL, OnUpdateBool, sess_binary_enabled, zend_php_memcached_globals, php_memcached_globals) - STD_PHP_INI_ENTRY("memcached.sess_lock_wait", "150000", PHP_INI_ALL, OnUpdateLongGEZero,sess_lock_wait, zend_php_memcached_globals, php_memcached_globals) - STD_PHP_INI_ENTRY("memcached.sess_lock_max_wait", "0", PHP_INI_ALL, OnUpdateLongGEZero, sess_lock_max_wait, zend_php_memcached_globals, php_memcached_globals) - STD_PHP_INI_ENTRY("memcached.sess_lock_expire", "0", PHP_INI_ALL, OnUpdateLongGEZero, sess_lock_expire, zend_php_memcached_globals, php_memcached_globals) - STD_PHP_INI_ENTRY("memcached.sess_prefix", "memc.sess.key.", PHP_INI_ALL, OnUpdateString, sess_prefix, zend_php_memcached_globals, php_memcached_globals) + MEMC_SESSION_INI_ENTRY("lock_enabled", "1", OnUpdateBool, lock_enabled) + MEMC_SESSION_INI_ENTRY("lock_wait_min", "1000", OnUpdateLongGEZero, lock_wait_min) + MEMC_SESSION_INI_ENTRY("lock_wait_max", "2000", OnUpdateLongGEZero, lock_wait_max) + MEMC_SESSION_INI_ENTRY("lock_retries", "5", OnUpdateLongGEZero, lock_retries) + MEMC_SESSION_INI_ENTRY("lock_expiration", "0", OnUpdateLongGEZero, lock_expiration) + MEMC_SESSION_INI_ENTRY("compression", "1", OnUpdateBool, compression_enabled) + MEMC_SESSION_INI_ENTRY("binary_protocol", "1", OnUpdateBool, binary_protocol_enabled) + MEMC_SESSION_INI_ENTRY("consistent_hash", "1", OnUpdateBool, consistent_hash_enabled) + MEMC_SESSION_INI_ENTRY("number_of_replicas", "0", OnUpdateLongGEZero, number_of_replicas) + MEMC_SESSION_INI_ENTRY("randomize_replica_read", "0", OnUpdateLongGEZero, randomize_replica_read_enabled) + MEMC_SESSION_INI_ENTRY("remove_failed_servers", "0", OnUpdateBool, remove_failed_servers_enabled) + MEMC_SESSION_INI_ENTRY("server_failure_limit", "0", OnUpdateLongGEZero, server_failure_limit) + MEMC_SESSION_INI_ENTRY("connect_timeout", "0", OnUpdateLongGEZero, connect_timeout) + MEMC_SESSION_INI_ENTRY("sasl_username", "", OnUpdateString, sasl_username) + MEMC_SESSION_INI_ENTRY("sasl_password", "", OnUpdateString, sasl_password) + MEMC_SESSION_INI_ENTRY("prefix", "memc.sess.", OnUpdateString, prefix) + MEMC_SESSION_INI_ENTRY("persistent", "0", OnUpdateBool, persistent_enabled) +#endif - STD_PHP_INI_ENTRY("memcached.sess_number_of_replicas", "0", PHP_INI_ALL, OnUpdateLongGEZero, sess_number_of_replicas, zend_php_memcached_globals, php_memcached_globals) - STD_PHP_INI_ENTRY("memcached.sess_randomize_replica_read", "0", PHP_INI_ALL, OnUpdateBool, sess_randomize_replica_read, zend_php_memcached_globals, php_memcached_globals) - STD_PHP_INI_ENTRY("memcached.sess_remove_failed", "0", PHP_INI_ALL, OnUpdateBool, sess_remove_failed_enabled, zend_php_memcached_globals, php_memcached_globals) - STD_PHP_INI_ENTRY("memcached.sess_connect_timeout", "1000", PHP_INI_ALL, OnUpdateLong, sess_connect_timeout, zend_php_memcached_globals, php_memcached_globals) -#if HAVE_MEMCACHED_SASL - STD_PHP_INI_ENTRY("memcached.sess_sasl_username", "", PHP_INI_ALL, OnUpdateString, sess_sasl_username, zend_php_memcached_globals, php_memcached_globals) - STD_PHP_INI_ENTRY("memcached.sess_sasl_password", "", PHP_INI_ALL, OnUpdateString, sess_sasl_password, zend_php_memcached_globals, php_memcached_globals) -#endif -#endif - STD_PHP_INI_ENTRY("memcached.compression_type", "fastlz", PHP_INI_ALL, OnUpdateCompressionType, compression_type, zend_php_memcached_globals, php_memcached_globals) - STD_PHP_INI_ENTRY("memcached.compression_factor", "1.3", PHP_INI_ALL, OnUpdateReal, compression_factor, zend_php_memcached_globals, php_memcached_globals) - STD_PHP_INI_ENTRY("memcached.compression_threshold", "2000", PHP_INI_ALL, OnUpdateLong, compression_threshold, zend_php_memcached_globals, php_memcached_globals) + MEMC_INI_ENTRY("compression_type", "fastlz", OnUpdateCompressionType, compression_type) + MEMC_INI_ENTRY("compression_factor", "1.3", OnUpdateReal, compression_factor) + MEMC_INI_ENTRY("compression_threshold", "2000", OnUpdateLong, compression_threshold) + MEMC_INI_ENTRY("serializer", SERIALIZER_DEFAULT_NAME, OnUpdateSerializer, serializer_name) + MEMC_INI_ENTRY("use_sasl", "0", OnUpdateBool, sasl_enabled) + MEMC_INI_ENTRY("store_retry_count", "2", OnUpdateLong, store_retry_count) - STD_PHP_INI_ENTRY("memcached.serializer", SERIALIZER_DEFAULT_NAME, PHP_INI_ALL, OnUpdateSerializer, serializer_name, zend_php_memcached_globals, php_memcached_globals) -#if HAVE_MEMCACHED_SASL - STD_PHP_INI_ENTRY("memcached.use_sasl", "0", PHP_INI_SYSTEM, OnUpdateBool, use_sasl, zend_php_memcached_globals, php_memcached_globals) -#endif - STD_PHP_INI_ENTRY("memcached.store_retry_count", "2", PHP_INI_ALL, OnUpdateLong, store_retry_count, zend_php_memcached_globals, php_memcached_globals) PHP_INI_END() /* }}} */ +#undef MEMC_INI_ENTRY +#undef MEMC_SESSION_INI_ENTRY + /**************************************** Forward declarations ****************************************/ static int php_memc_handle_error(php_memc_t *i_obj, memcached_return status); -static char *php_memc_zval_to_payload(zval *value, size_t *payload_len, uint32_t *flags, enum memcached_serializer serializer, enum memcached_compression_type compression_type); -static int php_memc_zval_from_payload(zval *value, const char *payload, size_t payload_len, uint32_t flags, enum memcached_serializer serializer); + static void php_memc_get_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key); static void php_memc_getMulti_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key); static void php_memc_store_impl(INTERNAL_FUNCTION_PARAMETERS, int op, zend_bool by_key); @@ -327,6 +375,14 @@ static memcached_return php_memc_do_stats_callback(const memcached_st *ptr, php_ static memcached_return php_memc_do_version_callback(const memcached_st *ptr, php_memcached_instance_st instance, void *in_context); static void php_memc_destroy(struct memc_obj *m_obj, zend_bool persistent); + +static + zend_bool s_memcached_result_to_zval(memcached_result_st *result, zval *return_value); + +static + zend_string *s_zval_to_payload(zval *value, uint32_t *flags, enum memcached_serializer serializer, enum memcached_compression_type compression_type); + + /**************************************** Method implementations ****************************************/ @@ -391,7 +447,7 @@ static zend_bool php_memcached_on_new_callback(zval *object, zend_fcall_info *fc efree (buf); ret = 0; } - + if (Z_TYPE(retval) != IS_UNDEF) zval_ptr_dtor(&retval); @@ -400,7 +456,7 @@ static zend_bool php_memcached_on_new_callback(zval *object, zend_fcall_info *fc return ret; } -static int le_memc, le_memc_sess; +static int le_memc; static int php_memc_list_entry(void) { @@ -443,7 +499,6 @@ static PHP_METHOD(Memcached, __construct) } i_obj->is_persistent = is_persistent; - array_init(&i_obj->last_udf_flags); if (!m_obj) { m_obj = pecalloc(1, sizeof(*m_obj), is_persistent); @@ -533,63 +588,211 @@ static PHP_METHOD(Memcached, __construct) } /* }}} */ -/* {{{ Memcached::getLastUserFlags() - Returns the user flags from last fetch operation */ -PHP_METHOD(Memcached, getLastUserFlags) +static +void s_uint64_to_zval (zval *target, uint64_t value) { - MEMC_METHOD_INIT_VARS; + if (value >= LONG_MAX) { + char *buffer; + spprintf (&buffer, 0, "%" PRIu64, value); + ZVAL_STRING (target, buffer); + efree(buffer); + } + else { + ZVAL_LONG (target, (zend_long) value); + } +} - if (zend_parse_parameters_none() == FAILURE) { +typedef struct { + + size_t num_valid_keys; + + const char **mkeys; + size_t *mkeys_len; + + zend_string **strings; + +} php_memcached_keys; + +static +void s_hash_to_keys(php_memcached_keys *keys_out, HashTable *hash_in, zend_bool preserve_order, zval *return_value) +{ + size_t idx = 0, alloc_count; + zval *zv; + + keys_out->num_valid_keys = 0; + + alloc_count = zend_hash_num_elements(hash_in); + if (!alloc_count) { return; } + keys_out->mkeys = ecalloc (alloc_count, sizeof (char *)); + keys_out->mkeys_len = ecalloc (alloc_count, sizeof (size_t)); + keys_out->strings = ecalloc (alloc_count, sizeof (zend_string *)); - MEMC_METHOD_FETCH_OBJECT; - RETVAL_ZVAL(&i_obj->last_udf_flags, 1, 0); + ZEND_HASH_FOREACH_VAL(hash_in, zv) { + zend_string *key = zval_get_string(zv); + + if (preserve_order && return_value) { + add_assoc_null_ex(return_value, key->val, key->len); + } + + if (key->len > 0 && key->len < MEMCACHED_MAX_KEY) { + keys_out->mkeys[idx] = key->val; + keys_out->mkeys_len[idx] = key->len; + + keys_out->strings[idx] = key; + idx++; + } + else { + zend_string_release (key); + } + + } ZEND_HASH_FOREACH_END(); + + if (!idx) { + efree (keys_out->mkeys); + efree (keys_out->mkeys_len); + efree (keys_out->strings); + } + keys_out->num_valid_keys = idx; } -/* }}} */ - -/* {{{ Memcached::get(string key [, mixed callback [, double &cas_token ] ]) - Returns a value for the given key or false */ -PHP_METHOD(Memcached, get) +static +void s_clear_keys(php_memcached_keys *keys) { - php_memc_get_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); + size_t i; + for (i = 0; i < keys->num_valid_keys; i++) { + zend_string_release (keys->strings[i]); + } + efree(keys->strings); + efree(keys->mkeys); + efree(keys->mkeys_len); } -/* }}} */ -/* {{{ Memcached::getByKey(string server_key, string key [, mixed callback [, double &cas_token ] ]) - Returns a value for key from the server identified by the server key or false */ -PHP_METHOD(Memcached, getByKey) +static +memcached_return s_memcached_get_multi(memcached_st *memc, HashTable *hash_keys, zend_string *server_key, zend_long get_flags, zval *return_value) { - php_memc_get_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); + php_memcached_keys keys = { 0 }; + zval values; + + memcached_return rc, status = MEMCACHED_SUCCESS; + + size_t num_keys; + uint64_t orig_cas_flag; + memcached_result_st result; + + zend_bool extended = (get_flags & MEMC_GET_EXTENDED); + zend_bool preserve_order = (get_flags & MEMC_GET_PRESERVE_ORDER); + + array_init(&values); + s_hash_to_keys(&keys, hash_keys, preserve_order, &values); + + if (!keys.num_valid_keys) { + zval_ptr_dtor(&values); + return MEMCACHED_NO_KEY_PROVIDED; + } + + orig_cas_flag = memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS); + + if (extended && !orig_cas_flag) { + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, 1); + } + + if (server_key) { + status = memcached_mget_by_key(memc, server_key->val, server_key->len, keys.mkeys, keys.mkeys_len, keys.num_valid_keys); + } else { + status = memcached_mget(memc, keys.mkeys, keys.mkeys_len, keys.num_valid_keys); + } + + s_clear_keys(&keys); + + if (extended && !orig_cas_flag) { + memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, 0); + } + + memcached_result_create(memc, &result); + while ((memcached_fetch_result(memc, &result, &rc)) != NULL) { + + const char *res_key = NULL; + size_t res_key_len = 0; + zval value; + + /* For some reason instead of success it's end */ + if (rc == MEMCACHED_END) { + rc = MEMCACHED_SUCCESS; + } + + if (rc != MEMCACHED_SUCCESS) { + status = rc; + continue; + } + + ZVAL_UNDEF(&value); + if (!s_memcached_result_to_zval(&result, &value)) { + if (EG(exception)) { + status = MEMC_RES_PAYLOAD_FAILURE; + + memcached_result_free(&result); + memcached_quit(memc); + zval_ptr_dtor (&values); + break; + } + status = MEMCACHED_SOME_ERRORS; + continue; + } + + res_key = memcached_result_key_value(&result); + res_key_len = memcached_result_key_length(&result); + + if (extended) { + uint32_t flags; + uint64_t cas; + zval cas_token, node; + + cas = memcached_result_cas(&result); + flags = memcached_result_flags(&result); + + s_uint64_to_zval (&cas_token, cas); + + array_init (&node); + add_assoc_zval (&node, "value", &value); + add_assoc_zval (&node, "cas", &cas_token); + add_assoc_long (&node, "flags", (zend_long) MEMC_VAL_GET_USER_FLAGS(flags)); + + add_assoc_zval_ex(&values, res_key, res_key_len, &node); + } + else { + add_assoc_zval_ex(&values, res_key, res_key_len, &value); + } + } + memcached_result_free(&result); + + if (Z_TYPE(values) != IS_UNDEF) { + ZVAL_ZVAL(return_value, &values, 0, 0); + } + return status; } -/* }}} */ -/* {{{ -- php_memc_get_impl */ -static void php_memc_get_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key) + +static +void php_memc_get_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key) { + zend_long get_flags = 0; zend_string *key; zend_string *server_key = NULL; - const char *payload = NULL; - size_t payload_len = 0; - uint32_t flags = 0; - uint64_t cas = 0; - const char* keys[1] = { NULL }; - size_t key_lens[1] = { 0 }; - zval *cas_token = NULL; - uint64_t orig_cas_flag; + memcached_return status = MEMCACHED_SUCCESS; zend_fcall_info fci = empty_fcall_info; zend_fcall_info_cache fcc = empty_fcall_info_cache; - memcached_result_st result; - memcached_return status = MEMCACHED_SUCCESS; + HashTable keys; + zval values, tmp; MEMC_METHOD_INIT_VARS; if (by_key) { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|f!z", &server_key, &key, &fci, &fcc, &cas_token) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "SS|f!l", &server_key, &key, &fci, &fcc, &get_flags) == FAILURE) { return; } } else { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|f!z", &key, &fci, &fcc, &cas_token) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|f!l", &key, &fci, &fcc, &get_flags) == FAILURE) { return; } } @@ -597,118 +800,58 @@ static void php_memc_get_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key) MEMC_METHOD_FETCH_OBJECT; i_obj->rescode = MEMCACHED_SUCCESS; - zend_hash_clean(Z_ARRVAL(i_obj->last_udf_flags)); + zend_hash_init (&keys, 1, 0, NULL, 0); + ZVAL_STR(&tmp, key); + zend_hash_add(&keys, key, &tmp); - if (key->len == 0 || (!memcached_behavior_get(m_obj->memc, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL) && strchr(key->val, ' '))) { - i_obj->rescode = MEMCACHED_BAD_KEY_PROVIDED; + ZVAL_UNDEF(&values); + status = s_memcached_get_multi(m_obj->memc, &keys, server_key, get_flags, &values); + zend_hash_destroy (&keys); + + if (EG(exception)) { + zval_ptr_dtor(&values); RETURN_FROM_GET; } - keys[0] = key->val; - key_lens[0] = key->len; - - orig_cas_flag = memcached_behavior_get(m_obj->memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS); - - /* - * Enable CAS support, but only if it is currently disabled. - */ - if (cas_token && Z_ISREF_P(cas_token) && orig_cas_flag == 0) { - memcached_behavior_set(m_obj->memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, 1); + if (status == MEMCACHED_SUCCESS) { + zval *zv = zend_hash_find (Z_ARRVAL(values), key); + if (zv) { + RETVAL_ZVAL(zv, 1, 0); + zval_ptr_dtor(&values); + return; + } + else { + status = MEMCACHED_NOTFOUND; + } } - if (by_key) { - status = memcached_mget_by_key(m_obj->memc, server_key->val, server_key->len, keys, key_lens, 1); - } else { - status = memcached_mget(m_obj->memc, keys, key_lens, 1); + if (Z_TYPE(values) != IS_UNDEF) { + zval_ptr_dtor(&values); } - if (cas_token && Z_ISREF_P(cas_token) && orig_cas_flag == 0) { - memcached_behavior_set(m_obj->memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, orig_cas_flag); + + if (status == MEMCACHED_NOTFOUND && fci.size > 0) { + // try to invoke cache cb + status = php_memc_do_cache_callback(getThis(), &fci, &fcc, key, return_value); } if (php_memc_handle_error(i_obj, status) < 0) { RETURN_FROM_GET; } +} - status = MEMCACHED_SUCCESS; - memcached_result_create(m_obj->memc, &result); - - if (memcached_fetch_result(m_obj->memc, &result, &status) == NULL) { - /* This is for historical reasons */ - if (status == MEMCACHED_END) - status = MEMCACHED_NOTFOUND; - - /* - * If the result wasn't found, and we have the read-through callback, invoke - * it to get the value. The CAS token will be 0, because we cannot generate it - * ourselves. - */ - if (cas_token) { - ZVAL_DEREF(cas_token); - zval_ptr_dtor(cas_token); - ZVAL_DOUBLE(cas_token, 0.0); - } - - if (status == MEMCACHED_NOTFOUND && fci.size != 0) { - status = php_memc_do_cache_callback(getThis(), &fci, &fcc, key, return_value); - } - - if (php_memc_handle_error(i_obj, status) < 0) { - memcached_result_free(&result); - RETURN_FROM_GET; - } - - /* if we have a callback, all processing is done */ - if (fci.size != 0) { - memcached_result_free(&result); - return; - } - } - - /* Fetch all remaining results */ - memcached_result_st dummy_result; - memcached_return dummy_status = MEMCACHED_SUCCESS; - memcached_result_create(m_obj->memc, &dummy_result); - while (memcached_fetch_result(m_obj->memc, &dummy_result, &dummy_status) != NULL) {} - memcached_result_free(&dummy_result); - - payload = memcached_result_value(&result); - payload_len = memcached_result_length(&result); - flags = memcached_result_flags(&result); - if (cas_token) { - cas = memcached_result_cas(&result); - } - - if (php_memc_zval_from_payload(return_value, payload, payload_len, flags, m_obj->serializer) < 0) { - memcached_result_free(&result); - i_obj->rescode = MEMC_RES_PAYLOAD_FAILURE; - RETURN_FROM_GET; - } - - if (cas_token) { - ZVAL_DEREF(cas_token); - zval_ptr_dtor(cas_token); - ZVAL_DOUBLE(cas_token, (double)cas); - } - - /* Parse user flags */ - add_assoc_long_ex(&i_obj->last_udf_flags, key->val, key->len, MEMC_VAL_GET_USER_FLAGS(flags)); - memcached_result_free(&result); +/* {{{ Memcached::get(string key [, mixed callback [, int get_flags = 0]) + Returns a value for the given key or false */ +PHP_METHOD(Memcached, get) +{ + php_memc_get_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } /* }}} */ -/* {{{ Memcached::getMulti(array keys [, array &cas_tokens ]) - Returns values for the given keys or false */ -PHP_METHOD(Memcached, getMulti) +/* {{{ Memcached::getByKey(string server_key, string key [, mixed callback [, int get_flags = 0]) + Returns a value for key from the server identified by the server key or false */ +PHP_METHOD(Memcached, getByKey) { - php_memc_getMulti_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); -} -/* }}} */ - -/* {{{ Memcached::getMultiByKey(string server_key, array keys [, array &cas_tokens ]) - Returns values for the given keys from the server identified by the server key or false */ -PHP_METHOD(Memcached, getMultiByKey) -{ - php_memc_getMulti_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); + php_memc_get_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } /* }}} */ @@ -717,191 +860,50 @@ static void php_memc_getMulti_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_ke { zval *keys = NULL; zend_string *server_key = NULL; - size_t num_keys = 0; - zval *entry = NULL; - const char *payload = NULL; - size_t payload_len = 0; - const char **mkeys = NULL; - size_t *mkeys_len = NULL; - const char *tmp_key = NULL; - size_t res_key_len = 0; - uint32_t flags; - uint64_t cas = 0; - zval *cas_tokens = NULL; - uint64_t orig_cas_flag = 0; - zval value; - long get_flags = 0; - int i = 0; - zend_bool preserve_order; - memcached_result_st result; - memcached_return status = MEMCACHED_SUCCESS; + zend_bool extended_results = 0; + memcached_return rc; + zend_long flags = 0; MEMC_METHOD_INIT_VARS; if (by_key) { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sa/|zl", &server_key, - &keys, &cas_tokens, &get_flags) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sa/|l", &server_key, + &keys, &flags) == FAILURE) { return; } } else { - if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/|zl", &keys, &cas_tokens, &get_flags) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS(), "a/|l", &keys, &flags) == FAILURE) { return; } } MEMC_METHOD_FETCH_OBJECT; - i_obj->rescode = MEMCACHED_SUCCESS; - /* clean flags */ - zend_hash_clean(Z_ARRVAL(i_obj->last_udf_flags)); - - preserve_order = (get_flags & MEMC_GET_PRESERVE_ORDER); - num_keys = zend_hash_num_elements(Z_ARRVAL_P(keys)); - mkeys = safe_emalloc(num_keys, sizeof(*mkeys), 0); - mkeys_len = safe_emalloc(num_keys, sizeof(*mkeys_len), 0); - array_init(return_value); - - /* - * Create the array of keys for libmemcached. If none of the keys were valid - * (strings), set bad key result code and return. - */ - ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(keys), entry) { - if (Z_TYPE_P(entry) != IS_STRING) { - convert_to_string_ex(entry); - } - - if (Z_TYPE_P(entry) == IS_STRING && Z_STRLEN_P(entry) > 0) { - mkeys[i] = Z_STRVAL_P(entry); - mkeys_len[i] = Z_STRLEN_P(entry); - - if (preserve_order) { - add_assoc_null_ex(return_value, mkeys[i], mkeys_len[i]); - } - i++; - } - } ZEND_HASH_FOREACH_END(); - - if (i == 0) { - i_obj->rescode = MEMCACHED_NOTFOUND; - efree(mkeys); - efree(mkeys_len); - return; - } - - /* - * Enable CAS support, but only if it is currently disabled. - */ - if (cas_tokens && Z_ISREF_P(cas_tokens)) { - orig_cas_flag = memcached_behavior_get(m_obj->memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS); - if (orig_cas_flag == 0) { - memcached_behavior_set(m_obj->memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, 1); - } - } - - if (by_key) { - status = memcached_mget_by_key(m_obj->memc, server_key->val, server_key->len, mkeys, mkeys_len, i); - } else { - status = memcached_mget(m_obj->memc, mkeys, mkeys_len, i); - } - /* Handle error, but ignore, there might still be some result */ - php_memc_handle_error(i_obj, status); - - /* - * Restore the CAS support flag, but only if we had to turn it on. - */ - if (cas_tokens && Z_ISREF_P(cas_tokens) && orig_cas_flag == 0) { - memcached_behavior_set(m_obj->memc, MEMCACHED_BEHAVIOR_SUPPORT_CAS, orig_cas_flag); - } - - efree(mkeys); - efree(mkeys_len); - - /* - * Iterate through the result set and create the result array. The CAS tokens are - * returned as doubles, because we cannot store potential 64-bit values in longs. - */ - if (cas_tokens) { - if (Z_ISREF_P(cas_tokens)) { - /* cas_tokens was passed by reference, we'll create an array for it. */ - ZVAL_DEREF(cas_tokens); - SEPARATE_ZVAL(cas_tokens); - zval_dtor(cas_tokens); - array_init(cas_tokens); - } else { - /* Not passed by reference, we allow this (eg.: if you specify null - to not enable cas but you want to use the udf_flags parameter). - We destruct it and set it to null for the peace of mind. */ - cas_tokens = NULL; - } - } - - /* - * Iterate through the result set and create the result array. The flags are - * returned as longs. - */ - - memcached_result_create(m_obj->memc, &result); - while ((memcached_fetch_result(m_obj->memc, &result, &status)) != NULL) { - char res_key [MEMCACHED_MAX_KEY]; - - if (status != MEMCACHED_SUCCESS) { - status = MEMCACHED_SOME_ERRORS; - php_memc_handle_error(i_obj, status); - continue; - } - - payload = memcached_result_value(&result); - payload_len = memcached_result_length(&result); - flags = memcached_result_flags(&result); - tmp_key = memcached_result_key_value(&result); - res_key_len = memcached_result_key_length(&result); - - /* - * This may be a bug in libmemcached, the key is not null terminated - * whe using the binary protocol. - */ - memcpy (res_key, tmp_key, res_key_len >= MEMCACHED_MAX_KEY ? MEMCACHED_MAX_KEY - 1 : res_key_len); - res_key [res_key_len] = '\0'; - - if (php_memc_zval_from_payload(&value, payload, payload_len, flags, m_obj->serializer) < 0) { - zval_ptr_dtor(&value); - if (EG(exception)) { - status = MEMC_RES_PAYLOAD_FAILURE; - php_memc_handle_error(i_obj, status); - memcached_quit(m_obj->memc); - - break; - } - status = MEMCACHED_SOME_ERRORS; - i_obj->rescode = MEMCACHED_SOME_ERRORS; - - continue; - } - - add_assoc_zval_ex(return_value, res_key, res_key_len, &value); - if (cas_tokens) { - cas = memcached_result_cas(&result); - add_assoc_double_ex(cas_tokens, res_key, res_key_len, (double)cas); - } - add_assoc_long_ex(&i_obj->last_udf_flags, res_key, res_key_len, MEMC_VAL_GET_USER_FLAGS(flags)); - } - - memcached_result_free(&result); + rc = s_memcached_get_multi(m_obj->memc, Z_ARRVAL_P(keys), server_key, flags, return_value); + php_memc_handle_error(i_obj, rc); if (EG(exception)) { - /* XXX: cas_tokens should only be set on success, currently we're destructive */ - if (cas_tokens) { - ZVAL_DEREF(cas_tokens); - SEPARATE_ZVAL(cas_tokens); - zval_dtor(cas_tokens); - ZVAL_NULL(cas_tokens); - } - zend_hash_clean(Z_ARRVAL(i_obj->last_udf_flags)); zval_dtor(return_value); RETURN_FALSE; } } /* }}} */ +/* {{{ Memcached::getMulti(array keys[, long flags = 0 ]) + Returns values for the given keys or false */ +PHP_METHOD(Memcached, getMulti) +{ + php_memc_getMulti_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); +} +/* }}} */ + +/* {{{ Memcached::getMultiByKey(string server_key, array keys[, long flags = 0 ]) + Returns values for the given keys from the server identified by the server key or false */ +PHP_METHOD(Memcached, getMultiByKey) +{ + php_memc_getMulti_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); +} +/* }}} */ + /* {{{ Memcached::getDelayed(array keys [, bool with_cas [, mixed callback ] ]) Sends a request for the given keys and returns immediately */ PHP_METHOD(Memcached, getDelayed) @@ -1075,9 +1077,8 @@ PHP_METHOD(Memcached, fetch) res_key_len = memcached_result_key_length(&result); cas = memcached_result_cas(&result); - if (php_memc_zval_from_payload(&value, payload, payload_len, flags, m_obj->serializer) < 0) { + if (!s_memcached_result_to_zval(&result, &value)) { memcached_result_free(&result); - zval_ptr_dtor(&value); i_obj->rescode = MEMC_RES_PAYLOAD_FAILURE; RETURN_FALSE; } @@ -1130,9 +1131,8 @@ PHP_METHOD(Memcached, fetchAll) res_key_len = memcached_result_key_length(&result); cas = memcached_result_cas(&result); - if (php_memc_zval_from_payload(&value, payload, payload_len, flags, m_obj->serializer) < 0) { + if (!s_memcached_result_to_zval(&result, &value)) { memcached_result_free(&result); - zval_ptr_dtor(&value); zval_dtor(return_value); i_obj->rescode = MEMC_RES_PAYLOAD_FAILURE; RETURN_FALSE; @@ -1242,11 +1242,10 @@ static void php_memc_setMulti_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_ke zval *entries; zend_string *server_key = NULL; time_t expiration = 0; - zval *entry; + zval *value; zend_string *skey, *str_key = NULL; ulong num_key; - char *payload; - size_t payload_len; + zend_string *payload; uint32_t flags = 0; uint32_t retry = 0; memcached_return status; @@ -1268,7 +1267,7 @@ static void php_memc_setMulti_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_ke MEMC_METHOD_FETCH_OBJECT; i_obj->rescode = MEMCACHED_SUCCESS; - ZEND_HASH_FOREACH_KEY_VAL (Z_ARRVAL_P(entries), num_key, skey, entry) { + ZEND_HASH_FOREACH_KEY_VAL (Z_ARRVAL_P(entries), num_key, skey, value) { if (skey) { str_key = skey; } else if (num_key || num_key == 0) { @@ -1291,7 +1290,7 @@ static void php_memc_setMulti_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_ke MEMC_VAL_SET_USER_FLAGS(flags, ((uint32_t) m_obj->set_udf_flags)); } - payload = php_memc_zval_to_payload(entry, &payload_len, &flags, m_obj->serializer, m_obj->compression_type); + payload = s_zval_to_payload(value, &flags, m_obj->serializer, m_obj->compression_type); if (payload == NULL) { i_obj->rescode = MEMC_RES_PAYLOAD_FAILURE; if (!skey) { @@ -1302,9 +1301,9 @@ static void php_memc_setMulti_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_ke retry: if (!by_key) { - status = memcached_set(m_obj->memc, str_key->val, str_key->len, payload, payload_len, expiration, flags); + status = memcached_set(m_obj->memc, str_key->val, str_key->len, payload->val, payload->len, expiration, flags); } else { - status = memcached_set_by_key(m_obj->memc, server_key->val, server_key->len, str_key->val, str_key->len, payload, payload_len, expiration, flags); + status = memcached_set_by_key(m_obj->memc, server_key->val, server_key->len, str_key->val, str_key->len, payload->val, payload->len, expiration, flags); } if (php_memc_handle_error(i_obj, status) < 0) { @@ -1312,13 +1311,13 @@ retry: if (!skey) { zend_string_release(str_key); } - efree(payload); + zend_string_release(payload); RETURN_FALSE; } if (!skey) { zend_string_release(str_key); } - efree(payload); + zend_string_release(payload); } ZEND_HASH_FOREACH_END(); RETURN_TRUE; @@ -1395,11 +1394,10 @@ static void php_memc_store_impl(INTERNAL_FUNCTION_PARAMETERS, int op, zend_bool zend_string *key; zend_string *server_key = NULL; zend_string *s_value; - zval s_zvalue; + zval s_zvalue; zval *value; - long expiration = 0; - char *payload = NULL; - size_t payload_len; + zend_long expiration = 0; + zend_string *payload = NULL; uint32_t flags = 0; uint32_t retry = 0; memcached_return status; @@ -1474,8 +1472,9 @@ static void php_memc_store_impl(INTERNAL_FUNCTION_PARAMETERS, int op, zend_bool php_error_docref(NULL, E_WARNING, "using touch command with binary protocol is not recommended with libmemcached versions below 1.0.16"); } #endif - } else { - payload = php_memc_zval_to_payload(value, &payload_len, &flags, m_obj->serializer, m_obj->compression_type); + } + else { + payload = s_zval_to_payload(value, &flags, m_obj->serializer, m_obj->compression_type); if (payload == NULL) { i_obj->rescode = MEMC_RES_PAYLOAD_FAILURE; RETURN_FALSE; @@ -1485,10 +1484,10 @@ retry: switch (op) { case MEMC_OP_SET: if (!server_key) { - status = memcached_set(m_obj->memc, key->val, key->len, payload, payload_len, expiration, flags); + status = memcached_set(m_obj->memc, key->val, key->len, payload->val, payload->len, expiration, flags); } else { status = memcached_set_by_key(m_obj->memc, server_key->val, server_key->len, key->val, - key->len, payload, payload_len, expiration, flags); + key->len, payload->val, payload->len, expiration, flags); } break; #ifdef HAVE_MEMCACHED_TOUCH @@ -1503,37 +1502,37 @@ retry: #endif case MEMC_OP_ADD: if (!server_key) { - status = memcached_add(m_obj->memc, key->val, key->len, payload, payload_len, expiration, flags); + status = memcached_add(m_obj->memc, key->val, key->len, payload->val, payload->len, expiration, flags); } else { status = memcached_add_by_key(m_obj->memc, server_key->val, server_key->len, key->val, - key->len, payload, payload_len, expiration, flags); + key->len, payload->val, payload->len, expiration, flags); } break; case MEMC_OP_REPLACE: if (!server_key) { - status = memcached_replace(m_obj->memc, key->val, key->len, payload, payload_len, expiration, flags); + status = memcached_replace(m_obj->memc, key->val, key->len, payload->val, payload->len, expiration, flags); } else { status = memcached_replace_by_key(m_obj->memc, server_key->val, server_key->len, key->val, - key->len, payload, payload_len, expiration, flags); + key->len, payload->val, payload->len, expiration, flags); } break; case MEMC_OP_APPEND: if (!server_key) { - status = memcached_append(m_obj->memc, key->val, key->len, payload, payload_len, expiration, flags); + status = memcached_append(m_obj->memc, key->val, key->len, payload->val, payload->len, expiration, flags); } else { status = memcached_append_by_key(m_obj->memc, server_key->val, server_key->len, key->val, - key->len, payload, payload_len, expiration, flags); + key->len, payload->val, payload->len, expiration, flags); } break; case MEMC_OP_PREPEND: if (!server_key) { - status = memcached_prepend(m_obj->memc, key->val, key->len, payload, payload_len, expiration, flags); + status = memcached_prepend(m_obj->memc, key->val, key->len, payload->val, payload->len, expiration, flags); } else { status = memcached_prepend_by_key(m_obj->memc, server_key->val, server_key->len, key->val, - key->len, payload, payload_len, expiration, flags); + key->len, payload->val, payload->len, expiration, flags); } break; @@ -1552,7 +1551,7 @@ retry: } if (payload) { - efree(payload); + zend_string_release(payload); } } /* }}} */ @@ -1566,8 +1565,7 @@ static void php_memc_cas_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key) zend_string *server_key = NULL; zval *value; time_t expiration = 0; - char *payload; - size_t payload_len; + zend_string *payload; uint32_t flags = 0; memcached_return status; MEMC_METHOD_INIT_VARS; @@ -1602,22 +1600,22 @@ static void php_memc_cas_impl(INTERNAL_FUNCTION_PARAMETERS, zend_bool by_key) * php_memcached uses 16 bits internally to store type, compression and serialization info. * We use 16 upper bits to store user defined flags. */ - if (m_obj->set_udf_flags > 0) { + if (m_obj->set_udf_flags >= 0) { MEMC_VAL_SET_USER_FLAGS(flags, ((uint32_t) m_obj->set_udf_flags)); } - payload = php_memc_zval_to_payload(value, &payload_len, &flags, m_obj->serializer, m_obj->compression_type); + payload = s_zval_to_payload(value, &flags, m_obj->serializer, m_obj->compression_type); if (payload == NULL) { i_obj->rescode = MEMC_RES_PAYLOAD_FAILURE; RETURN_FALSE; } if (by_key) { - status = memcached_cas_by_key(m_obj->memc, server_key->val, server_key->len, key->val, key->len, payload, payload_len, expiration, flags, cas); + status = memcached_cas_by_key(m_obj->memc, server_key->val, server_key->len, key->val, key->len, payload->val, payload->len, expiration, flags, cas); } else { - status = memcached_cas(m_obj->memc, key->val, key->len, payload, payload_len, expiration, flags, cas); + status = memcached_cas(m_obj->memc, key->val, key->len, payload->val, payload->len, expiration, flags, cas); } - efree(payload); + zend_string_release(payload); if (php_memc_handle_error(i_obj, status) < 0) { RETURN_FALSE; } @@ -2710,7 +2708,7 @@ static PHP_METHOD(Memcached, setSaslAuthData) return; } - if (!MEMC_G(use_sasl)) { + if (!MEMC_G(sasl_enabled)) { php_error_docref(NULL, E_WARNING, "SASL support (memcached.use_sasl) isn't enabled in php.ini"); RETURN_FALSE; } @@ -2839,8 +2837,6 @@ static void php_memc_free_storage(zend_object *obj) if (i_obj->obj && !i_obj->is_persistent) { php_memc_destroy(i_obj->obj, 0); } - - zval_ptr_dtor(&i_obj->last_udf_flags); zend_object_std_dtor(&i_obj->zo); i_obj->obj = NULL; } @@ -2893,15 +2889,6 @@ ZEND_RSRC_DTOR_FUNC(php_memc_dtor) } } -ZEND_RSRC_DTOR_FUNC(php_memc_sess_dtor) -{ - if (res->ptr) { - memcached_sess *memc_sess = (memcached_sess *)res->ptr; - memcached_free(memc_sess->memc_sess); - pefree(res->ptr, 1); - res->ptr = NULL; - } -} /* }}} */ /* {{{ internal API functions */ @@ -3037,61 +3024,74 @@ static int php_memc_handle_error(php_memc_t *i_obj, memcached_return status) } static -char *s_compress_value (enum memcached_compression_type compression_type, const char *payload, size_t *payload_len, uint32_t *flags) +zend_bool s_compress_value (enum memcached_compression_type compression_type, zend_string **payload_in, uint32_t *flags) { /* status */ zend_bool compress_status = 0; + zend_string *payload = *payload_in; /* Additional 5% for the data */ - size_t buffer_size = (size_t) (((double) *payload_len * 1.05) + 1.0); - char *buffer = emalloc(sizeof(uint32_t) + buffer_size); + size_t buffer_size = (size_t) (((double) payload->len * 1.05) + 1.0); + char *buffer = emalloc(buffer_size); /* Store compressed size here */ size_t compressed_size = 0; - uint32_t plen = *payload_len; - - /* Copy the uin32_t at the beginning */ - memcpy(buffer, &plen, sizeof(uint32_t)); - buffer += sizeof(uint32_t); + uint32_t original_size = payload->len; switch (compression_type) { case COMPRESSION_TYPE_FASTLZ: - compress_status = ((compressed_size = fastlz_compress(payload, *payload_len, buffer)) > 0); - MEMC_VAL_SET_FLAG(*flags, MEMC_VAL_COMPRESSION_FASTLZ); + { + compressed_size = fastlz_compress(payload->val, payload->len, buffer); + + if (compressed_size > 0) { + compress_status = 1; + MEMC_VAL_SET_FLAG(*flags, MEMC_VAL_COMPRESSION_FASTLZ); + } + } break; case COMPRESSION_TYPE_ZLIB: - /* ZLIB returns the compressed size in this buffer */ + { compressed_size = buffer_size; + int status = compress((Bytef *) buffer, &compressed_size, (Bytef *) payload->val, payload->len); - compress_status = (compress((Bytef *)buffer, &compressed_size, (Bytef *)payload, *payload_len) == Z_OK); - MEMC_VAL_SET_FLAG(*flags, MEMC_VAL_COMPRESSION_ZLIB); + if (status == Z_OK) { + compress_status = 1; + MEMC_VAL_SET_FLAG(*flags, MEMC_VAL_COMPRESSION_ZLIB); + } + } break; default: compress_status = 0; break; } - buffer -= sizeof(uint32_t); - *payload_len = compressed_size + sizeof(uint32_t); if (!compress_status) { php_error_docref(NULL, E_WARNING, "could not compress value"); MEMC_VAL_DEL_FLAG(*flags, MEMC_VAL_COMPRESSED); - efree (buffer); - *payload_len = 0; - return NULL; + return 0; } - else if (*payload_len > (compressed_size * MEMC_G(compression_factor))) { + /* This means the value was too small to be compressed, still a success */ + if (compressed_size > (payload->len * MEMC_G(compression_factor))) { + efree(buffer); MEMC_VAL_DEL_FLAG(*flags, MEMC_VAL_COMPRESSED); - efree (buffer); - *payload_len = 0; - return NULL; + return 1; } - return buffer; + + payload = zend_string_realloc(payload, compressed_size + sizeof(uint32_t), 0); + + /* Copy the uin32_t at the beginning */ + memcpy(payload->val, &original_size, sizeof(uint32_t)); + memcpy(payload->val + sizeof (uint32_t), buffer, compressed_size); + efree(buffer); + + zend_string_forget_hash_val(payload); + *payload_in = payload; + return 1; } static @@ -3166,149 +3166,142 @@ zend_bool s_serialize_value (enum memcached_serializer serializer, zval *value, } static -char *php_memc_zval_to_payload(zval *value, size_t *payload_len, uint32_t *flags, enum memcached_serializer serializer, enum memcached_compression_type compression_type) +zend_string *s_zval_to_payload(zval *value, uint32_t *flags, enum memcached_serializer serializer, enum memcached_compression_type compression_type) { - const char *pl; - size_t pl_len = 0; - - char *payload = NULL; - smart_str buf = {0}; - char tmp[40] = {0}; + zend_string *payload; switch (Z_TYPE_P(value)) { case IS_STRING: - pl = Z_STRVAL_P(value); - pl_len = Z_STRLEN_P(value); + payload = zval_get_string(value); MEMC_VAL_SET_TYPE(*flags, MEMC_VAL_IS_STRING); + MEMC_VAL_HAS_FLAG(*flags, MEMC_VAL_COMPRESSED); break; case IS_LONG: - pl_len = sprintf(tmp, "%ld", Z_LVAL_P(value)); - pl = tmp; + { + smart_str buffer = {0}; + smart_str_append_long (&buffer, Z_LVAL_P(value)); + smart_str_0(&buffer); + payload = buffer.s; + MEMC_VAL_SET_TYPE(*flags, MEMC_VAL_IS_LONG); + } break; case IS_DOUBLE: - php_memcached_g_fmt(tmp, Z_DVAL_P(value)); - pl = tmp; - pl_len = strlen(tmp); + { + char buffer[40]; + php_memcached_g_fmt(buffer, Z_DVAL_P(value)); + payload = zend_string_init (buffer, strlen (buffer), 0); MEMC_VAL_SET_TYPE(*flags, MEMC_VAL_IS_DOUBLE); + } break; case IS_TRUE: - pl_len = 1; - tmp[0] = '1'; - tmp[1] = '\0'; - pl = tmp; + payload = zend_string_init ("1", 1, 0); MEMC_VAL_SET_TYPE(*flags, MEMC_VAL_IS_BOOL); break; case IS_FALSE: - pl_len = 0; - tmp[0] = '\0'; - pl = tmp; + payload = zend_string_alloc (0, 0); MEMC_VAL_SET_TYPE(*flags, MEMC_VAL_IS_BOOL); break; default: - if (!s_serialize_value (serializer, value, &buf, flags)) { - smart_str_free(&buf); + { + smart_str buffer = {0}; + + if (!s_serialize_value (serializer, value, &buffer, flags)) { + smart_str_free(&buffer); return NULL; } - pl = buf.s->val; - pl_len = buf.s->len; + payload = buffer.s; + MEMC_VAL_HAS_FLAG(*flags, MEMC_VAL_COMPRESSED); + } break; } + zend_string_forget_hash_val(payload); /* turn off compression for values below the threshold */ - if (MEMC_VAL_HAS_FLAG(*flags, MEMC_VAL_COMPRESSED) && pl_len < MEMC_G(compression_threshold)) { + if (payload->len == 0 || payload->len < MEMC_G(compression_threshold)) { MEMC_VAL_DEL_FLAG(*flags, MEMC_VAL_COMPRESSED); } /* If we have compression flag, compress the value */ if (MEMC_VAL_HAS_FLAG(*flags, MEMC_VAL_COMPRESSED)) { /* status */ - *payload_len = pl_len; - payload = s_compress_value (compression_type, pl, payload_len, flags); + if (!s_compress_value (compression_type, &payload, flags)) { + zend_string_release(payload); + return NULL; + } } - /* If compression failed or value is below threshold we just use plain value */ - if (!payload || !MEMC_VAL_HAS_FLAG(*flags, MEMC_VAL_COMPRESSED)) { - *payload_len = (uint32_t) pl_len; - payload = estrndup(pl, pl_len); - } - - if (buf.s) { - smart_str_free(&buf); - } return payload; } static -char *s_decompress_value (const char *payload, size_t *payload_len, uint32_t flags) +zend_string *s_decompress_value (const char *payload, size_t payload_len, uint32_t flags) { - char *buffer = NULL; - uint32_t len; + zend_string *buffer; + + uint32_t stored_length; unsigned long length; zend_bool decompress_status = 0; + zend_bool is_fastlz = 0, is_zlib = 0; - /* Stored with newer memcached extension? */ - if (MEMC_VAL_HAS_FLAG(flags, MEMC_VAL_COMPRESSION_FASTLZ) || MEMC_VAL_HAS_FLAG(flags, MEMC_VAL_COMPRESSION_ZLIB)) { - /* This is copied from Ilia's patch */ - memcpy(&len, payload, sizeof(uint32_t)); - buffer = emalloc(len + 1); - *payload_len -= sizeof(uint32_t); - payload += sizeof(uint32_t); - length = len; - - if (MEMC_VAL_HAS_FLAG(flags, MEMC_VAL_COMPRESSION_FASTLZ)) { - decompress_status = ((length = fastlz_decompress(payload, *payload_len, buffer, len)) > 0); - } else if (MEMC_VAL_HAS_FLAG(flags, MEMC_VAL_COMPRESSION_ZLIB)) { - decompress_status = (uncompress((Bytef *)buffer, &length, (Bytef *)payload, *payload_len) == Z_OK); - } + if (payload_len < sizeof (uint32_t)) { + return NULL; } - /* Fall back to 'old style decompression' */ - if (!decompress_status) { - unsigned int factor = 1, maxfactor = 16; - int status; + is_fastlz = MEMC_VAL_HAS_FLAG(flags, MEMC_VAL_COMPRESSION_FASTLZ); + is_zlib = MEMC_VAL_HAS_FLAG(flags, MEMC_VAL_COMPRESSION_ZLIB); - do { - length = (unsigned long)*payload_len * (1 << factor++); - buffer = erealloc(buffer, length + 1); - memset(buffer, 0, length + 1); - status = uncompress((Bytef *)buffer, (uLongf *)&length, (const Bytef *)payload, *payload_len); - } while ((status==Z_BUF_ERROR) && (factor < maxfactor)); + if (!is_fastlz && !is_zlib) { + php_error_docref(NULL, E_WARNING, "could not decompress value: unrecognised encryption type"); + return NULL; + } - if (status == Z_OK) { - decompress_status = 1; - } + memcpy(&stored_length, payload, sizeof (uint32_t)); + + payload += sizeof (uint32_t); + payload_len -= sizeof (uint32_t); + + buffer = zend_string_alloc (stored_length, 0); + + if (is_fastlz) { + decompress_status = ((length = fastlz_decompress(payload, payload_len, &buffer->val, buffer->len)) > 0); + } + else if (is_zlib) { + decompress_status = (uncompress((Bytef *) buffer->val, &buffer->len, (Bytef *)payload, payload_len) == Z_OK); } if (!decompress_status) { php_error_docref(NULL, E_WARNING, "could not decompress value"); - efree(buffer); + zend_string_release (buffer); return NULL; } - buffer [length] = '\0'; - *payload_len = length; + + zend_string_forget_hash_val(buffer); return buffer; } static -zend_bool s_unserialize_value (enum memcached_serializer serializer, int val_type, zval *value, const char *payload, size_t payload_len) +zend_bool s_unserialize_value (int val_type, zend_string *payload, zval *return_value) { switch (val_type) { case MEMC_VAL_IS_SERIALIZED: { - const char *payload_tmp = payload; php_unserialize_data_t var_hash; + const unsigned char *p, *max; + + p = (const unsigned char *) payload->val; + max = p + payload->len; PHP_VAR_UNSERIALIZE_INIT(var_hash); - if (!php_var_unserialize(value, (const unsigned char **)&payload_tmp, (const unsigned char *)payload_tmp + payload_len, &var_hash)) { - zval_ptr_dtor(value); - ZVAL_FALSE(value); + if (!php_var_unserialize(return_value, &p, max, &var_hash)) { + zval_ptr_dtor(return_value); + ZVAL_FALSE(return_value); PHP_VAR_UNSERIALIZE_DESTROY(var_hash); php_error_docref(NULL, E_WARNING, "could not unserialize value"); return 0; @@ -3319,13 +3312,13 @@ zend_bool s_unserialize_value (enum memcached_serializer serializer, int val_typ case MEMC_VAL_IS_IGBINARY: #ifdef HAVE_MEMCACHED_IGBINARY - if (igbinary_unserialize((uint8_t *)payload, payload_len, &value)) { - ZVAL_FALSE(value); + if (igbinary_unserialize((uint8_t *) payload->val, payload->len, &value)) { + ZVAL_FALSE(return_value); php_error_docref(NULL, E_WARNING, "could not unserialize value with igbinary"); return 0; } #else - ZVAL_FALSE(value); + ZVAL_FALSE(return_value); php_error_docref(NULL, E_WARNING, "could not unserialize value, no igbinary support"); return 0; #endif @@ -3333,9 +3326,9 @@ zend_bool s_unserialize_value (enum memcached_serializer serializer, int val_typ case MEMC_VAL_IS_JSON: #ifdef HAVE_JSON_API - php_json_decode(value, (char*) payload, payload_len, (serializer == SERIALIZER_JSON_ARRAY), PHP_JSON_PARSER_DEFAULT_DEPTH); + php_json_decode(return_value, payload->val, payload->len, (serializer == SERIALIZER_JSON_ARRAY), PHP_JSON_PARSER_DEFAULT_DEPTH); #else - ZVAL_FALSE(value); + ZVAL_FALSE(return_value); php_error_docref(NULL, E_WARNING, "could not unserialize value, no json support"); return 0; #endif @@ -3343,9 +3336,9 @@ zend_bool s_unserialize_value (enum memcached_serializer serializer, int val_typ case MEMC_VAL_IS_MSGPACK: #ifdef HAVE_MEMCACHED_MSGPACK - php_msgpack_unserialize(value, payload, payload_len); + php_msgpack_unserialize(return_value, payload->val, payload->len); #else - ZVAL_FALSE(value); + ZVAL_FALSE(return_value); php_error_docref(NULL, E_WARNING, "could not unserialize value, no msgpack support"); return 0; #endif @@ -3354,117 +3347,85 @@ zend_bool s_unserialize_value (enum memcached_serializer serializer, int val_typ return 1; } -/* The caller MUST free the payload */ -static int php_memc_zval_from_payload(zval *value, const char *payload_in, size_t payload_len, uint32_t flags, enum memcached_serializer serializer) +static +zend_bool s_memcached_result_to_zval(memcached_result_st *result, zval *return_value) { - /* - A NULL payload is completely valid if length is 0, it is simply empty. - */ - int retval = 0; - zend_bool payload_emalloc = 0; - char *pl = NULL; + zend_string *data; + const char *payload; + size_t payload_len; + uint32_t flags; + zend_bool retval = 1; - if (payload_in == NULL && payload_len > 0) { - ZVAL_FALSE(value); - php_error_docref(NULL, E_WARNING, - "Could not handle non-existing value of length %zu", payload_len); - return -1; - } else if (payload_in == NULL) { - if (MEMC_VAL_GET_TYPE(flags) == MEMC_VAL_IS_BOOL) { - ZVAL_FALSE(value); - } else { - ZVAL_EMPTY_STRING(value); - } + payload = memcached_result_value(result); + payload_len = memcached_result_length(result); + flags = memcached_result_flags(result); + + if (!payload && payload_len > 0) { + php_error_docref(NULL, E_WARNING, "Could not handle non-existing value of length %zu", payload_len); return 0; } if (MEMC_VAL_HAS_FLAG(flags, MEMC_VAL_COMPRESSED)) { - char *datas = s_decompress_value (payload_in, &payload_len, flags); - if (!datas) { - ZVAL_FALSE(value); - return -1; + data = s_decompress_value (payload, payload_len, flags); + if (!data) { + return 0; } - pl = datas; - payload_emalloc = 1; } else { - pl = (char *) payload_in; + data = zend_string_init(payload, payload_len, 0); } switch (MEMC_VAL_GET_TYPE(flags)) { + case MEMC_VAL_IS_STRING: - ZVAL_STRINGL(value, pl, payload_len); + ZVAL_STR_COPY(return_value, data); break; case MEMC_VAL_IS_LONG: - { - long lval; - char conv_buf [128]; - - if (payload_len >= 128) { - php_error_docref(NULL, E_WARNING, "could not read long value, too big"); - retval = -1; - } - else { - memcpy (conv_buf, pl, payload_len); - conv_buf [payload_len] = '\0'; - - lval = strtol(conv_buf, NULL, 10); - ZVAL_LONG(value, lval); - } - } + ZVAL_LONG(return_value, strtol(data->val, NULL, 10)); break; case MEMC_VAL_IS_DOUBLE: { - char conv_buf [128]; - - if (payload_len >= 128) { - php_error_docref(NULL, E_WARNING, "could not read double value, too big"); - retval = -1; + if (payload_len == 8 && memcmp(data->val, "Infinity", 8) == 0) { + ZVAL_DOUBLE(return_value, php_get_inf()); + } + else if (payload_len == 9 && memcmp(data->val, "-Infinity", 9) == 0) { + ZVAL_DOUBLE(return_value, -php_get_inf()); + } + else if (payload_len == 3 && memcmp(data->val, "NaN", 3) == 0) { + ZVAL_DOUBLE(return_value, php_get_nan()); } else { - memcpy (conv_buf, pl, payload_len); - conv_buf [payload_len] = '\0'; - - if (payload_len == 8 && memcmp(conv_buf, "Infinity", 8) == 0) { - ZVAL_DOUBLE(value, php_get_inf()); - } else if (payload_len == 9 && memcmp(conv_buf, "-Infinity", 9) == 0) { - ZVAL_DOUBLE(value, -php_get_inf()); - } else if (payload_len == 3 && memcmp(conv_buf, "NaN", 3) == 0) { - ZVAL_DOUBLE(value, php_get_nan()); - } else { - ZVAL_DOUBLE(value, zend_strtod(conv_buf, NULL)); - } + ZVAL_DOUBLE(return_value, zend_strtod(data->val, NULL)); } } break; case MEMC_VAL_IS_BOOL: - ZVAL_BOOL(value, payload_len > 0 && pl[0] == '1'); + ZVAL_BOOL(return_value, payload_len > 0 && data->val[0] == '1'); break; case MEMC_VAL_IS_SERIALIZED: case MEMC_VAL_IS_IGBINARY: case MEMC_VAL_IS_JSON: case MEMC_VAL_IS_MSGPACK: - if (!s_unserialize_value (serializer, MEMC_VAL_GET_TYPE(flags), value, pl, payload_len)) { - retval = -1; - } + retval = s_unserialize_value (MEMC_VAL_GET_TYPE(flags), data, return_value); break; default: - ZVAL_FALSE(value); php_error_docref(NULL, E_WARNING, "unknown payload type"); - retval = -1; + retval = 0; break; } + zend_string_release(data); - if (payload_emalloc) { - efree(pl); + if (!retval) { + zval_ptr_dtor(return_value); } return retval; } + PHP_MEMCACHED_API zend_class_entry *php_memc_get_ce(void) { @@ -3508,8 +3469,7 @@ zend_class_entry *php_memc_get_exception_base(int root) static memcached_return php_memc_do_cache_callback(zval *zmemc_obj, zend_fcall_info *fci, zend_fcall_info_cache *fcc, zend_string *key, zval *value) { - char *payload = NULL; - size_t payload_len = 0; + zend_string *payload = NULL; zval params[4]; zval retval; zval z_key; @@ -3550,16 +3510,19 @@ static memcached_return php_memc_do_cache_callback(zval *zmemc_obj, zend_fcall_i time_t expir; expir = zval_get_long(expiration); + payload = s_zval_to_payload(value, &flags, m_obj->serializer, m_obj->compression_type); - payload = php_memc_zval_to_payload(value, &payload_len, &flags, m_obj->serializer, m_obj->compression_type); if (payload == NULL) { status = (memcached_return)MEMC_RES_PAYLOAD_FAILURE; } else { - rc = memcached_set(m_obj->memc, key->val, key->len, payload, payload_len, expir, flags); + if (m_obj->set_udf_flags >= 0) { + MEMC_VAL_SET_USER_FLAGS(flags, ((uint32_t) m_obj->set_udf_flags)); + } + rc = memcached_set(m_obj->memc, key->val, key->len, payload->val, payload->len, expir, flags); if (rc == MEMCACHED_SUCCESS || rc == MEMCACHED_BUFFERED) { status = rc; } - efree(payload); + zend_string_release(payload); } } else { status = MEMCACHED_NOTFOUND; @@ -3616,8 +3579,7 @@ static int php_memc_do_result_callback(zval *zmemc_obj, zend_fcall_info *fci, i_obj = Z_MEMC_OBJ_P(zmemc_obj); - if (php_memc_zval_from_payload(&value, payload, payload_len, flags, i_obj->obj->serializer) < 0) { - zval_ptr_dtor(&value); + if (!s_memcached_result_to_zval(result, &value)) { i_obj->rescode = MEMC_RES_PAYLOAD_FAILURE; return -1; } @@ -3734,27 +3696,23 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_get, 0, 0, 1) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, cache_cb) - ZEND_ARG_INFO(2, cas_token) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_getByKey, 0, 0, 2) ZEND_ARG_INFO(0, server_key) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, cache_cb) - ZEND_ARG_INFO(2, cas_token) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_getMulti, 0, 0, 1) ZEND_ARG_ARRAY_INFO(0, keys, 0) - ZEND_ARG_INFO(2, cas_tokens) - ZEND_ARG_INFO(0, flags) + ZEND_ARG_INFO(0, get_flags) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_getMultiByKey, 0, 0, 2) ZEND_ARG_INFO(0, server_key) ZEND_ARG_ARRAY_INFO(0, keys, 0) - ZEND_ARG_INFO(2, cas_tokens) - ZEND_ARG_INFO(0, flags) + ZEND_ARG_INFO(0, get_flags) ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO_EX(arginfo_getDelayed, 0, 0, 1) @@ -3811,9 +3769,6 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_setMultiByKey, 0, 0, 2) ZEND_ARG_INFO(0, expiration) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_INFO_EX(arginfo_getLastUserFlags, 0, 0, 0) -ZEND_END_ARG_INFO() - ZEND_BEGIN_ARG_INFO_EX(arginfo_add, 0, 0, 2) ZEND_ARG_INFO(0, key) ZEND_ARG_INFO(0, value) @@ -4041,8 +3996,6 @@ static zend_function_entry memcached_class_methods[] = { MEMC_ME(setMulti, arginfo_setMulti) MEMC_ME(setMultiByKey, arginfo_setMultiByKey) - MEMC_ME(getLastUserFlags, arginfo_getLastUserFlags) - MEMC_ME(cas, arginfo_cas) MEMC_ME(casByKey, arginfo_casByKey) MEMC_ME(add, arginfo_add) @@ -4136,35 +4089,36 @@ static PHP_GINIT_FUNCTION(php_memcached) { #ifdef HAVE_MEMCACHED_SESSION - php_memcached_globals->sess_locking_enabled = 1; - php_memcached_globals->sess_binary_enabled = 1; - php_memcached_globals->sess_consistent_hash_enabled = 0; - php_memcached_globals->sess_number_of_replicas = 0; - php_memcached_globals->sess_remove_failed_enabled = 0; - php_memcached_globals->sess_prefix = NULL; - php_memcached_globals->sess_lock_wait = 0; - php_memcached_globals->sess_lock_max_wait = 0; - php_memcached_globals->sess_lock_expire = 0; - php_memcached_globals->sess_locked = 0; - php_memcached_globals->sess_lock_key = NULL; - php_memcached_globals->sess_lock_key_len = 0; - php_memcached_globals->sess_randomize_replica_read = 0; - php_memcached_globals->sess_connect_timeout = 1000; + + php_memcached_globals->session_ini.lock_enabled = 0; + php_memcached_globals->session_ini.lock_wait_max = 2000; + php_memcached_globals->session_ini.lock_wait_min = 1000; + php_memcached_globals->session_ini.lock_retries = 5; + php_memcached_globals->session_ini.lock_expiration = 30; + php_memcached_globals->session_ini.compression_enabled = 1; + php_memcached_globals->session_ini.binary_protocol_enabled = 1; + php_memcached_globals->session_ini.consistent_hash_enabled = 1; + php_memcached_globals->session_ini.number_of_replicas = 0; + php_memcached_globals->session_ini.server_failure_limit = 1; + php_memcached_globals->session_ini.randomize_replica_read_enabled = 1; + php_memcached_globals->session_ini.remove_failed_servers_enabled = 1; + php_memcached_globals->session_ini.connect_timeout = 1000; + php_memcached_globals->session_ini.prefix = NULL; + php_memcached_globals->session_ini.persistent_enabled = 0; + php_memcached_globals->session_ini.sasl_username = NULL; + php_memcached_globals->session_ini.sasl_password = NULL; + +#endif + php_memcached_globals->memc_ini.serializer_name = NULL; + php_memcached_globals->memc_ini.serializer = SERIALIZER_DEFAULT; + php_memcached_globals->memc_ini.compression_type = NULL; + php_memcached_globals->memc_ini.compression_threshold = 2000; + php_memcached_globals->memc_ini.compression_type_real = COMPRESSION_TYPE_FASTLZ; + php_memcached_globals->memc_ini.compression_factor = 1.30; + php_memcached_globals->memc_ini.store_retry_count = 2; #if HAVE_MEMCACHED_SASL - php_memcached_globals->sess_sasl_username = NULL; - php_memcached_globals->sess_sasl_password = NULL; - php_memcached_globals->sess_sasl_data = 0; + php_memcached_globals->memc_ini.sasl_enabled = 0; #endif -#endif - php_memcached_globals->serializer_name = NULL; - php_memcached_globals->serializer = SERIALIZER_DEFAULT; - php_memcached_globals->compression_type = NULL; - php_memcached_globals->compression_type_real = COMPRESSION_TYPE_FASTLZ; - php_memcached_globals->compression_factor = 1.30; -#if HAVE_MEMCACHED_SASL - php_memcached_globals->use_sasl = 0; -#endif - php_memcached_globals->store_retry_count = 2; } zend_module_entry memcached_module_entry = { @@ -4374,6 +4328,7 @@ static void php_memc_register_constants(INIT_FUNC_ARGS) * Flags. */ REGISTER_MEMC_CLASS_CONST_LONG(GET_PRESERVE_ORDER, MEMC_GET_PRESERVE_ORDER); + REGISTER_MEMC_CLASS_CONST_LONG(GET_EXTENDED, MEMC_GET_EXTENDED); #ifdef HAVE_MEMCACHED_PROTOCOL /* @@ -4422,11 +4377,6 @@ static void php_memc_register_constants(INIT_FUNC_ARGS) } /* }}} */ -int php_memc_sess_list_entry(void) -{ - return le_memc_sess; -} - /* {{{ PHP_MINIT_FUNCTION */ PHP_MINIT_FUNCTION(memcached) { @@ -4438,7 +4388,6 @@ PHP_MINIT_FUNCTION(memcached) memcached_object_handlers.free_obj = php_memc_free_storage; le_memc = zend_register_list_destructors_ex(NULL, php_memc_dtor, "Memcached persistent connection", module_number); - le_memc_sess = zend_register_list_destructors_ex(NULL, php_memc_sess_dtor, "Memcached Sessions persistent connection", module_number); INIT_CLASS_ENTRY(ce, "Memcached", memcached_class_methods); memcached_ce = zend_register_internal_class(&ce); @@ -4462,12 +4411,12 @@ PHP_MINIT_FUNCTION(memcached) php_memc_register_constants(INIT_FUNC_ARGS_PASSTHRU); #ifdef HAVE_MEMCACHED_SESSION - php_session_register_module(ps_memcached_ptr); + php_memc_session_minit(module_number); #endif REGISTER_INI_ENTRIES(); #if HAVE_MEMCACHED_SASL - if (MEMC_G(use_sasl)) { + if (MEMC_G(sasl_enabled)) { if (sasl_client_init(NULL) != SASL_OK) { php_error_docref(NULL, E_ERROR, "Failed to initialize SASL library"); return FAILURE; @@ -4482,7 +4431,7 @@ PHP_MINIT_FUNCTION(memcached) PHP_MSHUTDOWN_FUNCTION(memcached) { #if HAVE_MEMCACHED_SASL - if (MEMC_G(use_sasl)) { + if (MEMC_G(sasl_enabled)) { sasl_done(); } #endif diff --git a/php_memcached_private.h b/php_memcached_private.h index ffca963..46565e7 100644 --- a/php_memcached_private.h +++ b/php_memcached_private.h @@ -129,46 +129,60 @@ typedef struct { #endif ZEND_BEGIN_MODULE_GLOBALS(php_memcached) + #ifdef HAVE_MEMCACHED_SESSION - zend_bool sess_locking_enabled; - long sess_lock_wait; - long sess_lock_max_wait; - long sess_lock_expire; - char* sess_prefix; - zend_bool sess_locked; - char* sess_lock_key; - int sess_lock_key_len; + /* Session related variables */ + struct { + zend_bool lock_enabled; + zend_long lock_wait_max; + zend_long lock_wait_min; + zend_long lock_retries; + zend_long lock_expiration; - int sess_number_of_replicas; - zend_bool sess_randomize_replica_read; - zend_bool sess_remove_failed_enabled; - long sess_connect_timeout; - zend_bool sess_consistent_hash_enabled; - zend_bool sess_binary_enabled; + zend_bool compression_enabled; + zend_bool binary_protocol_enabled; + zend_bool consistent_hash_enabled; + + zend_long server_failure_limit; + zend_long number_of_replicas; + zend_bool randomize_replica_read_enabled; + zend_bool remove_failed_servers_enabled; + + zend_long connect_timeout; + + char *prefix; + zend_bool persistent_enabled; + + char *sasl_username; + char *sasl_password; + } session_ini; + +#endif + + struct { + char *serializer_name; + char *compression_type; + zend_long compression_threshold; + double compression_factor; + zend_long store_retry_count; #if HAVE_MEMCACHED_SASL - char *sess_sasl_username; - char *sess_sasl_password; - zend_bool sess_sasl_data; + zend_bool sasl_enabled; #endif -#endif - char *serializer_name; - enum memcached_serializer serializer; - char *compression_type; - int compression_type_real; - int compression_threshold; + /* Converted values*/ + enum memcached_serializer serializer; + zend_long compression_type_real; + } memc_ini; + + - double compression_factor; -#if HAVE_MEMCACHED_SASL - zend_bool use_sasl; -#endif #ifdef HAVE_MEMCACHED_PROTOCOL struct { php_memc_server_cb_t callbacks [MEMC_SERVER_ON_MAX]; } server; #endif - long store_retry_count; + ZEND_END_MODULE_GLOBALS(php_memcached) PHP_RINIT_FUNCTION(memcached); @@ -177,19 +191,6 @@ PHP_MINIT_FUNCTION(memcached); PHP_MSHUTDOWN_FUNCTION(memcached); PHP_MINFO_FUNCTION(memcached); -#ifdef ZTS -#define MEMC_G(v) TSRMG(php_memcached_globals_id, zend_php_memcached_globals *, v) -#else -#define MEMC_G(v) (php_memcached_globals.v) -#endif - -typedef struct { - memcached_st *memc_sess; - zend_bool is_persistent; -} memcached_sess; - -int php_memc_sess_list_entry(void); - char *php_memc_printable_func (zend_fcall_info *fci, zend_fcall_info_cache *fci_cache TSRMLS_DC); memcached_return php_memcached_exist (memcached_st *memc, zend_string *key); diff --git a/php_memcached_session.c b/php_memcached_session.c index 85baad0..8c6454a 100644 --- a/php_memcached_session.c +++ b/php_memcached_session.c @@ -18,6 +18,8 @@ #include "php_memcached_private.h" #include "php_memcached_session.h" +#include "Zend/zend_smart_str_public.h" + extern ZEND_DECLARE_MODULE_GLOBALS(php_memcached) #define MEMC_SESS_DEFAULT_LOCK_WAIT 150000 @@ -27,278 +29,350 @@ ps_module ps_mod_memcached = { PS_MOD_UPDATE_TIMESTAMP(memcached) }; -static int php_memc_sess_lock(memcached_st *memc, const char *key) +typedef struct { + zend_bool is_persistent; + zend_bool has_sasl_data; + zend_bool is_locked; + zend_string *lock_key; +} php_memcached_user_data; + +#ifndef MIN +# define MIN(a,b) (((a)<(b))?(a):(b)) +#endif + +#ifndef MAX +# define MAX(a,b) (((a)>(b))?(a):(b)) +#endif + +#ifdef ZTS +#define MEMC_SESS_INI(v) TSRMG(php_memcached_globals_id, zend_php_memcached_globals *, session_ini.v) +#else +#define MEMC_SESS_INI(v) (php_memcached_globals.session_ini.v) +#endif + +#define MEMC_SESS_STR_INI(vv) ((MEMC_SESS_INI(vv) && *MEMC_SESS_INI(vv)) ? MEMC_SESS_INI(vv) : NULL) + +static + int le_memc_sess; + +static +int s_memc_sess_list_entry(void) { - char *lock_key = NULL; - int lock_key_len = 0; - unsigned long attempts; - long write_retry_attempts = 0; - long lock_maxwait = MEMC_G(sess_lock_max_wait); - long lock_wait = MEMC_G(sess_lock_wait); - long lock_expire = MEMC_G(sess_lock_expire); - time_t expiration; - memcached_return status; - /* set max timeout for session_start = max_execution_time. (c) Andrei Darashenka, Richter & Poweleit GmbH */ - if (lock_maxwait <= 0) { - lock_maxwait = zend_ini_long(ZEND_STRS("max_execution_time"), 0); - if (lock_maxwait <= 0) { - lock_maxwait = MEMC_SESS_LOCK_EXPIRATION; - } - } - if (lock_wait == 0) { - lock_wait = MEMC_SESS_DEFAULT_LOCK_WAIT; - } - if (lock_expire <= 0) { - lock_expire = lock_maxwait; - } - expiration = lock_expire + 1; - attempts = (unsigned long)((1000000.0 / lock_wait) * lock_maxwait); - - /* Set the number of write retry attempts to the number of replicas times the number of attempts to remove a server */ - if (MEMC_G(sess_remove_failed_enabled)) { - write_retry_attempts = MEMC_G(sess_number_of_replicas) * ( memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT) + 1); - } - - lock_key_len = spprintf(&lock_key, 0, "lock.%s", key); - do { - status = memcached_add(memc, lock_key, lock_key_len, "1", sizeof("1")-1, expiration, 0); - if (status == MEMCACHED_SUCCESS) { - MEMC_G(sess_locked) = 1; - MEMC_G(sess_lock_key) = lock_key; - MEMC_G(sess_lock_key_len) = lock_key_len; - return 0; - } else if (status != MEMCACHED_NOTSTORED && status != MEMCACHED_DATA_EXISTS) { - if (write_retry_attempts > 0) { - write_retry_attempts--; - continue; - } - php_error_docref(NULL, E_WARNING, "Write of lock failed"); - break; - } - - if (lock_wait > 0) { - usleep(lock_wait); - } - } while(--attempts > 0); - - efree(lock_key); - return -1; + return le_memc_sess; } -static void php_memc_sess_unlock(memcached_st *memc) +static +void s_destroy_mod_data(memcached_st *memc) { - if (MEMC_G(sess_locked)) { - memcached_delete(memc, MEMC_G(sess_lock_key), MEMC_G(sess_lock_key_len), 0); - MEMC_G(sess_locked) = 0; - efree(MEMC_G(sess_lock_key)); - MEMC_G(sess_lock_key_len) = 0; + php_memcached_user_data *user_data = memcached_get_user_data(memc); + + if (user_data->has_sasl_data) { + memcached_destroy_sasl_auth_data(memc); } + + memcached_free(memc); + pefree(memc, user_data->is_persistent); + pefree(user_data, user_data->is_persistent); +} + +ZEND_RSRC_DTOR_FUNC(php_memc_sess_dtor) +{ + if (res->ptr) { + s_destroy_mod_data((memcached_st *) res->ptr); + res->ptr = NULL; + } +} + +int php_memc_session_minit(int module_number) +{ + le_memc_sess = + zend_register_list_destructors_ex(NULL, php_memc_sess_dtor, "Memcached Sessions persistent connection", module_number); + + php_session_register_module(ps_memcached_ptr); + return SUCCESS; +} + +static +time_t s_lock_expiration() +{ + zend_long max_execution_time; + + if (MEMC_SESS_INI(lock_expiration) > 0) { + return time(NULL) + MEMC_SESS_INI(lock_expiration); + } + else { + zend_long max_execution_time = zend_ini_long(ZEND_STRS("max_execution_time"), 0); + if (max_execution_time > 0) { + return time(NULL) + max_execution_time; + } + } + return 0; +} + +static +zend_bool s_lock_session(memcached_st *memc, zend_string *sid) +{ + char *lock_key; + size_t lock_key_len; + time_t expiration; + zend_long wait_time, retries; + php_memcached_user_data *user_data = memcached_get_user_data(memc); + + lock_key_len = spprintf(&lock_key, 0, "lock.%s", sid->val); + expiration = s_lock_expiration(); + + wait_time = MEMC_SESS_INI(lock_wait_min); + retries = MEMC_SESS_INI(lock_retries); + + do { + memcached_return rc = + memcached_add(memc, lock_key, lock_key_len, "1", sizeof ("1") - 1, expiration, 0); + + switch (rc) { + + case MEMCACHED_SUCCESS: + user_data->lock_key = zend_string_init(lock_key, lock_key_len, user_data->is_persistent); + user_data->is_locked = 1; + break; + + case MEMCACHED_NOTSTORED: + case MEMCACHED_DATA_EXISTS: + usleep(wait_time * 1000); + wait_time = MIN(MEMC_SESS_INI(lock_wait_max), wait_time * 2); + break; + + default: + php_error_docref(NULL, E_WARNING, "Failed to write session lock: %s", memcached_strerror (memc, rc)); + break; + } + } while (!user_data->is_locked && retries-- > 0); + + efree(lock_key); + return user_data->is_locked; +} + +static +void s_unlock_session(memcached_st *memc) +{ + php_memcached_user_data *user_data = memcached_get_user_data(memc); + + if (user_data->is_locked) { + memcached_delete(memc, user_data->lock_key->val, user_data->lock_key->len, 0); + user_data->is_locked = 0; + zend_string_release (user_data->lock_key); + } +} + +static +zend_bool s_configure_from_ini_values(memcached_st *memc) +{ + memcached_return rc; + +#define check_set_behavior(behavior, value) \ + if ((rc = memcached_behavior_set(memc, (behavior), (value))) != MEMCACHED_SUCCESS) { \ + php_error_docref(NULL, E_WARNING, "failed to initialise session memcached configuration: %s", memcached_strerror(memc, rc)); \ + return 0; \ + } + + if (MEMC_SESS_INI(binary_protocol_enabled)) { + check_set_behavior(MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, 1); + } + + if (MEMC_SESS_INI(consistent_hash_enabled)) { + check_set_behavior(MEMCACHED_BEHAVIOR_KETAMA, 1); + } + + if (MEMC_SESS_INI(server_failure_limit)) { + check_set_behavior(MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT, MEMC_SESS_INI(server_failure_limit)); + } + + if (MEMC_SESS_INI(number_of_replicas)) { + check_set_behavior(MEMCACHED_BEHAVIOR_NUMBER_OF_REPLICAS, MEMC_SESS_INI(number_of_replicas)); + } + + if (MEMC_SESS_INI(randomize_replica_read_enabled)) { + check_set_behavior(MEMCACHED_BEHAVIOR_RANDOMIZE_REPLICA_READ, 1); + } + + if (MEMC_SESS_INI(remove_failed_servers_enabled)) { + check_set_behavior(MEMCACHED_BEHAVIOR_REMOVE_FAILED_SERVERS, 1); + } + + if (MEMC_SESS_INI(connect_timeout)) { + check_set_behavior(MEMCACHED_BEHAVIOR_CONNECT_TIMEOUT, MEMC_SESS_INI(connect_timeout)); + } + + if (MEMC_SESS_STR_INI(prefix)) { + memcached_callback_set(memc, MEMCACHED_CALLBACK_NAMESPACE, MEMC_SESS_STR_INI(prefix)); + } + +#ifdef HAVE_MEMCACHED_SASL + if (MEMC_SESS_STR_INI(sasl_username) && MEMC_SESS_STR_INI(sasl_password)) { + php_memcached_user_data *user_data; + + check_set_behavior(MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, 1); + + if (memcached_set_sasl_auth_data(memc, MEMC_SESS_STR_INI(sasl_username), MEMC_SESS_STR_INI(sasl_password)) == MEMCACHED_FAILURE) { + php_error_docref(NULL, E_WARNING, "failed to set memcached session sasl credentials"); + return 0; + } + user_data = memcached_get_user_data(memc); + user_data->has_sasl_data = 1; + } +#endif + +#undef safe_set_behavior + + return 1; +} + +static +void *s_pemalloc_fn(const memcached_st *memc, size_t size, void *context) +{ + zend_bool *is_persistent = memcached_get_user_data(memc); + + return + pemalloc(size, *is_persistent); +} + +static +void s_pefree_fn(const memcached_st *memc, void *mem, void *context) +{ + zend_bool *is_persistent = memcached_get_user_data(memc); + + return + pefree(mem, *is_persistent); +} + +static +void *s_perealloc_fn(const memcached_st *memc, void *mem, const size_t size, void *context) +{ + zend_bool *is_persistent = memcached_get_user_data(memc); + + return + perealloc(mem, size, *is_persistent); +} + +static +void *s_pecalloc_fn(const memcached_st *memc, size_t nelem, const size_t elsize, void *context) +{ + zend_bool *is_persistent = memcached_get_user_data(memc); + + return + pecalloc(nelem, elsize, *is_persistent); +} + + +static +memcached_st *s_init_mod_data (const memcached_server_list_st servers, zend_bool is_persistent) +{ + void *buffer; + php_memcached_user_data *user_data; + memcached_st *memc; + + buffer = pecalloc(1, sizeof(memcached_st), is_persistent); + memc = memcached_create (buffer); + + if (!memc) { + php_error_docref(NULL, E_ERROR, "failed to allocate memcached structure"); + /* not reached */ + } + + memcached_set_memory_allocators(memc, s_pemalloc_fn, s_pefree_fn, s_perealloc_fn, s_pecalloc_fn, NULL); + + user_data = pecalloc(1, sizeof(php_memcached_user_data), is_persistent); + user_data->is_persistent = is_persistent; + user_data->has_sasl_data = 0; + user_data->lock_key = NULL; + user_data->is_locked = 0; + + memcached_set_user_data(memc, user_data); + memcached_server_push (memc, servers); + return memc; } PS_OPEN_FUNC(memcached) { - memcached_sess *memc_sess = PS_GET_MOD_DATA(); - memcached_return status; - char *p, *plist_key = NULL; - int plist_key_len = 0; + memcached_st *memc = NULL; + char *plist_key = NULL; + size_t plist_key_len = 0; + memcached_server_list_st servers; - if (!strncmp((char *)save_path, "PERSISTENT=", sizeof("PERSISTENT=") - 1)) { - zend_resource *le = NULL; - zval *le_z = NULL; - char *e; + // First parse servers + servers = memcached_servers_parse(save_path); - p = (char *)save_path + sizeof("PERSISTENT=") - 1; - if (!*p) { -error: - php_error_docref(NULL, E_WARNING, "Invalid persistent id for session storage"); - return FAILURE; - } - if ((e = strchr(p, ' '))) { - plist_key_len = spprintf(&plist_key, 0, "memcached_sessions:id=%.*s", (int)(e - p), p); - } else { - goto error; - } - plist_key_len++; - - if ((le_z = zend_hash_str_find(&EG(persistent_list), plist_key, plist_key_len)) != NULL) { - le = Z_RES_P(le_z); - if (le->type == php_memc_sess_list_entry()) { - memc_sess = (memcached_sess *) le->ptr; - PS_SET_MOD_DATA(memc_sess); - return SUCCESS; - } - } - p = e + 1; - memc_sess = pecalloc(sizeof(*memc_sess), 1, 1); - memc_sess->is_persistent = 1; - } else { - p = (char *)save_path; - memc_sess = ecalloc(sizeof(*memc_sess), 1); - memc_sess->is_persistent = 0; + if (!servers) { + php_error_docref(NULL, E_WARNING, "failed to parse session.save_path"); + PS_SET_MOD_DATA(NULL); + return FAILURE; } - if (!strstr(p, "--SERVER")) { - memcached_server_st *servers = memcached_servers_parse(p); - if (servers) { - memc_sess->memc_sess = memcached_create(NULL); - if (memc_sess->memc_sess) { - if (MEMC_G(sess_consistent_hash_enabled)) { - if (memcached_behavior_set(memc_sess->memc_sess, MEMCACHED_BEHAVIOR_KETAMA_WEIGHTED, (uint64_t) 1) == MEMCACHED_FAILURE) { - PS_SET_MOD_DATA(NULL); - if (plist_key) { - efree(plist_key); - } - memcached_free(memc_sess->memc_sess); - php_error_docref(NULL, E_WARNING, "failed to enable memcached consistent hashing"); - return FAILURE; - } + if (MEMC_SESS_INI(persistent_enabled)) { + zend_resource *le_p; + + plist_key_len = spprintf(&plist_key, 0, "memc-session:%s", save_path); + + if ((le_p = zend_hash_str_find_ptr(&EG(persistent_list), plist_key, plist_key_len)) != NULL) { + if (le_p->type == s_memc_sess_list_entry()) { + memc = (memcached_st *) le_p->ptr; + + if (!s_configure_from_ini_values(memc)) { + // Remove existing plist entry + zend_hash_str_del(&EG(persistent_list), plist_key, plist_key_len); + memc = NULL; } - - status = memcached_server_push(memc_sess->memc_sess, servers); - memcached_server_list_free(servers); - - if (MEMC_G(sess_prefix) && MEMC_G(sess_prefix)[0] != 0 && memcached_callback_set(memc_sess->memc_sess, MEMCACHED_CALLBACK_PREFIX_KEY, MEMC_G(sess_prefix)) != MEMCACHED_SUCCESS) { - PS_SET_MOD_DATA(NULL); - if (plist_key) { - efree(plist_key); - } - memcached_free(memc_sess->memc_sess); - php_error_docref(NULL, E_WARNING, "bad memcached key prefix in memcached.sess_prefix"); - return FAILURE; - } - - if (status == MEMCACHED_SUCCESS) { - goto success; - } - } else { - memcached_server_list_free(servers); - php_error_docref(NULL, E_WARNING, "could not allocate libmemcached structure"); - } - } else { - php_error_docref(NULL, E_WARNING, "failed to parse session.save_path"); - } - } else { - memc_sess->memc_sess = php_memc_create_str(p, strlen(p)); - if (!memc_sess->memc_sess) { -#ifdef HAVE_LIBMEMCACHED_CHECK_CONFIGURATION - char error_buffer[1024]; - if (libmemcached_check_configuration(p, strlen(p), error_buffer, sizeof(error_buffer)) != MEMCACHED_SUCCESS) { - php_error_docref(NULL, E_WARNING, "session.save_path configuration error %s", error_buffer); - } else { - php_error_docref(NULL, E_WARNING, "failed to initialize memcached session storage"); - } -#else - php_error_docref(NULL, E_WARNING, "failed to initialize memcached session storage"); -#endif - - } else { -success: - PS_SET_MOD_DATA(memc_sess); - - if (plist_key) { - zend_resource le; - zend_string *tmp_key; - - le.type = php_memc_sess_list_entry(); - le.ptr = memc_sess; - - tmp_key = zend_string_init(plist_key, plist_key_len, 0); - if (zend_hash_update_mem(&EG(persistent_list), tmp_key, (void *)&le, sizeof(le)) == NULL) { - zend_string_release(tmp_key); + else { efree(plist_key); - php_error_docref(NULL, E_ERROR, "could not register persistent entry"); - } - zend_string_release(tmp_key); - efree(plist_key); - } - - if (MEMC_G(sess_binary_enabled)) { - if (memcached_behavior_set(memc_sess->memc_sess, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, (uint64_t) 1) == MEMCACHED_FAILURE) { - php_error_docref(NULL, E_WARNING, "failed to set memcached session binary protocol"); - return FAILURE; + PS_SET_MOD_DATA(memc); + return SUCCESS; } } -#ifdef HAVE_MEMCACHED_SASL - if (MEMC_G(use_sasl)) { - /* - * Enable SASL support if username and password are set - * - */ - if (MEMC_G(sess_sasl_username) && MEMC_G(sess_sasl_password)) { - /* Force binary protocol */ - if (memcached_behavior_set(memc_sess->memc_sess, MEMCACHED_BEHAVIOR_BINARY_PROTOCOL, (uint64_t) 1) == MEMCACHED_FAILURE) { - php_error_docref(NULL, E_WARNING, "failed to set memcached session binary protocol"); - return FAILURE; - } - if (memcached_set_sasl_auth_data(memc_sess->memc_sess, MEMC_G(sess_sasl_username), MEMC_G(sess_sasl_password)) == MEMCACHED_FAILURE) { - php_error_docref(NULL, E_WARNING, "failed to set memcached session sasl credentials"); - return FAILURE; - } - MEMC_G(sess_sasl_data) = 1; - } - } - - -#endif - if (MEMC_G(sess_number_of_replicas) > 0) { - if (memcached_behavior_set(memc_sess->memc_sess, MEMCACHED_BEHAVIOR_NUMBER_OF_REPLICAS, (uint64_t) MEMC_G(sess_number_of_replicas)) == MEMCACHED_FAILURE) { - php_error_docref(NULL, E_WARNING, "failed to set memcached session number of replicas"); - return FAILURE; - } - if (memcached_behavior_set(memc_sess->memc_sess, MEMCACHED_BEHAVIOR_RANDOMIZE_REPLICA_READ, (uint64_t) MEMC_G(sess_randomize_replica_read)) == MEMCACHED_FAILURE) { - php_error_docref(NULL, E_WARNING, "failed to set memcached session randomize replica read"); - } - } - - if (memcached_behavior_set(memc_sess->memc_sess, MEMCACHED_BEHAVIOR_CONNECT_TIMEOUT, (uint64_t) MEMC_G(sess_connect_timeout)) == MEMCACHED_FAILURE) { - php_error_docref(NULL, E_WARNING, "failed to set memcached connection timeout"); - return FAILURE; - } -#ifdef HAVE_MEMCACHED_BEHAVIOR_REMOVE_FAILED_SERVERS - /* Allow libmemcached remove failed servers */ - if (MEMC_G(sess_remove_failed_enabled)) { - if (memcached_behavior_set(memc_sess->memc_sess, MEMCACHED_BEHAVIOR_REMOVE_FAILED_SERVERS, (uint64_t) 1) == MEMCACHED_FAILURE) { - php_error_docref(NULL, E_WARNING, "failed to set: remove failed servers"); - return FAILURE; - } - } -#endif - return SUCCESS; } } + memc = s_init_mod_data(servers, MEMC_SESS_INI(persistent_enabled)); + memcached_server_list_free(servers); + if (plist_key) { + zend_resource le; + + le.type = s_memc_sess_list_entry(); + le.ptr = memc; + + GC_REFCOUNT(&le) = 1; + + /* plist_key is not a persistent allocated key, thus we use str_update here */ + if (zend_hash_str_update_mem(&EG(persistent_list), plist_key, plist_key_len, &le, sizeof(le)) == NULL) { + php_error_docref(NULL, E_ERROR, "Could not register persistent entry for the memcached session"); + /* not reached */ + } efree(plist_key); } - PS_SET_MOD_DATA(NULL); - return FAILURE; + PS_SET_MOD_DATA(memc); + return SUCCESS; } PS_CLOSE_FUNC(memcached) { - memcached_sess *memc_sess = PS_GET_MOD_DATA(); + memcached_st *memc = PS_GET_MOD_DATA(); + php_memcached_user_data *user_data; - if (!memc_sess) { + if (!memc) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Session is not allocated, check session.save_path value"); return FAILURE; } - if (MEMC_G(sess_locking_enabled)) { - php_memc_sess_unlock(memc_sess->memc_sess); - } - if (memc_sess->memc_sess) { - if (!memc_sess->is_persistent) { -#ifdef HAVE_MEMCACHED_SASL - if (MEMC_G(sess_sasl_data)) { - memcached_destroy_sasl_auth_data(memc_sess->memc_sess); - } -#endif - memcached_free(memc_sess->memc_sess); - efree(memc_sess); - } - PS_SET_MOD_DATA(NULL); + user_data = memcached_get_user_data(memc); + + if (user_data->is_locked) { + s_unlock_session(memc); } + if (!user_data->is_persistent) { + s_destroy_mod_data(memc); + } + + PS_SET_MOD_DATA(NULL); return SUCCESS; } @@ -306,35 +380,27 @@ PS_READ_FUNC(memcached) { char *payload = NULL; size_t payload_len = 0; - int key_len = key->len; uint32_t flags = 0; memcached_return status; - memcached_sess *memc_sess = PS_GET_MOD_DATA(); - size_t key_length; + memcached_st *memc = PS_GET_MOD_DATA(); - if (!memc_sess) { + if (!memc) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Session is not allocated, check session.save_path value"); return FAILURE; } - key_length = strlen(MEMC_G(sess_prefix)) + key_len + 5; // prefix + "lock." - if (!key_length || key_length >= MEMCACHED_MAX_KEY) { - php_error_docref(NULL, E_WARNING, "The session id is too long or contains illegal characters"); - return FAILURE; - } - - if (MEMC_G(sess_locking_enabled)) { - if (php_memc_sess_lock(memc_sess->memc_sess, key->val) < 0) { + if (MEMC_SESS_INI(lock_enabled)) { + if (!s_lock_session(memc, key)) { php_error_docref(NULL, E_WARNING, "Unable to clear session lock record"); return FAILURE; } } - payload = memcached_get(memc_sess->memc_sess, key->val, key_len, &payload_len, &flags, &status); + payload = memcached_get(memc, key->val, key->len, &payload_len, &flags, &status); if (status == MEMCACHED_SUCCESS) { - *val = zend_string_init(payload, payload_len, 1); - free(payload); + *val = zend_string_init(payload, payload_len, 0); + efree(payload); return SUCCESS; } else if (status == MEMCACHED_NOTFOUND) { *val = ZSTR_EMPTY_ALLOC(); @@ -346,59 +412,53 @@ PS_READ_FUNC(memcached) PS_WRITE_FUNC(memcached) { - int key_len = key->len; - time_t expiration = 0; - long write_try_attempts = 1; - memcached_return status; - memcached_sess *memc_sess = PS_GET_MOD_DATA(); + zend_long retries = 1; + memcached_st *memc = PS_GET_MOD_DATA(); size_t key_length; + time_t expiration; - if (!memc_sess) { + if (!memc) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Session is not allocated, check session.save_path value"); return FAILURE; } - key_length = strlen(MEMC_G(sess_prefix)) + key_len + 5; // prefix + "lock." - if (!key_length || key_length >= MEMCACHED_MAX_KEY) { - php_error_docref(NULL, E_WARNING, "The session id is too long or contains illegal characters"); - return FAILURE; - } - - if (maxlifetime > 0) { - expiration = maxlifetime; - } - /* Set the number of write retry attempts to the number of replicas times the number of attempts to remove a server plus the initial write */ - if (MEMC_G(sess_remove_failed_enabled)) { - write_try_attempts = 1 + MEMC_G(sess_number_of_replicas) * ( memcached_behavior_get(memc_sess->memc_sess, MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT) + 1); + if (MEMC_SESS_INI(remove_failed_servers_enabled)) { + zend_long replicas, failure_limit; + + replicas = memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_NUMBER_OF_REPLICAS); + failure_limit = memcached_behavior_get(memc, MEMCACHED_BEHAVIOR_SERVER_FAILURE_LIMIT); + + retries = 1 + replicas * (failure_limit + 1); } + expiration = time(NULL) + maxlifetime; + do { - status = memcached_set(memc_sess->memc_sess, key->val, key_len, val->val, val->len, expiration, 0); - if (status == MEMCACHED_SUCCESS) { + if (memcached_set(memc, key->val, key->len, val->val, val->len, expiration, 0) == MEMCACHED_SUCCESS) { return SUCCESS; - } else { - write_try_attempts--; } - } while (write_try_attempts > 0); + } while (--retries > 0); return FAILURE; } PS_DESTROY_FUNC(memcached) { - memcached_sess *memc_sess = PS_GET_MOD_DATA(); + php_memcached_user_data *user_data; + memcached_st *memc = PS_GET_MOD_DATA(); - if (!memc_sess) { + if (!memc) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Session is not allocated, check session.save_path value"); return FAILURE; } - memcached_delete(memc_sess->memc_sess, key->val, key->len, 0); - if (MEMC_G(sess_locking_enabled)) { - php_memc_sess_unlock(memc_sess->memc_sess); - } + memcached_delete(memc, key->val, key->len, 0); + user_data = memcached_get_user_data(memc); + if (user_data->is_locked) { + s_unlock_session(memc); + } return SUCCESS; } @@ -410,34 +470,31 @@ PS_GC_FUNC(memcached) PS_CREATE_SID_FUNC(memcached) { zend_string *sid; - int retries = 3; - memcached_sess *memc_sess = PS_GET_MOD_DATA(); - time_t expiration = PS(gc_maxlifetime); + memcached_st *memc = PS_GET_MOD_DATA(); - if (!memc_sess) { - return NULL; + if (!memc) { + sid = php_session_create_id(NULL); } + else { + int retries = 3; + while (retries-- > 0) { + sid = php_session_create_id((void **) &memc); - while (retries-- > 0) { - sid = php_session_create_id((void**)&memc_sess); - - if (sid) { - if (memcached_add(memc_sess->memc_sess, sid->val, sid->len, "0", 0, expiration, 0) == MEMCACHED_SUCCESS) { - return sid; - } - else { - zend_string_release(sid); + if (memcached_add (memc, sid->val, sid->len, NULL, 0, s_lock_expiration(), 0) == MEMCACHED_SUCCESS) { + break; } + zend_string_release(sid); + sid = NULL; } } - return NULL; + return sid; } PS_VALIDATE_SID_FUNC(memcached) { - memcached_sess *memc_sess = PS_GET_MOD_DATA(); + memcached_st *memc = PS_GET_MOD_DATA(); - if (php_memcached_exist(memc_sess->memc_sess, key) == MEMCACHED_SUCCESS) { + if (php_memcached_exist(memc, key) == MEMCACHED_SUCCESS) { return SUCCESS; } else { return FAILURE; @@ -446,13 +503,10 @@ PS_VALIDATE_SID_FUNC(memcached) PS_UPDATE_TIMESTAMP_FUNC(memcached) { - memcached_sess *memc_sess = PS_GET_MOD_DATA(); - time_t expiration = 0; + memcached_st *memc = PS_GET_MOD_DATA(); + time_t expiration = time(NULL) + maxlifetime; - if (maxlifetime > 0) { - expiration = maxlifetime; - } - if (memcached_touch(memc_sess->memc_sess, key->val, key->len, expiration) == MEMCACHED_FAILURE) { + if (memcached_touch(memc, key->val, key->len, expiration) == MEMCACHED_FAILURE) { return FAILURE; } return SUCCESS; diff --git a/php_memcached_session.h b/php_memcached_session.h index 4118362..13ce363 100644 --- a/php_memcached_session.h +++ b/php_memcached_session.h @@ -36,4 +36,7 @@ PS_CREATE_SID_FUNC(memcached); PS_VALIDATE_SID_FUNC(memcached); PS_UPDATE_TIMESTAMP_FUNC(memcached); +/* Called from php_memcached.c */ +int php_memc_session_minit(int module_number); + #endif /* PHP_MEMCACHED_SESSION_H */ diff --git a/tests/cachecallback.phpt b/tests/cachecallback.phpt index 9fe8f4f..31a736b 100644 --- a/tests/cachecallback.phpt +++ b/tests/cachecallback.phpt @@ -13,6 +13,10 @@ $first_key = uniqid ('cache_test_'); $second_key = uniqid ('cache_test_'); $third_key = uniqid ('cache_test_'); +$m->delete($first_key); +$m->delete($second_key); +$m->delete($third_key); + var_dump ( $m->get ($first_key, function (Memcached $memc, $key, &$value, &$expiration) { $value = "hello"; diff --git a/tests/cas.phpt b/tests/cas.phpt index e999079..c1c1752 100644 --- a/tests/cas.phpt +++ b/tests/cas.phpt @@ -8,47 +8,12 @@ include dirname (__FILE__) . '/config.inc'; $m = memc_get_instance (); $m->delete('cas_test'); -$cas_token = null; +$cas_token = 0; -$m->set('cas_test', 10); -$v = $m->get('cas_test', null, $cas_token); - -if (is_null($cas_token)) { - echo "Null cas token for key: cas_test value: 10\n"; - return; -} - -$v = $m->cas($cas_token, 'cas_test', 11); -if (!$v) { - echo "Error setting key: cas_test value: 11 with CAS: $cas_token\n"; - return; -} - -$v = $m->get('cas_test'); - -if ($v !== 11) { - echo "Wanted cas_test to be 11, value is: "; - var_dump($v); -} - -$v = $m->get('cas_test', null, 2); -if ($v != 11) { - echo "Failed to get the value with \$cas_token passed by value (2)\n"; - return; -} - -$v = $m->get('cas_test', null, null); -if ($v != 11) { - echo "Failed to get the value with \$cas_token passed by value (null)\n"; - return; -} - -$v = $m->get('cas_test', null, $data = array(2, 4)); -if ($v != 11 || $data !== array(2, 4)) { - echo "Failed to get the value with \$cas_token passed by value (\$data = array(2, 4))\n"; - return; -} +$m->set('cas_test', 'hello'); +$cas_token = $m->get('cas_test', null, Memcached::GET_EXTENDED)['cas']; +$v = $m->cas($cas_token, 'cas_test', 0); echo "OK\n"; ?> --EXPECT-- diff --git a/tests/cas_multi.phpt b/tests/cas_multi.phpt index 240ecad..499742b 100644 --- a/tests/cas_multi.phpt +++ b/tests/cas_multi.phpt @@ -16,25 +16,22 @@ foreach ($data as $key => $v) { $m->delete($key); } -$cas_tokens = array(); $m->setMulti($data, 10); -$actual = $m->getMulti(array_keys($data), $cas_tokens); +$actual = $m->getMulti(array_keys($data), Memcached::GET_EXTENDED); -foreach ($data as $key => $v) { - if (is_null($cas_tokens[$key])) { +foreach ($actual as $key => $v) { + if (is_null($v['cas'])) { echo "missing cas token(s)\n"; echo "data: "; var_dump($data); echo "actual data: "; var_dump($actual); - echo "cas tokens: "; - var_dump($cas_tokens); return; } - $v = $m->cas($cas_tokens[$key], $key, 11); + $v = $m->cas($v['cas'], $key, 11); if (!$v) { - echo "Error setting key: $key value: 11 with CAS: ", $cas_tokens[$key], "\n"; + echo "Error setting key: $key value: 11 with CAS: ", $v['cas'], "\n"; return; } $v = $m->get($key); @@ -54,24 +51,6 @@ if (array_keys($actual) !== array_keys($data)) { return; } -$actual = $m->getMulti(array_keys($data), 2); -if (array_keys($actual) !== array_keys($data)) { - echo "Failed to getMulti \$cas_token passed by value (2)\n"; - return; -} - -$actual = $m->getMulti(array_keys($data), null); -if (array_keys($actual) !== array_keys($data)) { - echo "Failed to getMulti \$cas_token passed by value (null)\n"; - return; -} - -$actual = $m->getMulti(array_keys($data), $cas_tokens = array(2, 4)); -if (array_keys($actual) !== array_keys($data) || $cas_tokens !== array(2, 4)) { - echo "Failed to getMulti \$cas_token passed by value (\$cas_tokens = array(2, 4))\n"; - return; -} - echo "OK\n"; ?> diff --git a/tests/multi_order.phpt b/tests/multi_order.phpt index 4fd5f5b..876ddc6 100644 --- a/tests/multi_order.phpt +++ b/tests/multi_order.phpt @@ -21,10 +21,9 @@ foreach ($data as $k => $v) { $m->set($k, $v, 3600); } -$null = null; $keys = array_keys($data); $keys[] = 'zoo'; -$got = $m->getMulti($keys, $null, Memcached::GET_PRESERVE_ORDER); +$got = $m->getMulti($keys, Memcached::GET_PRESERVE_ORDER); foreach ($got as $k => $v) { echo "$k $v\n"; diff --git a/tests/no-not-found.phpt b/tests/no-not-found.phpt index de96d3e..06ae85e 100644 --- a/tests/no-not-found.phpt +++ b/tests/no-not-found.phpt @@ -11,8 +11,7 @@ $memcached->addServer('localhost', 5555); // Server should not exist $result = $memcached->get('foo_not_exists'); var_dump ($result === Memcached::GET_ERROR_RETURN_VALUE); -$cas = 7; -$result = $memcached->get('foo_not_exists', null, $cas); +$result = $memcached->get('foo_not_exists'); var_dump ($result === Memcached::GET_ERROR_RETURN_VALUE); echo "OK\n"; diff --git a/tests/rescode.phpt b/tests/rescode.phpt index fbda4f7..5b835d4 100644 --- a/tests/rescode.phpt +++ b/tests/rescode.phpt @@ -23,6 +23,7 @@ $m->delete('bar_foo'); echo $m->getResultCode(), "\n"; echo $m->getResultMessage(), "\n"; +$m->set ('asdf_a', 'aa'); $m->getMulti(array('asdf_a', 'jkhjkhjkb', 'nbahsdgc')); echo $m->getResultMessage(), "\n"; $code = $m->getResultCode(); diff --git a/tests/set_large.phpt b/tests/set_large.phpt index d9353a8..bf1098e 100644 --- a/tests/set_large.phpt +++ b/tests/set_large.phpt @@ -9,8 +9,9 @@ $m = memc_get_instance (); $key = 'foobarbazDEADC0DE'; $value = str_repeat("foo bar", 1024 * 1024); -$m->set($key, $value, 360); +var_dump($m->set($key, $value, 360)); var_dump($m->get($key) === $value); ?> --EXPECT-- bool(true) +bool(true) diff --git a/tests/user-flags.phpt b/tests/user-flags.phpt index 137ff1e..7b02ea2 100644 --- a/tests/user-flags.phpt +++ b/tests/user-flags.phpt @@ -16,6 +16,10 @@ function check_flags ($flags, $expected_flags) echo "Flags OK" . PHP_EOL; } +function get_flags($m, $key) { + return $m->get($key, null, Memcached::GET_EXTENDED)['flags']; +} + define ('FLAG_1', 1); define ('FLAG_2', 2); define ('FLAG_4', 4); @@ -30,26 +34,26 @@ $key = uniqid ('udf_test_'); // Set with flags off $m->set ($key, '1', 10); -$m->get($key); -var_dump($m->getLastUserFlags()); +$v = $m->get($key, null, Memcached::GET_EXTENDED); +var_dump($v); // Set flags on $m->setOption(Memcached::OPT_USER_FLAGS, FLAG_1); $m->set ($key, '1', 10); $m->get($key); -check_flags($m->getLastUserFlags()[$key], array(FLAG_1)); +check_flags(get_flags($m, $key), array(FLAG_1)); // Multiple flags $m->setOption(Memcached::OPT_USER_FLAGS, FLAG_1 | FLAG_2 | FLAG_4); $m->set ($key, '1', 10); $m->get($key); -check_flags($m->getLastUserFlags()[$key], array(FLAG_1, FLAG_2, FLAG_4)); +check_flags(get_flags($m, $key), array(FLAG_1, FLAG_2, FLAG_4)); // Even more flags $m->setOption(Memcached::OPT_USER_FLAGS, FLAG_1 | FLAG_2 | FLAG_4 | FLAG_32 | FLAG_64); $m->set ($key, '1', 10); $m->get($key); -check_flags($m->getLastUserFlags()[$key], array(FLAG_1, FLAG_2, FLAG_4, FLAG_32, FLAG_64)); +check_flags(get_flags($m, $key), array(FLAG_1, FLAG_2, FLAG_4, FLAG_32, FLAG_64)); // User flags with get multi $values = array( @@ -61,10 +65,10 @@ $values = array( $m->setOption(Memcached::OPT_USER_FLAGS, FLAG_2 | FLAG_4); $m->setMulti($values); $m->getMulti(array_keys($values)); -$flags = $m->getLastUserFlags(); +$flags = $m->getMulti(array_keys($values), Memcached::GET_EXTENDED); foreach (array_keys($values) as $key) { - check_flags($flags[$key], array(FLAG_2, FLAG_4)); + check_flags($flags[$key]['flags'], array(FLAG_2, FLAG_4)); } // User flags with compression on @@ -74,7 +78,7 @@ $m->setOption(Memcached::OPT_COMPRESSION_TYPE, Memcached::COMPRESSION_FASTLZ); $m->set ($key, '1', 10); $m->get($key); -check_flags($m->getLastUserFlags()[$key], array(FLAG_1, FLAG_2, FLAG_4)); +check_flags(get_flags($m, $key), array(FLAG_1, FLAG_2, FLAG_4)); // Too large flags @@ -83,8 +87,12 @@ $m->setOption(Memcached::OPT_USER_FLAGS, FLAG_TOO_LARGE); echo "DONE TEST\n"; ?> --EXPECTF-- -array(1) { - ["udf_test_%s"]=> +array(3) { + ["value"]=> + string(1) "1" + ["cas"]=> + int(%d) + ["flags"]=> int(0) } Flags OK