/
usr
/
local
/
php-memcached
/
Upload File
HOME
/* +----------------------------------------------------------------------+ | Copyright (c) 2009-2010 The PHP Group | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt. | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ | Authors: Andrei Zmievski <andrei@php.net> | +----------------------------------------------------------------------+ */ #include "php_memcached.h" #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 #define MEMC_SESS_LOCK_EXPIRATION 30 #define REALTIME_MAXDELTA 60*60*24*30 ps_module ps_mod_memcached = { PS_MOD_UPDATE_TIMESTAMP(memcached) }; 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.v) #else #define MEMC_SESS_INI(v) (php_memcached_globals.session.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) { return le_memc_sess; } static void s_destroy_mod_data(memcached_st *memc) { php_memcached_user_data *user_data = memcached_get_user_data(memc); #ifdef HAVE_MEMCACHED_SASL if (user_data->has_sasl_data) { memcached_destroy_sasl_auth_data(memc); } #endif 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_adjust_expiration(zend_long expiration) { if (expiration <= REALTIME_MAXDELTA) { return expiration; } else { return time(NULL) + expiration; } } static time_t s_lock_expiration() { if (MEMC_SESS_INI(lock_expiration) > 0) { return s_adjust_expiration(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 s_adjust_expiration(max_execution_time); } } return 0; } static time_t s_session_expiration(zend_long maxlifetime) { if (maxlifetime > 0) { return s_adjust_expiration(maxlifetime); } return 0; } static zend_bool s_lock_session(memcached_st *memc, zend_string *sid) { memcached_return rc; 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 { 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: if (retries > 0) { 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, zend_bool silent) { memcached_return rc; #define check_set_behavior(behavior, value) \ if ((rc = memcached_behavior_set(memc, (behavior), (value))) != MEMCACHED_SUCCESS) { \ if (!silent) { 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)); } if (MEMC_SESS_STR_INI(sasl_username) && MEMC_SESS_STR_INI(sasl_password)) { php_memcached_user_data *user_data; if (!php_memc_init_sasl_if_needed()) { return 0; } 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; } #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); memcached_behavior_set(memc, MEMCACHED_BEHAVIOR_VERIFY_KEY, 1); return memc; } PS_OPEN_FUNC(memcached) { memcached_st *memc = NULL; char *plist_key = NULL; size_t plist_key_len = 0; memcached_server_list_st servers; // First parse servers servers = memcached_servers_parse(save_path); if (!servers) { php_error_docref(NULL, E_WARNING, "failed to parse session.save_path"); PS_SET_MOD_DATA(NULL); 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, 1)) { // Remove existing plist entry zend_hash_str_del(&EG(persistent_list), plist_key, plist_key_len); memc = NULL; } else { efree(plist_key); PS_SET_MOD_DATA(memc); return SUCCESS; } } } } memc = s_init_mod_data(servers, MEMC_SESS_INI(persistent_enabled)); memcached_server_list_free(servers); if (!s_configure_from_ini_values(memc, 0)) { if (plist_key) { efree(plist_key); } s_destroy_mod_data(memc); PS_SET_MOD_DATA(NULL); return FAILURE; } 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(memc); return SUCCESS; } PS_CLOSE_FUNC(memcached) { php_memcached_user_data *user_data; memcached_st *memc = PS_GET_MOD_DATA(); if (!memc) { php_error_docref(NULL, E_WARNING, "Session is not allocated, check session.save_path value"); return FAILURE; } 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; } PS_READ_FUNC(memcached) { char *payload = NULL; size_t payload_len = 0; uint32_t flags = 0; memcached_return status; memcached_st *memc = PS_GET_MOD_DATA(); if (!memc) { php_error_docref(NULL, E_WARNING, "Session is not allocated, check session.save_path value"); return FAILURE; } 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, key->val, key->len, &payload_len, &flags, &status); if (status == MEMCACHED_SUCCESS) { *val = zend_string_init(payload, payload_len, 0); efree(payload); return SUCCESS; } else if (status == MEMCACHED_NOTFOUND) { *val = ZSTR_EMPTY_ALLOC(); return SUCCESS; } else { return FAILURE; } } PS_WRITE_FUNC(memcached) { zend_long retries = 1; memcached_st *memc = PS_GET_MOD_DATA(); time_t expiration = s_session_expiration(maxlifetime); if (!memc) { php_error_docref(NULL, E_WARNING, "Session is not allocated, check session.save_path value"); return FAILURE; } /* 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_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); } do { if (memcached_set(memc, key->val, key->len, val->val, val->len, expiration, 0) == MEMCACHED_SUCCESS) { return SUCCESS; } } while (--retries > 0); return FAILURE; } PS_DESTROY_FUNC(memcached) { php_memcached_user_data *user_data; memcached_st *memc = PS_GET_MOD_DATA(); if (!memc) { php_error_docref(NULL, E_WARNING, "Session is not allocated, check session.save_path value"); return FAILURE; } 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; } PS_GC_FUNC(memcached) { return SUCCESS; } PS_CREATE_SID_FUNC(memcached) { zend_string *sid; memcached_st *memc = PS_GET_MOD_DATA(); if (!memc) { sid = php_session_create_id(NULL); } else { int retries = 3; while (retries-- > 0) { sid = php_session_create_id((void **) &memc); if (memcached_add (memc, sid->val, sid->len, NULL, 0, s_lock_expiration(), 0) == MEMCACHED_SUCCESS) { break; } zend_string_release(sid); sid = NULL; } } return sid; } PS_VALIDATE_SID_FUNC(memcached) { memcached_st *memc = PS_GET_MOD_DATA(); if (php_memcached_exist(memc, key) == MEMCACHED_SUCCESS) { return SUCCESS; } else { return FAILURE; } } PS_UPDATE_TIMESTAMP_FUNC(memcached) { memcached_st *memc = PS_GET_MOD_DATA(); time_t expiration = s_session_expiration(maxlifetime); if (memcached_touch(memc, key->val, key->len, expiration) == MEMCACHED_FAILURE) { return FAILURE; } return SUCCESS; } /* }}} */