diff options
author | Dmitry Stogov <dmitry@zend.com> | 2015-05-06 23:46:49 +0300 |
---|---|---|
committer | Dmitry Stogov <dmitry@zend.com> | 2015-05-06 23:46:49 +0300 |
commit | 3abde432314787f697533c88e7afd64946676306 (patch) | |
tree | ebcba1326f8844a0f90c41ffdd28bf5c9092644c | |
parent | 500b884f17d76629cb066da55145a83659b8acfb (diff) | |
download | php-git-3abde432314787f697533c88e7afd64946676306.tar.gz |
Added experimental (disabled by default) file based opcode cache.
-rw-r--r-- | NEWS | 2 | ||||
-rw-r--r-- | ext/opcache/ZendAccelerator.c | 400 | ||||
-rw-r--r-- | ext/opcache/ZendAccelerator.h | 10 | ||||
-rw-r--r-- | ext/opcache/config.m4 | 8 | ||||
-rw-r--r-- | ext/opcache/config.w32 | 7 | ||||
-rw-r--r-- | ext/opcache/tests/001_cli.phpt | 1 | ||||
-rw-r--r-- | ext/opcache/tests/blacklist-win32.phpt | 1 | ||||
-rw-r--r-- | ext/opcache/tests/blacklist.phpt | 1 | ||||
-rw-r--r-- | ext/opcache/tests/bug69281.phpt | 1 | ||||
-rw-r--r-- | ext/opcache/tests/is_script_cached.phpt | 1 | ||||
-rw-r--r-- | ext/opcache/zend_accelerator_module.c | 74 | ||||
-rw-r--r-- | ext/opcache/zend_accelerator_util_funcs.c | 25 | ||||
-rw-r--r-- | ext/opcache/zend_accelerator_util_funcs.h | 2 | ||||
-rw-r--r-- | ext/opcache/zend_file_cache.c | 1301 | ||||
-rw-r--r-- | ext/opcache/zend_file_cache.h | 26 | ||||
-rw-r--r-- | ext/opcache/zend_persist.c | 2 | ||||
-rw-r--r-- | ext/opcache/zend_shared_alloc.c | 40 | ||||
-rw-r--r-- | ext/opcache/zend_shared_alloc.h | 2 | ||||
-rw-r--r-- | ext/phar/tests/create_new_and_modify.phpt | 2 | ||||
-rw-r--r-- | ext/phar/tests/tar/create_new_and_modify.phpt | 2 | ||||
-rw-r--r-- | ext/phar/tests/zip/create_new_and_modify.phpt | 2 | ||||
-rwxr-xr-x | run-tests.php | 15 |
22 files changed, 1815 insertions, 110 deletions
@@ -141,6 +141,8 @@ . Removed mcrypt_ecb(), mcrypt_cbc(), mcrypt_cfb(), mcrypt_ofb(). (Nikita) - Opcache: + . Added experimental (disabled by default) file based opcode cache. + (Dmitry, Laruence, Anatol) . Fixed bug with try blocks being removed when extended_info opcode generation is turned on. (Laruence) . Fixed bug #68644 (strlen incorrect : mbstring + func_overload=2 +UTF-8 diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index ede6efd94c..847746b01f 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -41,6 +41,11 @@ #include "zend_accelerator_hash.h" #include "ext/pcre/php_pcre.h" +#ifdef HAVE_OPCACHE_FILE_CACHE +# include "zend_file_cache.h" +# include "ext/standard/md5.h" +#endif + #ifndef ZEND_WIN32 #include <netdb.h> #endif @@ -311,6 +316,12 @@ zend_string *accel_new_interned_string(zend_string *str) uint idx; Bucket *p; +#ifdef HAVE_OPCACHE_FILE_CACHE + if (ZCG(accel_directives).file_cache_only) { + return str; + } +#endif + if (IS_ACCEL_INTERNED(str)) { /* this is already an interned string */ return str; @@ -727,7 +738,7 @@ static accel_time_t zend_get_file_handle_timestamp_win(zend_file_handle *file_ha } #endif -static accel_time_t zend_get_file_handle_timestamp(zend_file_handle *file_handle, size_t *size) +accel_time_t zend_get_file_handle_timestamp(zend_file_handle *file_handle, size_t *size) { zend_stat_t statbuf; #ifdef ZEND_WIN32 @@ -896,29 +907,6 @@ int validate_timestamp_and_record(zend_persistent_script *persistent_script, zen } } -static unsigned int zend_accel_script_checksum(zend_persistent_script *persistent_script) -{ - signed char *mem = (signed char*)persistent_script->mem; - size_t size = persistent_script->size; - size_t persistent_script_check_block_size = ((char *)&(persistent_script->dynamic_members)) - (char *)persistent_script; - unsigned int checksum = ADLER32_INIT; - - if (mem < (signed char*)persistent_script) { - checksum = zend_adler32(checksum, mem, (signed char*)persistent_script - mem); - size -= (signed char*)persistent_script - mem; - mem += (signed char*)persistent_script - mem; - } - - zend_adler32(checksum, mem, persistent_script_check_block_size); - mem += sizeof(*persistent_script); - size -= sizeof(*persistent_script); - - if (size > 0) { - checksum = zend_adler32(checksum, mem, size); - } - return checksum; -} - /* Instead of resolving full real path name each time we need to identify file, * we create a key that consist from requested file name, current working * directory, current include_path, etc */ @@ -1080,6 +1068,15 @@ int zend_accel_invalidate(const char *filename, int filename_len, zend_bool forc zend_persistent_script *persistent_script; if (!ZCG(enabled) || !accel_startup_ok || !ZCSG(accelerator_enabled) || accelerator_shm_read_lock() != SUCCESS) { +#ifdef HAVE_OPCACHE_FILE_CACHE + if (ZCG(accel_directives).file_cache) { + realpath = accelerator_orig_zend_resolve_path(filename, filename_len); + if (realpath) { + zend_file_cache_invalidate(realpath); + zend_string_release(realpath); + } + } +#endif return FAILURE; } @@ -1089,6 +1086,12 @@ int zend_accel_invalidate(const char *filename, int filename_len, zend_bool forc return FAILURE; } +#ifdef HAVE_OPCACHE_FILE_CACHE + if (ZCG(accel_directives).file_cache) { + zend_file_cache_invalidate(realpath); + } +#endif + persistent_script = zend_accel_hash_find(&ZCSG(hash), realpath); if (persistent_script && !persistent_script->corrupted) { zend_file_handle file_handle; @@ -1118,7 +1121,7 @@ int zend_accel_invalidate(const char *filename, int filename_len, zend_bool forc } accelerator_shm_read_unlock(); - efree(realpath); + zend_string_release(realpath); return SUCCESS; } @@ -1145,6 +1148,64 @@ static void zend_accel_add_key(char *key, unsigned int key_length, zend_accel_ha } } +#ifdef HAVE_OPCACHE_FILE_CACHE +static zend_persistent_script *cache_script_in_file_cache(zend_persistent_script *new_persistent_script, int *from_shared_memory) +{ + uint memory_used; + + /* Check if script may be stored in shared memory */ + if (!zend_accel_script_persistable(new_persistent_script)) { + return new_persistent_script; + } + + if (!zend_accel_script_optimize(new_persistent_script)) { + return new_persistent_script; + } + + zend_shared_alloc_init_xlat_table(); + + /* Calculate the required memory size */ + memory_used = zend_accel_script_persist_calc(new_persistent_script, NULL, 0); + + /* Allocate memory block */ +#ifdef __SSE2__ + /* Align to 64-byte boundary */ + ZCG(mem) = zend_arena_alloc(&CG(arena), memory_used + 64); + ZCG(mem) = (void*)(((zend_uintptr_t)ZCG(mem) + 63L) & ~63L); +#else + ZCG(mem) = zend_arena_alloc(&CG(arena), memory_used); +#endif + + /* Copy into shared memory */ + new_persistent_script = zend_accel_script_persist(new_persistent_script, NULL, 0); + + zend_shared_alloc_destroy_xlat_table(); + + new_persistent_script->is_phar = + new_persistent_script->full_path && + strstr(new_persistent_script->full_path->val, ".phar") && + !strstr(new_persistent_script->full_path->val, "://"); + + /* Consistency check */ + if ((char*)new_persistent_script->mem + new_persistent_script->size != (char*)ZCG(mem)) { + zend_accel_error( + ((char*)new_persistent_script->mem + new_persistent_script->size < (char*)ZCG(mem)) ? ACCEL_LOG_ERROR : ACCEL_LOG_WARNING, + "Internal error: wrong size calculation: %s start=0x%08x, end=0x%08x, real=0x%08x\n", + new_persistent_script->full_path->val, + new_persistent_script->mem, + (char *)new_persistent_script->mem + new_persistent_script->size, + ZCG(mem)); + } + + new_persistent_script->dynamic_members.checksum = zend_accel_script_checksum(new_persistent_script); + + zend_file_cache_script_store(new_persistent_script); + + *from_shared_memory = 1; + return new_persistent_script; +} +#endif + static zend_persistent_script *cache_script_in_shared_memory(zend_persistent_script *new_persistent_script, char *key, unsigned int key_length, int *from_shared_memory) { zend_accel_hash_entry *bucket; @@ -1188,6 +1249,9 @@ static zend_persistent_script *cache_script_in_shared_memory(zend_persistent_scr } } + + zend_shared_alloc_init_xlat_table(); + /* Calculate the required memory size */ memory_used = zend_accel_script_persist_calc(new_persistent_script, key, key_length); @@ -1200,6 +1264,7 @@ static zend_persistent_script *cache_script_in_shared_memory(zend_persistent_scr ZCG(mem) = zend_shared_alloc(memory_used); #endif if (!ZCG(mem)) { + zend_shared_alloc_destroy_xlat_table(); zend_accel_schedule_restart_if_necessary(ACCEL_RESTART_OOM); zend_shared_alloc_unlock(); return new_persistent_script; @@ -1208,6 +1273,8 @@ static zend_persistent_script *cache_script_in_shared_memory(zend_persistent_scr /* Copy into shared memory */ new_persistent_script = zend_accel_script_persist(new_persistent_script, &key, key_length); + zend_shared_alloc_destroy_xlat_table(); + new_persistent_script->is_phar = new_persistent_script->full_path && strstr(new_persistent_script->full_path->val, ".phar") && @@ -1250,6 +1317,14 @@ static zend_persistent_script *cache_script_in_shared_memory(zend_persistent_scr zend_shared_alloc_unlock(); +#ifdef HAVE_OPCACHE_FILE_CACHE + if (ZCG(accel_directives).file_cache) { + SHM_PROTECT(); + zend_file_cache_script_store(new_persistent_script); + SHM_UNPROTECT(); + } +#endif + *from_shared_memory = 1; return new_persistent_script; } @@ -1315,7 +1390,7 @@ static void zend_accel_init_auto_globals(void) } } -static zend_persistent_script *compile_and_cache_file(zend_file_handle *file_handle, int type, char *key, unsigned int key_length, zend_op_array **op_array_p, int *from_shared_memory) +static zend_persistent_script *opcache_compile_file(zend_file_handle *file_handle, int type, char *key, unsigned int key_length, zend_op_array **op_array_p) { zend_persistent_script *new_persistent_script; zend_op_array *orig_active_op_array; @@ -1460,9 +1535,81 @@ static zend_persistent_script *compile_and_cache_file(zend_file_handle *file_han zend_string_hash_val(new_persistent_script->full_path); /* Now persistent_script structure is ready in process memory */ - return cache_script_in_shared_memory(new_persistent_script, key, key_length, from_shared_memory); + return new_persistent_script; } +#ifdef HAVE_OPCACHE_FILE_CACHE +zend_op_array *file_cache_compile_file(zend_file_handle *file_handle, int type) +{ + zend_persistent_script *persistent_script; + zend_op_array *op_array = NULL; + int from_memory; /* if the script we've got is stored in SHM */ + + if (is_stream_path(file_handle->filename) && + !is_cacheable_stream_path(file_handle->filename)) { + return accelerator_orig_compile_file(file_handle, type); + } + + if (!file_handle->opened_path) { + if (file_handle->type == ZEND_HANDLE_FILENAME && + accelerator_orig_zend_stream_open_function(file_handle->filename, file_handle) == FAILURE) { + if (type == ZEND_REQUIRE) { + zend_message_dispatcher(ZMSG_FAILED_REQUIRE_FOPEN, file_handle->filename); + zend_bailout(); + } else { + zend_message_dispatcher(ZMSG_FAILED_INCLUDE_FOPEN, file_handle->filename); + } + return NULL; + } + } + + persistent_script = zend_file_cache_script_load(file_handle); + if (persistent_script) { + /* see bug #15471 (old BTS) */ + if (persistent_script->full_path) { + if (!EG(current_execute_data) || !EG(current_execute_data)->opline || + !EG(current_execute_data)->func || + !ZEND_USER_CODE(EG(current_execute_data)->func->common.type) || + EG(current_execute_data)->opline->opcode != ZEND_INCLUDE_OR_EVAL || + (EG(current_execute_data)->opline->extended_value != ZEND_INCLUDE_ONCE && + EG(current_execute_data)->opline->extended_value != ZEND_REQUIRE_ONCE)) { + if (zend_hash_add_empty_element(&EG(included_files), persistent_script->full_path) != NULL) { + /* ext/phar has to load phar's metadata into memory */ + if (persistent_script->is_phar) { + php_stream_statbuf ssb; + char *fname = emalloc(sizeof("phar://") + persistent_script->full_path->len); + + memcpy(fname, "phar://", sizeof("phar://") - 1); + memcpy(fname + sizeof("phar://") - 1, persistent_script->full_path->val, persistent_script->full_path->len + 1); + php_stream_stat_path(fname, &ssb); + efree(fname); + } + } + } + } + zend_file_handle_dtor(file_handle); + + persistent_script->dynamic_members.last_used = ZCG(request_time); + + if (persistent_script->ping_auto_globals_mask) { + zend_accel_set_auto_globals(persistent_script->ping_auto_globals_mask); + } + + return zend_accel_load_script(persistent_script, 1); + } + + persistent_script = opcache_compile_file(file_handle, type, NULL, 0, &op_array); + + if (persistent_script) { + from_memory = 0; + persistent_script = cache_script_in_file_cache(persistent_script, &from_memory); + return zend_accel_load_script(persistent_script, from_memory); + } + + return op_array; +} +#endif + /* zend_compile() replacement */ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) { @@ -1471,11 +1618,21 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) int key_length; int from_shared_memory; /* if the script we've got is stored in SHM */ - if (!file_handle->filename || !ZCG(enabled) || !accel_startup_ok || - (!ZCG(counted) && !ZCSG(accelerator_enabled)) || - (ZCSG(restart_in_progress) && accel_restart_is_active())) { + if (!file_handle->filename || !ZCG(enabled) || !accel_startup_ok) { /* The Accelerator is disabled, act as if without the Accelerator */ return accelerator_orig_compile_file(file_handle, type); +#ifdef HAVE_OPCACHE_FILE_CACHE + } else if (ZCG(accel_directives).file_cache_only) { + return file_cache_compile_file(file_handle, type); +#endif + } else if ((!ZCG(counted) && !ZCSG(accelerator_enabled)) || + (ZCSG(restart_in_progress) && accel_restart_is_active())) { +#ifdef HAVE_OPCACHE_FILE_CACHE + if (ZCG(accel_directives).file_cache) { + return file_cache_compile_file(file_handle, type); + } +#endif + return accelerator_orig_compile_file(file_handle, type); } /* In case this callback is called from include_once, require_once or it's @@ -1602,6 +1759,13 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) } } +#ifdef HAVE_OPCACHE_FILE_CACHE + /* Check the second level cache */ + if (!persistent_script && ZCG(accel_directives).file_cache) { + persistent_script = zend_file_cache_script_load(file_handle); + } +#endif + /* If script was not found or invalidated by validate_timestamps */ if (!persistent_script) { uint32_t old_const_num = zend_hash_next_free_element(EG(zend_constants)); @@ -1620,7 +1784,10 @@ zend_op_array *persistent_compile_file(zend_file_handle *file_handle, int type) * If it isn't compile_and_cache_file() changes the flag to 0 */ from_shared_memory = 0; - persistent_script = compile_and_cache_file(file_handle, type, key, key_length, &op_array, &from_shared_memory); + persistent_script = opcache_compile_file(file_handle, type, key, key ? key_length : 0, &op_array); + if (persistent_script) { + persistent_script = cache_script_in_shared_memory(persistent_script, key, key ? key_length : 0, &from_shared_memory); + } /* Caching is disabled, returning op_array; * or something went wrong during compilation, returning NULL @@ -1830,7 +1997,6 @@ static void accel_activate(void) zend_accel_copy_internal_functions(); } - SHM_UNPROTECT(); /* PHP-5.4 and above return "double", but we use 1 sec precision */ ZCG(auto_globals_mask) = 0; ZCG(request_time) = (time_t)sapi_get_request_time(); @@ -1839,6 +2005,23 @@ static void accel_activate(void) ZCG(include_path_key_len) = 0; ZCG(include_path_check) = 1; + /* check if ZCG(function_table) wasn't somehow polluted on the way */ + if (ZCG(internal_functions_count) != zend_hash_num_elements(&ZCG(function_table))) { + zend_accel_error(ACCEL_LOG_WARNING, "Internal functions count changed - was %d, now %d", ZCG(internal_functions_count), zend_hash_num_elements(&ZCG(function_table))); + } + + ZCG(cwd) = NULL; + ZCG(cwd_key_len) = 0; + ZCG(cwd_check) = 1; + +#ifdef HAVE_OPCACHE_FILE_CACHE + if (ZCG(accel_directives).file_cache_only) { + return; + } +#endif + + SHM_UNPROTECT(); + if (ZCG(counted)) { #ifdef ZTS zend_accel_error(ACCEL_LOG_WARNING, "Stuck count for thread id %d", tsrm_thread_id()); @@ -1894,15 +2077,6 @@ static void accel_activate(void) zend_shared_alloc_unlock(); } - /* check if ZCG(function_table) wasn't somehow polluted on the way */ - if (ZCG(internal_functions_count) != zend_hash_num_elements(&ZCG(function_table))) { - zend_accel_error(ACCEL_LOG_WARNING, "Internal functions count changed - was %d, now %d", ZCG(internal_functions_count), zend_hash_num_elements(&ZCG(function_table))); - } - - ZCG(cwd) = NULL; - ZCG(cwd_key_len) = 0; - ZCG(cwd_check) = 1; - SHM_PROTECT(); if (ZCSG(last_restart_time) != ZCG(last_restart_time)) { @@ -2285,6 +2459,38 @@ static void accel_globals_dtor(zend_accel_globals *accel_globals) } } +#ifdef HAVE_OPCACHE_FILE_CACHE + +#define ZEND_BIN_ID "BIN_" ZEND_TOSTR(SIZEOF_CHAR) ZEND_TOSTR(SIZEOF_INT) ZEND_TOSTR(SIZEOF_LONG) ZEND_TOSTR(SIZEOF_SIZE_T) ZEND_TOSTR(SIZEOF_ZEND_LONG) ZEND_TOSTR(ZEND_MM_ALIGNMENT) + +static void accel_gen_system_id(void) +{ + PHP_MD5_CTX context; + unsigned char digest[16], c; + char *md5str = ZCG(system_id); + int i; + + PHP_MD5Init(&context); + PHP_MD5Update(&context, PHP_VERSION, sizeof(PHP_VERSION)-1); + PHP_MD5Update(&context, ZEND_EXTENSION_BUILD_ID, sizeof(ZEND_EXTENSION_BUILD_ID)-1); + PHP_MD5Update(&context, ZEND_BIN_ID, sizeof(ZEND_BIN_ID)-1); + if (strstr(PHP_VERSION, "-dev") != 0) { + /* Development versions may be changed from build to build */ + PHP_MD5Update(&context, __DATE__, sizeof(__DATE__)-1); + PHP_MD5Update(&context, __TIME__, sizeof(__TIME__)-1); + } + PHP_MD5Final(digest, &context); + for (i = 0; i < 16; i++) { + c = digest[i] >> 4; + c = (c <= 9) ? c + '0' : c - 10 + 'a'; + md5str[i * 2] = c; + c = digest[i] & 0x0f; + c = (c <= 9) ? c + '0' : c - 10 + 'a'; + md5str[(i * 2) + 1] = c; + } +} +#endif + static int accel_startup(zend_extension *extension) { zend_function *func; @@ -2306,6 +2512,10 @@ static int accel_startup(zend_extension *extension) return FAILURE; } +#ifdef HAVE_OPCACHE_FILE_CACHE + accel_gen_system_id(); +#endif + /* no supported SAPI found - disable acceleration and stop initialization */ if (accel_find_sapi() == FAILURE) { accel_startup_ok = 0; @@ -2321,49 +2531,73 @@ static int accel_startup(zend_extension *extension) if (ZCG(enabled) == 0) { return SUCCESS ; } + /********************************************/ /* End of non-SHM dependent initializations */ /********************************************/ - switch (zend_shared_alloc_startup(ZCG(accel_directives).memory_consumption)) { - case ALLOC_SUCCESS: - if (zend_accel_init_shm() == FAILURE) { +#ifdef HAVE_OPCACHE_FILE_CACHE + if (!ZCG(accel_directives).file_cache_only) { +#else + if (1) { +#endif + switch (zend_shared_alloc_startup(ZCG(accel_directives).memory_consumption)) { + case ALLOC_SUCCESS: + if (zend_accel_init_shm() == FAILURE) { + accel_startup_ok = 0; + return FAILURE; + } + break; + case ALLOC_FAILURE: accel_startup_ok = 0; - return FAILURE; - } - break; - case ALLOC_FAILURE: - accel_startup_ok = 0; - zend_accel_error(ACCEL_LOG_FATAL, "Failure to initialize shared memory structures - probably not enough shared memory."); - return SUCCESS; - case SUCCESSFULLY_REATTACHED: - accel_shared_globals = (zend_accel_shared_globals *) ZSMMG(app_shared_globals); - zend_shared_alloc_lock(); - orig_new_interned_string = zend_new_interned_string; - orig_interned_strings_snapshot = zend_interned_strings_snapshot; - orig_interned_strings_restore = zend_interned_strings_restore; - - zend_new_interned_string = accel_new_interned_string_for_php; - zend_interned_strings_snapshot = accel_interned_strings_snapshot_for_php; - zend_interned_strings_restore = accel_interned_strings_restore_for_php; + zend_accel_error(ACCEL_LOG_FATAL, "Failure to initialize shared memory structures - probably not enough shared memory."); + return SUCCESS; + case SUCCESSFULLY_REATTACHED: + accel_shared_globals = (zend_accel_shared_globals *) ZSMMG(app_shared_globals); + zend_shared_alloc_lock(); + orig_new_interned_string = zend_new_interned_string; + orig_interned_strings_snapshot = zend_interned_strings_snapshot; + orig_interned_strings_restore = zend_interned_strings_restore; + + zend_new_interned_string = accel_new_interned_string_for_php; + zend_interned_strings_snapshot = accel_interned_strings_snapshot_for_php; + zend_interned_strings_restore = accel_interned_strings_restore_for_php; #ifndef ZTS - accel_use_shm_interned_strings(); + accel_use_shm_interned_strings(); #endif - zend_shared_alloc_unlock(); - break; - case FAILED_REATTACHED: - accel_startup_ok = 0; - zend_accel_error(ACCEL_LOG_FATAL, "Failure to initialize shared memory structures - can not reattach to exiting shared memory."); - return SUCCESS; - break; - } + zend_shared_alloc_unlock(); + break; + case FAILED_REATTACHED: + accel_startup_ok = 0; + zend_accel_error(ACCEL_LOG_FATAL, "Failure to initialize shared memory structures - can not reattach to exiting shared memory."); + return SUCCESS; + break; + } - /* remeber the last restart time in the process memory */ - ZCG(last_restart_time) = ZCSG(last_restart_time); + /* from this point further, shared memory is supposed to be OK */ - /* from this point further, shared memory is supposed to be OK */ + /* remeber the last restart time in the process memory */ + ZCG(last_restart_time) = ZCSG(last_restart_time); + + /* Init auto-global strings */ + zend_accel_init_auto_globals(); + + zend_shared_alloc_lock(); + zend_shared_alloc_save_state(); + zend_shared_alloc_unlock(); + + SHM_PROTECT(); +#ifdef HAVE_OPCACHE_FILE_CACHE + } else if (!ZCG(accel_directives).file_cache) { + accel_startup_ok = 0; + zend_accel_error(ACCEL_LOG_FATAL, "opcache.file_cache_only is set without a proper setting of opcache.file_cache"); + return SUCCESS; + } else { + accel_shared_globals = calloc(1, sizeof(zend_accel_shared_globals)); - /* Init auto-global strings */ - zend_accel_init_auto_globals(); + /* Init auto-global strings */ + zend_accel_init_auto_globals(); +#endif + } /* Override compiler */ accelerator_orig_compile_file = zend_compile_file; @@ -2395,12 +2629,6 @@ static int accel_startup(zend_extension *extension) ini_entry->on_modify = accel_include_path_on_modify; } - zend_shared_alloc_lock(); - zend_shared_alloc_save_state(); - zend_shared_alloc_unlock(); - - SHM_PROTECT(); - accel_startup_ok = 1; /* Override file_exists(), is_file() and is_readable() */ @@ -2430,6 +2658,7 @@ static void accel_free_ts_resources() void accel_shutdown(void) { zend_ini_entry *ini_entry; + zend_bool file_cache_only = 0; zend_accel_blacklist_shutdown(&accel_blacklist); @@ -2453,8 +2682,15 @@ void accel_shutdown(void) zend_interned_strings_snapshot = orig_interned_strings_snapshot; zend_interned_strings_restore = orig_interned_strings_restore; +#ifdef HAVE_OPCACHE_FILE_CACHE + file_cache_only = ZCG(accel_directives).file_cache_only; +#endif + accel_free_ts_resources(); - zend_shared_alloc_shutdown(); + + if (!file_cache_only) { + zend_shared_alloc_shutdown(); + } zend_compile_file = accelerator_orig_compile_file; if ((ini_entry = zend_hash_str_find_ptr(EG(ini_directives), "include_path", sizeof("include_path")-1)) != NULL) { diff --git a/ext/opcache/ZendAccelerator.h b/ext/opcache/ZendAccelerator.h index a41d0a94d0..2c6584a1e2 100644 --- a/ext/opcache/ZendAccelerator.h +++ b/ext/opcache/ZendAccelerator.h @@ -215,6 +215,11 @@ typedef struct _zend_accel_directives { zend_long max_file_size; zend_long interned_strings_buffer; char *restrict_api; +#ifdef HAVE_OPCACHE_FILE_CACHE + char *file_cache; + zend_bool file_cache_only; + zend_bool file_cache_consistency_checks; +#endif } zend_accel_directives; typedef struct _zend_accel_globals { @@ -238,6 +243,10 @@ typedef struct _zend_accel_globals { int auto_globals_mask; time_t request_time; time_t last_restart_time; /* used to synchronize SHM and in-process caches */ +#ifdef HAVE_OPCACHE_FILE_CACHE + char system_id[32]; +#endif + HashTable xlat_table; /* preallocated shared-memory block to save current script */ void *mem; void *arena_mem; @@ -302,6 +311,7 @@ extern char *zps_api_failure_reason; void accel_shutdown(void); void zend_accel_schedule_restart(zend_accel_restart_reason reason); void zend_accel_schedule_restart_if_necessary(zend_accel_restart_reason reason); +accel_time_t zend_get_file_handle_timestamp(zend_file_handle *file_handle, size_t *size); int validate_timestamp_and_record(zend_persistent_script *persistent_script, zend_file_handle *file_handle); int zend_accel_invalidate(const char *filename, int filename_len, zend_bool force); int zend_accel_script_optimize(zend_persistent_script *persistent_script); diff --git a/ext/opcache/config.m4 b/ext/opcache/config.m4 index b10567b468..856f24ab58 100644 --- a/ext/opcache/config.m4 +++ b/ext/opcache/config.m4 @@ -5,8 +5,15 @@ dnl PHP_ARG_ENABLE(opcache, whether to enable Zend OPcache support, [ --disable-opcache Disable Zend OPcache support], yes) +PHP_ARG_ENABLE(opcache-file, whether to enable file based caching (experimental), +[ --enable-opcache-file Enable file based caching], no) + if test "$PHP_OPCACHE" != "no"; then + if test "$PHP_OPCACHE_FILE" == "yes"; then + AC_DEFINE(HAVE_OPCACHE_FILE_CACHE, 1, [Define to enable file based caching (experimental)]) + fi + AC_CHECK_FUNC(mprotect,[ AC_DEFINE(HAVE_MPROTECT, 1, [Define if you have mprotect() function]) ]) @@ -371,6 +378,7 @@ fi zend_accelerator_module.c \ zend_persist.c \ zend_persist_calc.c \ + zend_file_cache.c \ zend_shared_alloc.c \ zend_accelerator_util_funcs.c \ shared_alloc_shm.c \ diff --git a/ext/opcache/config.w32 b/ext/opcache/config.w32 index edcbc320c6..dcc2501ffc 100644 --- a/ext/opcache/config.w32 +++ b/ext/opcache/config.w32 @@ -1,9 +1,15 @@ ARG_ENABLE("opcache", "whether to enable Zend OPcache support", "yes"); +ARG_ENABLE("opcache-file", "whether to enable file based caching (experimental)", "no"); + var PHP_OPCACHE_PGO = false; if (PHP_OPCACHE != "no") { + if (PHP_OPCACHE_FILE == "yes") { + AC_DEFINE('HAVE_OPCACHE_FILE_CACHE', 1, 'Define to enable file based caching (experimental)'); + } + EXTENSION('opcache', "\ ZendAccelerator.c \ zend_accelerator_blacklist.c \ @@ -13,6 +19,7 @@ if (PHP_OPCACHE != "no") { zend_accelerator_util_funcs.c \ zend_persist.c \ zend_persist_calc.c \ + zend_file_cache.c \ zend_shared_alloc.c \ shared_alloc_win32.c", true, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); diff --git a/ext/opcache/tests/001_cli.phpt b/ext/opcache/tests/001_cli.phpt index c51db23f56..3be1a52228 100644 --- a/ext/opcache/tests/001_cli.phpt +++ b/ext/opcache/tests/001_cli.phpt @@ -3,6 +3,7 @@ --INI-- opcache.enable=1 opcache.enable_cli=1 +opcache.file_cache_only=0 --SKIPIF-- <?php require_once('skipif.inc'); ?> --FILE-- diff --git a/ext/opcache/tests/blacklist-win32.phpt b/ext/opcache/tests/blacklist-win32.phpt index 909c695fcd..1e479b6c2e 100644 --- a/ext/opcache/tests/blacklist-win32.phpt +++ b/ext/opcache/tests/blacklist-win32.phpt @@ -5,6 +5,7 @@ opcache.enable=1 opcache.enable_cli=1 opcache.blacklist_filename={PWD}/opcache-*.blacklist opcache.file_update_protection=0 +opcache.file_cache_only=0 --SKIPIF-- <?php require_once('skipif.inc'); ?> <?php if (substr(PHP_OS, 0, 3) != 'WIN') { die('skip only for Windows'); } ?> diff --git a/ext/opcache/tests/blacklist.phpt b/ext/opcache/tests/blacklist.phpt index 0c60425dac..4e9a0f16fc 100644 --- a/ext/opcache/tests/blacklist.phpt +++ b/ext/opcache/tests/blacklist.phpt @@ -5,6 +5,7 @@ opcache.enable=1 opcache.enable_cli=1 opcache.blacklist_filename={PWD}/opcache-*.blacklist opcache.file_update_protection=0 +opcache.file_cache_only=0 --SKIPIF-- <?php require_once('skipif.inc'); ?> <?php if (substr(PHP_OS, 0, 3) == 'WIN') { die('skip not for Windows'); } ?> diff --git a/ext/opcache/tests/bug69281.phpt b/ext/opcache/tests/bug69281.phpt index 4d68d5007b..506f466ac8 100644 --- a/ext/opcache/tests/bug69281.phpt +++ b/ext/opcache/tests/bug69281.phpt @@ -5,6 +5,7 @@ opcache.enable=1 opcache.enable_cli=1 opcache.file_update_protection=0 opcache.validate_timestamps=0 +opcache.file_cache_only=0 --SKIPIF-- <?php require_once('skipif.inc'); ?> --FILE-- diff --git a/ext/opcache/tests/is_script_cached.phpt b/ext/opcache/tests/is_script_cached.phpt index bac561103f..c3ab054251 100644 --- a/ext/opcache/tests/is_script_cached.phpt +++ b/ext/opcache/tests/is_script_cached.phpt @@ -5,6 +5,7 @@ opcache.enable=1 opcache.enable_cli=1 opcache.file_update_protection=0 opcache.validate_timestamps=1 +opcache.file_cache_only=0 --SKIPIF-- <?php require_once('skipif.inc'); ?> --FILE-- diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c index b79816b8f9..11c0635090 100644 --- a/ext/opcache/zend_accelerator_module.c +++ b/ext/opcache/zend_accelerator_module.c @@ -241,6 +241,38 @@ static ZEND_INI_MH(OnEnable) } } +#ifdef HAVE_OPCACHE_FILE_CACHE + +#ifndef S_ISDIR +# define S_ISDIR(mode) (((mode)&S_IFMT) == S_IFDIR) +#endif + +static ZEND_INI_MH(OnUpdateFileCache) +{ + if (new_value) { + if (!new_value->len) { + new_value = NULL; + } else { + zend_stat_t buf; + + if (!IS_ABSOLUTE_PATH(new_value->val, new_value->len) || + zend_stat(new_value->val, &buf) != 0 || + !S_ISDIR(buf.st_mode) || +#ifndef ZEND_WIN32 + access(new_value->val, R_OK | W_OK | X_OK) != 0) { +#else + _access(new_value->val, 06) != 0) { +#endif + zend_accel_error(ACCEL_LOG_WARNING, "opcache.file_cache must be a full path of accessable directory.\n"); + new_value = NULL; + } + } + } + OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); + return SUCCESS; +} +#endif + ZEND_INI_BEGIN() STD_PHP_INI_BOOLEAN("opcache.enable" , "1", PHP_INI_ALL, OnEnable, enabled , zend_accel_globals, accel_globals) STD_PHP_INI_BOOLEAN("opcache.use_cwd" , "1", PHP_INI_SYSTEM, OnUpdateBool, accel_directives.use_cwd , zend_accel_globals, accel_globals) @@ -276,6 +308,12 @@ ZEND_INI_BEGIN() #ifdef ZEND_WIN32 STD_PHP_INI_ENTRY("opcache.mmap_base", NULL, PHP_INI_SYSTEM, OnUpdateString, accel_directives.mmap_base, zend_accel_globals, accel_globals) #endif + +#ifdef HAVE_OPCACHE_FILE_CACHE + STD_PHP_INI_ENTRY("opcache.file_cache" , NULL , PHP_INI_SYSTEM, OnUpdateFileCache, accel_directives.file_cache, zend_accel_globals, accel_globals) + STD_PHP_INI_ENTRY("opcache.file_cache_only" , "0" , PHP_INI_SYSTEM, OnUpdateBool, accel_directives.file_cache_only, zend_accel_globals, accel_globals) + STD_PHP_INI_ENTRY("opcache.file_cache_consistency_checks" , "1" , PHP_INI_SYSTEM, OnUpdateBool, accel_directives.file_cache_consistency_checks, zend_accel_globals, accel_globals) +#endif ZEND_INI_END() static int filename_is_in_cache(zend_string *filename) @@ -395,6 +433,25 @@ void zend_accel_info(ZEND_MODULE_INFO_FUNC_ARGS) } else { php_info_print_table_row(2, "Optimization", "Disabled"); } +#ifdef HAVE_OPCACHE_FILE_CACHE + if (!ZCG(accel_directives).file_cache_only) { + php_info_print_table_row(2, "SHM Cache", "Enabled"); + } else { + php_info_print_table_row(2, "SHM Cache", "Disabled"); + } + if (ZCG(accel_directives).file_cache) { + php_info_print_table_row(2, "File Cache", "Enabled"); + } else { + php_info_print_table_row(2, "File Cache", "Disabled"); + } + if (ZCG(accel_directives).file_cache_only) { + if (!accel_startup_ok || zps_api_failure_reason) { + php_info_print_table_row(2, "Startup Failed", zps_api_failure_reason); + } else { + php_info_print_table_row(2, "Startup", "OK"); + } + } else +#endif if (ZCG(enabled)) { if (!accel_startup_ok || zps_api_failure_reason) { php_info_print_table_row(2, "Startup Failed", zps_api_failure_reason); @@ -529,6 +586,17 @@ static ZEND_FUNCTION(opcache_get_status) /* Trivia */ add_assoc_bool(return_value, "opcache_enabled", ZCG(enabled) && (ZCG(counted) || ZCSG(accelerator_enabled))); + +#ifdef HAVE_OPCACHE_FILE_CACHE + if (ZCG(accel_directives).file_cache) { + add_assoc_string(return_value, "file_cache", ZCG(accel_directives).file_cache); + } + if (ZCG(accel_directives).file_cache_only) { + add_assoc_bool(return_value, "file_cache_only", 1); + return; + } +#endif + add_assoc_bool(return_value, "cache_full", ZSMMG(memory_exhausted)); add_assoc_bool(return_value, "restart_pending", ZCSG(restart_pending)); add_assoc_bool(return_value, "restart_in_progress", ZCSG(restart_in_progress)); @@ -630,6 +698,12 @@ static ZEND_FUNCTION(opcache_get_configuration) add_assoc_bool(&directives, "opcache.enable_file_override", ZCG(accel_directives).file_override_enabled); add_assoc_long(&directives, "opcache.optimization_level", ZCG(accel_directives).optimization_level); +#ifdef HAVE_OPCACHE_FILE_CACHE + add_assoc_string(&directives, "opcache.file_cache", ZCG(accel_directives).file_cache ? ZCG(accel_directives).file_cache : ""); + add_assoc_bool(&directives, "opcache.file_cache_only", ZCG(accel_directives).file_cache_only); + add_assoc_bool(&directives, "opcache.file_cache_consistency_checks", ZCG(accel_directives).file_cache_consistency_checks); +#endif + add_assoc_zval(return_value, "directives", &directives); /*version */ diff --git a/ext/opcache/zend_accelerator_util_funcs.c b/ext/opcache/zend_accelerator_util_funcs.c index 92ada0b17f..9407b2ec39 100644 --- a/ext/opcache/zend_accelerator_util_funcs.c +++ b/ext/opcache/zend_accelerator_util_funcs.c @@ -97,7 +97,7 @@ void free_persistent_script(zend_persistent_script *persistent_script, int destr zend_hash_destroy(&persistent_script->class_table); if (persistent_script->full_path) { - efree(persistent_script->full_path); + zend_string_release(persistent_script->full_path); } efree(persistent_script); @@ -929,3 +929,26 @@ unsigned int zend_adler32(unsigned int checksum, signed char *buf, uint len) return (s2 << 16) | s1; } + +unsigned int zend_accel_script_checksum(zend_persistent_script *persistent_script) +{ + signed char *mem = (signed char*)persistent_script->mem; + size_t size = persistent_script->size; + size_t persistent_script_check_block_size = ((char *)&(persistent_script->dynamic_members)) - (char *)persistent_script; + unsigned int checksum = ADLER32_INIT; + + if (mem < (signed char*)persistent_script) { + checksum = zend_adler32(checksum, mem, (signed char*)persistent_script - mem); + size -= (signed char*)persistent_script - mem; + mem += (signed char*)persistent_script - mem; + } + + zend_adler32(checksum, mem, persistent_script_check_block_size); + mem += sizeof(*persistent_script); + size -= sizeof(*persistent_script); + + if (size > 0) { + checksum = zend_adler32(checksum, mem, size); + } + return checksum; +} diff --git a/ext/opcache/zend_accelerator_util_funcs.h b/ext/opcache/zend_accelerator_util_funcs.h index 2ef1595849..e2d8433145 100644 --- a/ext/opcache/zend_accelerator_util_funcs.h +++ b/ext/opcache/zend_accelerator_util_funcs.h @@ -39,6 +39,8 @@ zend_op_array* zend_accel_load_script(zend_persistent_script *persistent_script, unsigned int zend_adler32(unsigned int checksum, signed char *buf, uint len); +unsigned int zend_accel_script_checksum(zend_persistent_script *persistent_script); + #endif /* ZEND_ACCELERATOR_UTIL_FUNCS_H */ /* diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c new file mode 100644 index 0000000000..61c20695ff --- /dev/null +++ b/ext/opcache/zend_file_cache.c @@ -0,0 +1,1301 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2015 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: Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +#include "zend.h" +#include "zend_virtual_cwd.h" +#include "zend_compile.h" +#include "zend_vm.h" + +#include "php.h" + +#ifdef HAVE_OPCACHE_FILE_CACHE + +#include "ZendAccelerator.h" +#include "zend_file_cache.h" +#include "zend_shared_alloc.h" +#include "zend_accelerator_util_funcs.h" +#include "zend_accelerator_hash.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#ifdef HAVE_SYS_UIO_H +# include <sys/uio.h> +#endif + +#ifdef HAVE_SYS_FILE_H +# include <sys/file.h> +#endif + +#ifdef ZEND_WIN32 +# define LOCK_SH 0 +# define LOCK_EX 1 +# define LOCK_UN 2 +static int zend_file_cache_flock(int fd, int op) +{ + OVERLAPPED offset = {0,0,0,0,NULL}; + if (op == LOCK_EX) { + if (LockFileEx((HANDLE)_get_osfhandle(fd), + LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 0, &offset) == TRUE) { + return 0; + } + } else if (op == LOCK_SH) { + if (LockFileEx((HANDLE)_get_osfhandle(fd), + 0, 0, 1, 0, &offset) == TRUE) { + return 0; + } + } else if (op == LOCK_UN) { + if (UnlockFileEx((HANDLE)_get_osfhandle(fd), + 0, 1, 0, &offset) == TRUE) { + return 0; + } + } + return -1; +} +#elif defined(HAVE_FLOCK) +# define zend_file_cache_flock flock +#else +# define LOCK_SH 0 +# define LOCK_EX 1 +# define LOCK_UN 2 +static int zend_file_cache_flock(int fd, int type) +{ + return 0; +} +#endif + +#ifndef O_BINARY +# define O_BINARY 0 +#endif + +#define SUFFIX ".bin" + +#define IS_SERIALIZED_INTERNED(ptr) \ + ((size_t)(ptr) & Z_UL(1)) +#define IS_SERIALIZED(ptr) \ + ((char*)(ptr) < (char*)script->size) +#define IS_UNSERIALIZED(ptr) \ + (((char*)(ptr) >= (char*)script->mem && (char*)(ptr) < (char*)script->mem + script->size) || \ + IS_ACCEL_INTERNED(ptr)) +#define SERIALIZE_PTR(ptr) do { \ + if (ptr) { \ + if (IS_ACCEL_INTERNED(ptr)) { \ + (ptr) = zend_file_cache_serialize_interned((zend_string*)(ptr), info); \ + } else { \ + ZEND_ASSERT(IS_UNSERIALIZED(ptr)); \ + (ptr) = (void*)((char*)(ptr) - (char*)script->mem); \ + } \ + } \ + } while (0) +#define UNSERIALIZE_PTR(ptr) do { \ + if (ptr) { \ + if (IS_SERIALIZED_INTERNED(ptr)) { \ + (ptr) = (void*)zend_file_cache_unserialize_interned((zend_string*)(ptr)); \ + } else { \ + ZEND_ASSERT(IS_SERIALIZED(ptr)); \ + (ptr) = (void*)((char*)buf + (size_t)(ptr)); \ + } \ + } \ + } while (0) + +static const uint32_t uninitialized_bucket[-HT_MIN_MASK] = + {HT_INVALID_IDX, HT_INVALID_IDX}; + +typedef struct _zend_file_cache_metainfo { + char magic[8]; + char system_id[32]; + size_t mem_size; + size_t str_size; + size_t script_offset; + accel_time_t timestamp; + uint32_t checksum; +} zend_file_cache_metainfo; + +static int zend_file_cache_mkdir(char *filename, size_t start) +{ + char *s = filename + start; + + while (*s) { + if (IS_SLASH(*s)) { + char old = *s; + *s = '\000'; + if (mkdir(filename, S_IRWXU) < 0 && errno != EEXIST) { + *s = old; + return FAILURE; + } + *s = old; + } + s++; + } + return SUCCESS; +} + +typedef void (*serialize_callback_t)(zval *zv, + zend_persistent_script *script, + zend_file_cache_metainfo *info, + void *buf); + +typedef void (*unserialize_callback_t)(zval *zv, + zend_persistent_script *script, + void *buf); + +static void zend_file_cache_serialize_zval(zval *zv, + zend_persistent_script *script, + zend_file_cache_metainfo *info, + void *buf); +static void zend_file_cache_unserialize_zval(zval *zv, + zend_persistent_script *script, + void *buf); + +static void *zend_file_cache_serialize_interned(zend_string *str, + zend_file_cache_metainfo *info) +{ + size_t len; + void *ret; + + /* check if the same interned string was already stored */ + ret = zend_shared_alloc_get_xlat_entry(str); + if (ret) { + return ret; + } + + len = ZEND_MM_ALIGNED_SIZE(_STR_HEADER_SIZE + str->len + 1); + ret = (void*)(info->str_size | Z_UL(1)); + zend_shared_alloc_register_xlat_entry(str, ret); + if (info->str_size + len > ((zend_string*)ZCG(mem))->len) { + size_t new_len = info->str_size + len; + ZCG(mem) = (void*)zend_string_realloc( + (zend_string*)ZCG(mem), + ((_STR_HEADER_SIZE + 1 + new_len + 4095) & ~0xfff) - (_STR_HEADER_SIZE + 1), + 0); + } + memcpy(((zend_string*)ZCG(mem))->val + info->str_size, str, len); + info->str_size += len; + return ret; +} + +static void *zend_file_cache_unserialize_interned(zend_string *str) +{ + zend_string *ret; + + str = (zend_string*)((char*)ZCG(mem) + ((size_t)(str) & ~Z_UL(1))); + ret = accel_new_interned_string(str); + if (ret == str) { + /* String wasn't interned but we will use it as interned anyway */ + GC_FLAGS(ret) |= IS_STR_INTERNED | IS_STR_PERMANENT; + } + return ret; +} + +static void zend_file_cache_serialize_hash(HashTable *ht, + zend_persistent_script *script, + zend_file_cache_metainfo *info, + void *buf, + serialize_callback_t func) +{ + Bucket *p, *end; + + if (!(ht->u.flags & HASH_FLAG_INITIALIZED)) { + ht->arData = NULL; + return; + } + if (IS_SERIALIZED(ht->arData)) { + return; + } + SERIALIZE_PTR(ht->arData); + p = ht->arData; + UNSERIALIZE_PTR(p); + end = p + ht->nNumUsed; + while (p < end) { + if (Z_TYPE(p->val) != IS_UNDEF) { + SERIALIZE_PTR(p->key); + func(&p->val, script, info, buf); + } + p++; + } +} + +static zend_ast *zend_file_cache_serialize_ast(zend_ast *ast, + zend_persistent_script *script, + zend_file_cache_metainfo *info, + void *buf) +{ + uint32_t i; + zend_ast *ret; + + SERIALIZE_PTR(ast); + ret = ast; + UNSERIALIZE_PTR(ast); + + if (ast->kind == ZEND_AST_ZVAL) { + zend_file_cache_serialize_zval(&((zend_ast_zval*)ast)->val, script, info, buf); + } else if (zend_ast_is_list(ast)) { + zend_ast_list *list = zend_ast_get_list(ast); + for (i = 0; i < list->children; i++) { + if (list->child[i]) { + list->child[i] = zend_file_cache_serialize_ast(list->child[i], script, info, buf); + } + } + } else { + uint32_t children = zend_ast_get_num_children(ast); + for (i = 0; i < children; i++) { + if (ast->child[i]) { + ast->child[i] = zend_file_cache_serialize_ast(ast->child[i], script, info, buf); + } + } + } + return ret; +} + +static void zend_file_cache_serialize_zval(zval *zv, + zend_persistent_script *script, + zend_file_cache_metainfo *info, + void *buf) +{ + switch (Z_TYPE_P(zv)) { + case IS_STRING: + case IS_CONSTANT: + if (!IS_SERIALIZED(Z_STR_P(zv))) { + SERIALIZE_PTR(Z_STR_P(zv)); + } + break; + case IS_ARRAY: + if (!IS_SERIALIZED(Z_ARR_P(zv))) { + HashTable *ht; + + SERIALIZE_PTR(Z_ARR_P(zv)); + ht = Z_ARR_P(zv); + UNSERIALIZE_PTR(ht); + zend_file_cache_serialize_hash(ht, script, info, buf, zend_file_cache_serialize_zval); + } + break; + case IS_REFERENCE: + if (!IS_SERIALIZED(Z_REF_P(zv))) { + zend_reference *ref; + + SERIALIZE_PTR(Z_REF_P(zv)); + ref = Z_REF_P(zv); + UNSERIALIZE_PTR(ref); + zend_file_cache_serialize_zval(&ref->val, script, info, buf); + } + break; + case IS_CONSTANT_AST: + if (!IS_SERIALIZED(Z_AST_P(zv))) { + zend_ast_ref *ast; + + SERIALIZE_PTR(Z_AST_P(zv)); + ast = Z_AST_P(zv); + UNSERIALIZE_PTR(ast); + if (!IS_SERIALIZED(ast->ast)) { + ast->ast = zend_file_cache_serialize_ast(ast->ast, script, info, buf); + } + } + break; + } +} + +static void zend_file_cache_serialize_op_array(zend_op_array *op_array, + zend_persistent_script *script, + zend_file_cache_metainfo *info, + void *buf) +{ + if (op_array->static_variables && !IS_SERIALIZED(op_array->static_variables)) { + HashTable *ht; + + SERIALIZE_PTR(op_array->static_variables); + ht = op_array->static_variables; + UNSERIALIZE_PTR(ht); + zend_file_cache_serialize_hash(ht, script, info, buf, zend_file_cache_serialize_zval); + } + + if (op_array->literals && !IS_SERIALIZED(op_array->literals)) { + zval *p, *end; + + SERIALIZE_PTR(op_array->literals); + p = op_array->literals; + UNSERIALIZE_PTR(p); + end = p + op_array->last_literal; + while (p < end) { + zend_file_cache_serialize_zval(p, script, info, buf); + p++; + } + } + + if (!IS_SERIALIZED(op_array->opcodes)) { +#if ZEND_USE_ABS_CONST_ADDR || ZEND_USE_ABS_JMP_ADDR + zend_op *opline, *end; + + SERIALIZE_PTR(op_array->opcodes); + opline = op_array->opcodes; + UNSERIALIZE_PTR(opline); + end = opline + op_array->last; + while (opline < end) { +# if ZEND_USE_ABS_CONST_ADDR + if (ZEND_OP1_TYPE(opline) == IS_CONST) { + SERIALIZE_PTR(opline->op1.zv); + } + if (ZEND_OP2_TYPE(opline) == IS_CONST) { + SERIALIZE_PTR(opline->op2.zv); + } +# endif +# if ZEND_USE_ABS_JMP_ADDR + switch (opline->opcode) { + case ZEND_JMP: + case ZEND_GOTO: + case ZEND_FAST_CALL: + SERIALIZE_PTR(opline->op1.jmp_addr); + break; + case ZEND_JMPZNZ: + /* relative extended_value don't have to be changed */ + /* break omitted intentionally */ + case ZEND_JMPZ: + case ZEND_JMPNZ: + case ZEND_JMPZ_EX: + case ZEND_JMPNZ_EX: + case ZEND_JMP_SET: + case ZEND_COALESCE: + case ZEND_NEW: + case ZEND_FE_RESET_R: + case ZEND_FE_RESET_RW: + case ZEND_FE_FETCH_R: + case ZEND_FE_FETCH_RW: + case ZEND_ASSERT_CHECK: + SERIALIZE_PTR(opline->op2.jmp_addr); + break; + } +# endif + opline++; + } +#else + SERIALIZE_PTR(op_array->opcodes); +#endif + + if (op_array->arg_info) { + zend_arg_info *p, *end; + SERIALIZE_PTR(op_array->arg_info); + p = op_array->arg_info; + UNSERIALIZE_PTR(p); + end = p + op_array->num_args; + if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { + p--; + } + if (op_array->fn_flags & ZEND_ACC_VARIADIC) { + end++; + } + while (p < end) { + if (!IS_SERIALIZED(p->name)) { + SERIALIZE_PTR(p->name); + } + if (!IS_SERIALIZED(p->class_name)) { + SERIALIZE_PTR(p->class_name); + } + p++; + } + } + + if (op_array->vars) { + zend_string **p, **end; + + SERIALIZE_PTR(op_array->vars); + p = op_array->vars; + UNSERIALIZE_PTR(p); + end = p + op_array->last_var; + while (p < end) { + if (!IS_SERIALIZED(*p)) { + SERIALIZE_PTR(*p); + } + p++; + } + } + + SERIALIZE_PTR(op_array->function_name); + SERIALIZE_PTR(op_array->filename); + SERIALIZE_PTR(op_array->brk_cont_array); + SERIALIZE_PTR(op_array->scope); + SERIALIZE_PTR(op_array->doc_comment); + SERIALIZE_PTR(op_array->try_catch_array); + SERIALIZE_PTR(op_array->prototype); + } +} + +static void zend_file_cache_serialize_func(zval *zv, + zend_persistent_script *script, + zend_file_cache_metainfo *info, + void *buf) +{ + zend_op_array *op_array; + + SERIALIZE_PTR(Z_PTR_P(zv)); + op_array = Z_PTR_P(zv); + UNSERIALIZE_PTR(op_array); + zend_file_cache_serialize_op_array(op_array, script, info, buf); +} + +static void zend_file_cache_serialize_prop_info(zval *zv, + zend_persistent_script *script, + zend_file_cache_metainfo *info, + void *buf) +{ + if (!IS_SERIALIZED(Z_PTR_P(zv))) { + zend_property_info *prop; + + SERIALIZE_PTR(Z_PTR_P(zv)); + prop = Z_PTR_P(zv); + UNSERIALIZE_PTR(prop); + + if (prop->ce && !IS_SERIALIZED(prop->ce)) { + SERIALIZE_PTR(prop->ce); + } + if (prop->name && !IS_SERIALIZED(prop->name)) { + SERIALIZE_PTR(prop->name); + } + if (prop->doc_comment && !IS_SERIALIZED(prop->doc_comment)) { + SERIALIZE_PTR(prop->doc_comment); + } + } +} + +static void zend_file_cache_serialize_class(zval *zv, + zend_persistent_script *script, + zend_file_cache_metainfo *info, + void *buf) +{ + zend_class_entry *ce; + + SERIALIZE_PTR(Z_PTR_P(zv)); + ce = Z_PTR_P(zv); + UNSERIALIZE_PTR(ce); + + SERIALIZE_PTR(ce->name); + zend_file_cache_serialize_hash(&ce->function_table, script, info, buf, zend_file_cache_serialize_func); + if (ce->default_properties_table) { + zval *p, *end; + + SERIALIZE_PTR(ce->default_properties_table); + p = ce->default_properties_table; + UNSERIALIZE_PTR(p); + end = p + ce->default_properties_count; + while (p < end) { + zend_file_cache_serialize_zval(p, script, info, buf); + p++; + } + } + if (ce->default_static_members_table) { + zval *p, *end; + + SERIALIZE_PTR(ce->default_static_members_table); + p = ce->default_static_members_table; + UNSERIALIZE_PTR(p); + end = p + ce->default_static_members_count; + while (p < end) { + zend_file_cache_serialize_zval(p, script, info, buf); + p++; + } + } + zend_file_cache_serialize_hash(&ce->constants_table, script, info, buf, zend_file_cache_serialize_zval); + SERIALIZE_PTR(ZEND_CE_FILENAME(ce)); + SERIALIZE_PTR(ZEND_CE_DOC_COMMENT(ce)); + zend_file_cache_serialize_hash(&ce->properties_info, script, info, buf, zend_file_cache_serialize_prop_info); + + if (ce->trait_aliases) { + zend_trait_alias **p, *q; + + SERIALIZE_PTR(ce->trait_aliases); + p = ce->trait_aliases; + UNSERIALIZE_PTR(p); + + while (*p) { + SERIALIZE_PTR(*p); + q = *p; + UNSERIALIZE_PTR(q); + + if (q->trait_method) { + zend_trait_method_reference *m; + + SERIALIZE_PTR(q->trait_method); + m = q->trait_method; + UNSERIALIZE_PTR(m); + + if (m->method_name) { + SERIALIZE_PTR(m->method_name); + } + if (m->class_name) { + SERIALIZE_PTR(m->class_name); + } + } + + if (q->alias) { + SERIALIZE_PTR(q->alias); + } + p++; + } + } + + if (ce->trait_precedences) { + zend_trait_precedence **p, *q; + + SERIALIZE_PTR(ce->trait_precedences); + p = ce->trait_precedences; + UNSERIALIZE_PTR(p); + + while (*p) { + SERIALIZE_PTR(*p); + q = *p; + UNSERIALIZE_PTR(q); + + if (q->trait_method) { + zend_trait_method_reference *m; + + SERIALIZE_PTR(q->trait_method); + m = q->trait_method; + UNSERIALIZE_PTR(m); + + if (m->method_name) { + SERIALIZE_PTR(m->method_name); + } + if (m->class_name) { + SERIALIZE_PTR(m->class_name); + } + } + + if (q->exclude_from_classes) { + zend_string **s; + + SERIALIZE_PTR(q->exclude_from_classes); + s = (zend_string**)q->exclude_from_classes; + UNSERIALIZE_PTR(s); + + while (*s) { + SERIALIZE_PTR(*s); + s++; + } + } + p++; + } + } + + SERIALIZE_PTR(ce->parent); + SERIALIZE_PTR(ce->constructor); + SERIALIZE_PTR(ce->destructor); + SERIALIZE_PTR(ce->clone); + SERIALIZE_PTR(ce->__get); + SERIALIZE_PTR(ce->__set); + SERIALIZE_PTR(ce->__call); + SERIALIZE_PTR(ce->serialize_func); + SERIALIZE_PTR(ce->unserialize_func); + SERIALIZE_PTR(ce->__isset); + SERIALIZE_PTR(ce->__unset); + SERIALIZE_PTR(ce->__tostring); + SERIALIZE_PTR(ce->__callstatic); + SERIALIZE_PTR(ce->__debugInfo); +} + +static void zend_file_cache_serialize(zend_persistent_script *script, + zend_file_cache_metainfo *info, + void *buf) +{ + zend_persistent_script *new_script; + + memcpy(info->magic, "OPCACHE", 8); + memcpy(info->system_id, ZCG(system_id), 32); + info->mem_size = script->size; + info->str_size = 0; + info->script_offset = (char*)script - (char*)script->mem; + info->timestamp = script->timestamp; + + memcpy(buf, script->mem, script->size); + + new_script = (zend_persistent_script*)((char*)buf + info->script_offset); + SERIALIZE_PTR(new_script->full_path); + + zend_file_cache_serialize_hash(&new_script->class_table, script, info, buf, zend_file_cache_serialize_class); + zend_file_cache_serialize_hash(&new_script->function_table, script, info, buf, zend_file_cache_serialize_func); + zend_file_cache_serialize_op_array(&new_script->main_op_array, script, info, buf); + + SERIALIZE_PTR(new_script->arena_mem); + new_script->mem = NULL; +} + +int zend_file_cache_script_store(zend_persistent_script *script) +{ + size_t len; + int fd; + char *filename; + zend_file_cache_metainfo info; +#ifndef ZEND_WIN32 + struct iovec vec[3]; +#endif + void *mem, *buf; + + len = strlen(ZCG(accel_directives).file_cache); + filename = emalloc(len + 33 + script->full_path->len + sizeof(SUFFIX)); + memcpy(filename, ZCG(accel_directives).file_cache, len); + filename[len] = '/'; + memcpy(filename + len + 1, ZCG(system_id), 32); + memcpy(filename + len + 33, script->full_path->val, script->full_path->len); + memcpy(filename + len + 33 + script->full_path->len, SUFFIX, sizeof(SUFFIX)); + + if (zend_file_cache_mkdir(filename, len) != SUCCESS) { + zend_accel_error(ACCEL_LOG_WARNING, "opcache cannot create directory for file '%s'\n", filename); + efree(filename); + return FAILURE; + } + +#ifndef ZEND_WIN32 + fd = open(filename, O_CREAT | O_EXCL | O_RDWR | O_BINARY, S_IRUSR | S_IWUSR); +#else + fd = open(filename, O_CREAT | O_EXCL | O_RDWR | O_BINARY, _S_IREAD | _S_IWRITE); +#endif + if (fd < 0) { + if (errno != EEXIST) { + zend_accel_error(ACCEL_LOG_WARNING, "opcache cannot create file '%s'\n", filename); + } + efree(filename); + return FAILURE; + } + + if (zend_file_cache_flock(fd, LOCK_EX) != 0) { + close(fd); + efree(filename); + return FAILURE; + } + +#ifdef __SSE2__ + /* Align to 64-byte boundary */ + mem = emalloc(script->size + 64); + buf = (void*)(((zend_uintptr_t)mem + 63L) & ~63L); +#else + mem = buf = emalloc(script->size); +#endif + + ZCG(mem) = zend_string_alloc(4096 - (_STR_HEADER_SIZE + 1), 0); + + zend_shared_alloc_init_xlat_table(); + zend_file_cache_serialize(script, &info, buf); + zend_shared_alloc_destroy_xlat_table(); + + info.checksum = zend_adler32(ADLER32_INIT, buf, script->size); + info.checksum = zend_adler32(info.checksum, (signed char*)((zend_string*)ZCG(mem))->val, info.str_size); + +#ifndef ZEND_WIN32 + vec[0].iov_base = &info; + vec[0].iov_len = sizeof(info); + vec[1].iov_base = buf; + vec[1].iov_len = script->size; + vec[2].iov_base = ((zend_string*)ZCG(mem))->val; + vec[2].iov_len = info.str_size; + + if (writev(fd, vec, 3) != (ssize_t)(sizeof(info) + script->size + info.str_size)) { + zend_accel_error(ACCEL_LOG_WARNING, "opcache cannot write to file '%s'\n", filename); + zend_string_release((zend_string*)ZCG(mem)); + efree(mem); + unlink(filename); + efree(filename); + return FAILURE; + } +#else + if (ZEND_LONG_MAX < (zend_long)(sizeof(info) + script->size + info.str_size) || + write(fd, &info, sizeof(info)) != sizeof(info) || + write(fd, buf, script->size) != script->size || + write(fd, ((zend_string*)ZCG(mem))->val, info.str_size) != info.str_size + ) { + zend_accel_error(ACCEL_LOG_WARNING, "opcache cannot write to file '%s'\n", filename); + zend_string_release((zend_string*)ZCG(mem)); + efree(mem); + unlink(filename); + efree(filename); + return FAILURE; + } +#endif + + zend_string_release((zend_string*)ZCG(mem)); + efree(mem); + if (zend_file_cache_flock(fd, LOCK_UN) != 0) { + zend_accel_error(ACCEL_LOG_WARNING, "opcache cannot unlock file '%s'\n", filename); + } + close(fd); + efree(filename); + + return SUCCESS; +} + +static void zend_file_cache_unserialize_hash(HashTable *ht, + zend_persistent_script *script, + void *buf, + unserialize_callback_t func) +{ + Bucket *p, *end; + + if (!(ht->u.flags & HASH_FLAG_INITIALIZED)) { + HT_SET_DATA_ADDR(ht, &uninitialized_bucket); + return; + } + if (IS_UNSERIALIZED(ht->arData)) { + return; + } + UNSERIALIZE_PTR(ht->arData); + p = ht->arData; + end = p + ht->nNumUsed; + while (p < end) { + if (Z_TYPE(p->val) != IS_UNDEF) { + UNSERIALIZE_PTR(p->key); + func(&p->val, script, buf); + } + p++; + } +} + +static zend_ast *zend_file_cache_unserialize_ast(zend_ast *ast, + zend_persistent_script *script, + void *buf) +{ + uint32_t i; + + UNSERIALIZE_PTR(ast); + + if (ast->kind == ZEND_AST_ZVAL) { + zend_file_cache_unserialize_zval(&((zend_ast_zval*)ast)->val, script, buf); + } else if (zend_ast_is_list(ast)) { + zend_ast_list *list = zend_ast_get_list(ast); + for (i = 0; i < list->children; i++) { + if (list->child[i]) { + list->child[i] = zend_file_cache_unserialize_ast(list->child[i], script, buf); + } + } + } else { + uint32_t children = zend_ast_get_num_children(ast); + for (i = 0; i < children; i++) { + if (ast->child[i]) { + ast->child[i] = zend_file_cache_unserialize_ast(ast->child[i], script, buf); + } + } + } + return ast; +} + +static void zend_file_cache_unserialize_zval(zval *zv, + zend_persistent_script *script, + void *buf) +{ + switch (Z_TYPE_P(zv)) { + case IS_STRING: + case IS_CONSTANT: + if (!IS_UNSERIALIZED(Z_STR_P(zv))) { + UNSERIALIZE_PTR(Z_STR_P(zv)); + } + break; + case IS_ARRAY: + if (!IS_UNSERIALIZED(Z_ARR_P(zv))) { + HashTable *ht; + + UNSERIALIZE_PTR(Z_ARR_P(zv)); + ht = Z_ARR_P(zv); + zend_file_cache_unserialize_hash(ht, script, buf, zend_file_cache_unserialize_zval); + } + break; + case IS_REFERENCE: + if (!IS_UNSERIALIZED(Z_REF_P(zv))) { + zend_reference *ref; + + UNSERIALIZE_PTR(Z_REF_P(zv)); + ref = Z_REF_P(zv); + zend_file_cache_unserialize_zval(&ref->val, script, buf); + } + break; + case IS_CONSTANT_AST: + if (!IS_UNSERIALIZED(Z_AST_P(zv))) { + zend_ast_ref *ast; + + UNSERIALIZE_PTR(Z_AST_P(zv)); + ast = Z_AST_P(zv); + if (!IS_UNSERIALIZED(ast->ast)) { + ast->ast = zend_file_cache_unserialize_ast(ast->ast, script, buf); + } + } + break; + } +} + +static void zend_file_cache_unserialize_op_array(zend_op_array *op_array, + zend_persistent_script *script, + void *buf) +{ + if (op_array->static_variables && !IS_UNSERIALIZED(op_array->static_variables)) { + HashTable *ht; + + UNSERIALIZE_PTR(op_array->static_variables); + ht = op_array->static_variables; + zend_file_cache_unserialize_hash(ht, script, buf, zend_file_cache_unserialize_zval); + } + + if (op_array->literals && !IS_UNSERIALIZED(op_array->literals)) { + zval *p, *end; + + UNSERIALIZE_PTR(op_array->literals); + p = op_array->literals; + end = p + op_array->last_literal; + while (p < end) { + zend_file_cache_unserialize_zval(p, script, buf); + p++; + } + } + + if (!IS_UNSERIALIZED(op_array->opcodes)) { + zend_op *opline, *end; + + UNSERIALIZE_PTR(op_array->opcodes); + opline = op_array->opcodes; + end = opline + op_array->last; + while (opline < end) { +# if ZEND_USE_ABS_CONST_ADDR + if (ZEND_OP1_TYPE(opline) == IS_CONST) { + UNSERIALIZE_PTR(opline->op1.zv); + } + if (ZEND_OP2_TYPE(opline) == IS_CONST) { + UNSERIALIZE_PTR(opline->op2.zv); + } +# endif +# if ZEND_USE_ABS_JMP_ADDR + switch (opline->opcode) { + case ZEND_JMP: + case ZEND_GOTO: + case ZEND_FAST_CALL: + UNSERIALIZE_PTR(opline->op1.jmp_addr); + break; + case ZEND_JMPZNZ: + /* relative extended_value don't have to be changed */ + /* break omitted intentionally */ + case ZEND_JMPZ: + case ZEND_JMPNZ: + case ZEND_JMPZ_EX: + case ZEND_JMPNZ_EX: + case ZEND_JMP_SET: + case ZEND_COALESCE: + case ZEND_NEW: + case ZEND_FE_RESET_R: + case ZEND_FE_RESET_RW: + case ZEND_FE_FETCH_R: + case ZEND_FE_FETCH_RW: + case ZEND_ASSERT_CHECK: + UNSERIALIZE_PTR(opline->op2.jmp_addr); + break; + } +# endif + ZEND_VM_SET_OPCODE_HANDLER(opline); + opline++; + } + + if (op_array->arg_info) { + zend_arg_info *p, *end; + UNSERIALIZE_PTR(op_array->arg_info); + p = op_array->arg_info; + end = p + op_array->num_args; + if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { + p--; + } + if (op_array->fn_flags & ZEND_ACC_VARIADIC) { + end++; + } + while (p < end) { + if (!IS_UNSERIALIZED(p->name)) { + UNSERIALIZE_PTR(p->name); + } + if (!IS_UNSERIALIZED(p->class_name)) { + UNSERIALIZE_PTR(p->class_name); + } + p++; + } + } + + if (op_array->vars) { + zend_string **p, **end; + + UNSERIALIZE_PTR(op_array->vars); + p = op_array->vars; + end = p + op_array->last_var; + while (p < end) { + if (!IS_UNSERIALIZED(*p)) { + UNSERIALIZE_PTR(*p); + } + p++; + } + } + + UNSERIALIZE_PTR(op_array->function_name); + UNSERIALIZE_PTR(op_array->filename); + UNSERIALIZE_PTR(op_array->brk_cont_array); + UNSERIALIZE_PTR(op_array->scope); + UNSERIALIZE_PTR(op_array->doc_comment); + UNSERIALIZE_PTR(op_array->try_catch_array); + UNSERIALIZE_PTR(op_array->prototype); + } +} + +static void zend_file_cache_unserialize_func(zval *zv, + zend_persistent_script *script, + void *buf) +{ + zend_op_array *op_array; + + UNSERIALIZE_PTR(Z_PTR_P(zv)); + op_array = Z_PTR_P(zv); + zend_file_cache_unserialize_op_array(op_array, script, buf); +} + +static void zend_file_cache_unserialize_prop_info(zval *zv, + zend_persistent_script *script, + void *buf) +{ + if (!IS_UNSERIALIZED(Z_PTR_P(zv))) { + zend_property_info *prop; + + UNSERIALIZE_PTR(Z_PTR_P(zv)); + prop = Z_PTR_P(zv); + + if (prop->ce && !IS_UNSERIALIZED(prop->ce)) { + UNSERIALIZE_PTR(prop->ce); + } + if (prop->name && !IS_UNSERIALIZED(prop->name)) { + UNSERIALIZE_PTR(prop->name); + } + if (prop->doc_comment && !IS_UNSERIALIZED(prop->doc_comment)) { + UNSERIALIZE_PTR(prop->doc_comment); + } + } +} + +static void zend_file_cache_unserialize_class(zval *zv, + zend_persistent_script *script, + void *buf) +{ + zend_class_entry *ce; + + UNSERIALIZE_PTR(Z_PTR_P(zv)); + ce = Z_PTR_P(zv); + + UNSERIALIZE_PTR(ce->name); + zend_file_cache_unserialize_hash(&ce->function_table, script, buf, zend_file_cache_unserialize_func); + if (ce->default_properties_table) { + zval *p, *end; + + UNSERIALIZE_PTR(ce->default_properties_table); + p = ce->default_properties_table; + end = p + ce->default_properties_count; + while (p < end) { + zend_file_cache_unserialize_zval(p, script, buf); + p++; + } + } + if (ce->default_static_members_table) { + zval *p, *end; + + UNSERIALIZE_PTR(ce->default_static_members_table); + p = ce->default_static_members_table; + end = p + ce->default_static_members_count; + while (p < end) { + zend_file_cache_unserialize_zval(p, script, buf); + p++; + } + } + zend_file_cache_unserialize_hash(&ce->constants_table, script, buf, zend_file_cache_unserialize_zval); + UNSERIALIZE_PTR(ZEND_CE_FILENAME(ce)); + UNSERIALIZE_PTR(ZEND_CE_DOC_COMMENT(ce)); + zend_file_cache_unserialize_hash(&ce->properties_info, script, buf, zend_file_cache_unserialize_prop_info); + + if (ce->trait_aliases) { + zend_trait_alias **p, *q; + + UNSERIALIZE_PTR(ce->trait_aliases); + p = ce->trait_aliases; + + while (*p) { + UNSERIALIZE_PTR(*p); + q = *p; + + if (q->trait_method) { + zend_trait_method_reference *m; + + UNSERIALIZE_PTR(q->trait_method); + m = q->trait_method; + + if (m->method_name) { + UNSERIALIZE_PTR(m->method_name); + } + if (m->class_name) { + UNSERIALIZE_PTR(m->class_name); + } + } + + if (q->alias) { + UNSERIALIZE_PTR(q->alias); + } + p++; + } + } + + if (ce->trait_precedences) { + zend_trait_precedence **p, *q; + + UNSERIALIZE_PTR(ce->trait_precedences); + p = ce->trait_precedences; + + while (*p) { + UNSERIALIZE_PTR(*p); + q = *p; + + if (q->trait_method) { + zend_trait_method_reference *m; + + UNSERIALIZE_PTR(q->trait_method); + m = q->trait_method; + + if (m->method_name) { + UNSERIALIZE_PTR(m->method_name); + } + if (m->class_name) { + UNSERIALIZE_PTR(m->class_name); + } + } + + if (q->exclude_from_classes) { + zend_string **s; + + UNSERIALIZE_PTR(q->exclude_from_classes); + s = (zend_string**)q->exclude_from_classes; + + while (*s) { + UNSERIALIZE_PTR(*s); + s++; + } + } + p++; + } + } + + UNSERIALIZE_PTR(ce->parent); + UNSERIALIZE_PTR(ce->constructor); + UNSERIALIZE_PTR(ce->destructor); + UNSERIALIZE_PTR(ce->clone); + UNSERIALIZE_PTR(ce->__get); + UNSERIALIZE_PTR(ce->__set); + UNSERIALIZE_PTR(ce->__call); + UNSERIALIZE_PTR(ce->serialize_func); + UNSERIALIZE_PTR(ce->unserialize_func); + UNSERIALIZE_PTR(ce->__isset); + UNSERIALIZE_PTR(ce->__unset); + UNSERIALIZE_PTR(ce->__tostring); + UNSERIALIZE_PTR(ce->__callstatic); + UNSERIALIZE_PTR(ce->__debugInfo); +} + +static void zend_file_cache_unserialize(zend_persistent_script *script, + void *buf) +{ + script->mem = buf; + + UNSERIALIZE_PTR(script->full_path); + + zend_file_cache_unserialize_hash(&script->class_table, script, buf, zend_file_cache_unserialize_class); + zend_file_cache_unserialize_hash(&script->function_table, script, buf, zend_file_cache_unserialize_func); + zend_file_cache_unserialize_op_array(&script->main_op_array, script, buf); + + UNSERIALIZE_PTR(script->arena_mem); +} + +zend_persistent_script *zend_file_cache_script_load(zend_file_handle *file_handle) +{ + zend_string *full_path = file_handle->opened_path; + size_t len; + int fd; + char *filename; + zend_persistent_script *script; + zend_file_cache_metainfo info; + zend_accel_hash_entry *bucket; + void *mem, *checkpoint, *buf; + int cache_it = 1; + + if (!full_path) { + return NULL; + } + len = strlen(ZCG(accel_directives).file_cache); + filename = emalloc(len + 33 + full_path->len + sizeof(SUFFIX)); + memcpy(filename, ZCG(accel_directives).file_cache, len); + filename[len] = '/'; + memcpy(filename + len + 1, ZCG(system_id), 32); + memcpy(filename + len + 33, full_path->val, full_path->len); + memcpy(filename + len + 33 + full_path->len, SUFFIX, sizeof(SUFFIX)); + + fd = open(filename, O_RDONLY | O_BINARY); + if (fd < 0) { + efree(filename); + return NULL; + } + + if (zend_file_cache_flock(fd, LOCK_SH) != 0) { + close(fd); + efree(filename); + return NULL; + } + + if (read(fd, &info, sizeof(info)) != sizeof(info)) { + zend_accel_error(ACCEL_LOG_WARNING, "opcache cannot read from file '%s'\n", filename); + zend_file_cache_flock(fd, LOCK_UN); + close(fd); + unlink(filename); + efree(filename); + return NULL; + } + + /* verify header */ + if (memcmp(info.magic, "OPCACHE", 8) != 0 || + memcmp(info.system_id, ZCG(system_id), 32) != 0) { + zend_accel_error(ACCEL_LOG_WARNING, "opcache cannot read from file '%s'\n", filename); + zend_file_cache_flock(fd, LOCK_UN); + close(fd); + unlink(filename); + efree(filename); + return NULL; + } + + /* verify timestamp */ + if (ZCG(accel_directives).validate_timestamps && + zend_get_file_handle_timestamp(file_handle, NULL) != info.timestamp) { + if (zend_file_cache_flock(fd, LOCK_UN) != 0) { + zend_accel_error(ACCEL_LOG_WARNING, "opcache cannot unlock file '%s'\n", filename); + } + close(fd); + unlink(filename); + efree(filename); + return NULL; + } + + checkpoint = zend_arena_checkpoint(CG(arena)); +#ifdef __SSE2__ + /* Align to 64-byte boundary */ + mem = zend_arena_alloc(&CG(arena), info.mem_size + info.str_size + 64); + mem = (void*)(((zend_uintptr_t)mem + 63L) & ~63L); +#else + mem = zend_arena_alloc(&CG(arena), info.mem_size + info.str_size); +#endif + + if (read(fd, mem, info.mem_size + info.str_size) != (ssize_t)(info.mem_size + info.str_size)) { + zend_accel_error(ACCEL_LOG_WARNING, "opcache cannot read from file '%s'\n", filename); + zend_file_cache_flock(fd, LOCK_UN); + close(fd); + unlink(filename); + zend_arena_release(&CG(arena), checkpoint); + efree(filename); + return NULL; + } + if (zend_file_cache_flock(fd, LOCK_UN) != 0) { + zend_accel_error(ACCEL_LOG_WARNING, "opcache cannot unlock file '%s'\n", filename); + } + close(fd); + + /* verify checksum */ + if (ZCG(accel_directives).file_cache_consistency_checks && + zend_adler32(ADLER32_INIT, mem, info.mem_size + info.str_size) != info.checksum) { + zend_accel_error(ACCEL_LOG_WARNING, "corrupted file '%s'\n", filename); + unlink(filename); + zend_arena_release(&CG(arena), checkpoint); + efree(filename); + return NULL; + } + + if (!ZCG(accel_directives).file_cache_only) { + /* exclusive lock */ + zend_shared_alloc_lock(); + + /* Check if we still need to put the file into the cache (may be it was + * already stored by another process. This final check is done under + * exclusive lock) */ + bucket = zend_accel_hash_find_entry(&ZCSG(hash), full_path); + if (bucket) { + script = (zend_persistent_script *)bucket->data; + if (!script->corrupted) { + zend_shared_alloc_unlock(); + zend_arena_release(&CG(arena), checkpoint); + efree(filename); + return script; + } + } + + if (zend_accel_hash_is_full(&ZCSG(hash))) { + zend_accel_error(ACCEL_LOG_DEBUG, "No more entries in hash table!"); + ZSMMG(memory_exhausted) = 1; + zend_accel_schedule_restart_if_necessary(ACCEL_RESTART_HASH); + zend_shared_alloc_unlock(); + goto use_process_mem; + } + +#ifdef __SSE2__ + /* Align to 64-byte boundary */ + buf = zend_shared_alloc(info.mem_size + 64); + buf = (void*)(((zend_uintptr_t)buf + 63L) & ~63L); +#else + buf = zend_shared_alloc(info.mem_size); +#endif + + if (!buf) { + zend_accel_schedule_restart_if_necessary(ACCEL_RESTART_OOM); + zend_shared_alloc_unlock(); + goto use_process_mem; + } + memcpy(buf, mem, info.mem_size); + } else { +use_process_mem: + buf = mem; + cache_it = 0; + } + + ZCG(mem) = ((char*)mem + info.mem_size); + script = (zend_persistent_script*)((char*)buf + info.script_offset); + zend_file_cache_unserialize(script, buf); + + if (cache_it) { + script->dynamic_members.checksum = zend_accel_script_checksum(script); + + zend_accel_hash_update(&ZCSG(hash), script->full_path->val, script->full_path->len, 0, script); + + zend_shared_alloc_unlock(); + zend_arena_release(&CG(arena), checkpoint); + } + efree(filename); + + return script; +} + +void zend_file_cache_invalidate(zend_string *full_path) +{ + size_t len; + char *filename; + + len = strlen(ZCG(accel_directives).file_cache); + filename = emalloc(len + 33 + full_path->len + sizeof(SUFFIX)); + memcpy(filename, ZCG(accel_directives).file_cache, len); + filename[len] = '/'; + memcpy(filename + len + 1, ZCG(system_id), 32); + memcpy(filename + len + 33, full_path->val, full_path->len); + memcpy(filename + len + 33 + full_path->len, SUFFIX, sizeof(SUFFIX)); + + unlink(filename); + efree(filename); +} + +#endif /* HAVE_OPCACHE_FILE_CACHE */ diff --git a/ext/opcache/zend_file_cache.h b/ext/opcache/zend_file_cache.h new file mode 100644 index 0000000000..0660bfbc6f --- /dev/null +++ b/ext/opcache/zend_file_cache.h @@ -0,0 +1,26 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2015 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: Dmitry Stogov <dmitry@zend.com> | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_FILE_CACHE_H +#define ZEND_FILE_CACHE_H + +int zend_file_cache_script_store(zend_persistent_script *script); +zend_persistent_script *zend_file_cache_script_load(zend_file_handle *file_handle); +void zend_file_cache_invalidate(zend_string *full_path); + +#endif /* ZEND_FILE_CACHE_H */ diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 5597d382e8..4aee6fb628 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -870,7 +870,7 @@ zend_persistent_script *zend_accel_script_persist(zend_persistent_script *script zend_shared_alloc_clear_xlat_table(); zend_accel_store(script, sizeof(zend_persistent_script)); - if (*key) { + if (key && *key) { *key = zend_accel_memdup(*key, key_length + 1); } zend_accel_store_string(script->full_path); diff --git a/ext/opcache/zend_shared_alloc.c b/ext/opcache/zend_shared_alloc.c index d1925446fa..19b51e7c59 100644 --- a/ext/opcache/zend_shared_alloc.c +++ b/ext/opcache/zend_shared_alloc.c @@ -45,7 +45,6 @@ /* True globals */ /* old/new mapping. We can use true global even for ZTS because its usage is wrapped with exclusive lock anyway */ -static HashTable xlat_table; static const zend_shared_memory_handlers *g_shared_alloc_handler = NULL; static const char *g_shared_model; /* pointer to globals allocated in SHM and shared across processes */ @@ -325,7 +324,7 @@ int zend_shared_memdup_size(void *source, size_t size) { void *old_p; - if ((old_p = zend_hash_index_find_ptr(&xlat_table, (zend_ulong)source)) != NULL) { + if ((old_p = zend_hash_index_find_ptr(&ZCG(xlat_table), (zend_ulong)source)) != NULL) { /* we already duplicated this pointer */ return 0; } @@ -337,7 +336,7 @@ void *_zend_shared_memdup(void *source, size_t size, zend_bool free_source) { void *old_p, *retval; - if ((old_p = zend_hash_index_find_ptr(&xlat_table, (zend_ulong)source)) != NULL) { + if ((old_p = zend_hash_index_find_ptr(&ZCG(xlat_table), (zend_ulong)source)) != NULL) { /* we already duplicated this pointer */ return old_p; } @@ -393,21 +392,10 @@ void zend_shared_alloc_lock(void) #endif ZCG(locked) = 1; - - /* Prepare translation table - * - * Make it persistent so that it uses malloc() and allocated blocks - * won't be taken from space which is freed by efree in memdup. - * Otherwise it leads to false matches in memdup check. - */ - zend_hash_init(&xlat_table, 128, NULL, NULL, 1); } void zend_shared_alloc_unlock(void) { - /* Destroy translation table */ - zend_hash_destroy(&xlat_table); - ZCG(locked) = 0; #ifndef ZEND_WIN32 @@ -422,21 +410,39 @@ void zend_shared_alloc_unlock(void) #endif } +void zend_shared_alloc_init_xlat_table(void) +{ + + /* Prepare translation table + * + * Make it persistent so that it uses malloc() and allocated blocks + * won't be taken from space which is freed by efree in memdup. + * Otherwise it leads to false matches in memdup check. + */ + zend_hash_init(&ZCG(xlat_table), 128, NULL, NULL, 1); +} + +void zend_shared_alloc_destroy_xlat_table(void) +{ + /* Destroy translation table */ + zend_hash_destroy(&ZCG(xlat_table)); +} + void zend_shared_alloc_clear_xlat_table(void) { - zend_hash_clean(&xlat_table); + zend_hash_clean(&ZCG(xlat_table)); } void zend_shared_alloc_register_xlat_entry(const void *old, const void *new) { - zend_hash_index_update_ptr(&xlat_table, (zend_ulong)old, (void*)new); + zend_hash_index_update_ptr(&ZCG(xlat_table), (zend_ulong)old, (void*)new); } void *zend_shared_alloc_get_xlat_entry(const void *old) { void *retval; - if ((retval = zend_hash_index_find_ptr(&xlat_table, (zend_ulong)old)) == NULL) { + if ((retval = zend_hash_index_find_ptr(&ZCG(xlat_table), (zend_ulong)old)) == NULL) { return NULL; } return retval; diff --git a/ext/opcache/zend_shared_alloc.h b/ext/opcache/zend_shared_alloc.h index 1dbe6f8557..63947169b4 100644 --- a/ext/opcache/zend_shared_alloc.h +++ b/ext/opcache/zend_shared_alloc.h @@ -148,6 +148,8 @@ void zend_shared_alloc_unlock(void); /* returns the allocated size during lock.. void zend_shared_alloc_safe_unlock(void); /* old/new mapping functions */ +void zend_shared_alloc_init_xlat_table(void); +void zend_shared_alloc_destroy_xlat_table(void); void zend_shared_alloc_clear_xlat_table(void); void zend_shared_alloc_register_xlat_entry(const void *old, const void *new); void *zend_shared_alloc_get_xlat_entry(const void *old); diff --git a/ext/phar/tests/create_new_and_modify.phpt b/ext/phar/tests/create_new_and_modify.phpt index c03576cb2c..c924d74c68 100644 --- a/ext/phar/tests/create_new_and_modify.phpt +++ b/ext/phar/tests/create_new_and_modify.phpt @@ -23,7 +23,7 @@ include $pname . '/a.php'; if (function_exists("opcache_get_status")) { $status = opcache_get_status(); - if ($status["opcache_enabled"]) { + if ($status["opcache_enabled"] || (isset($status["file_cache_only"]) && $status["file_cache_only"])) { ini_set("opcache.revalidate_freq", "0"); sleep(2); } diff --git a/ext/phar/tests/tar/create_new_and_modify.phpt b/ext/phar/tests/tar/create_new_and_modify.phpt index 905bfabc82..7d8bcb17da 100644 --- a/ext/phar/tests/tar/create_new_and_modify.phpt +++ b/ext/phar/tests/tar/create_new_and_modify.phpt @@ -17,7 +17,7 @@ file_put_contents($pname . '/a.php', "brand new!\n"); if (function_exists("opcache_get_status")) { $status = opcache_get_status(); - if ($status["opcache_enabled"]) { + if ($status["opcache_enabled"] || (isset($status["file_cache_only"]) && $status["file_cache_only"])) { ini_set("opcache.revalidate_freq", "0"); sleep(2); } diff --git a/ext/phar/tests/zip/create_new_and_modify.phpt b/ext/phar/tests/zip/create_new_and_modify.phpt index 55d69cca0e..c49ec513ff 100644 --- a/ext/phar/tests/zip/create_new_and_modify.phpt +++ b/ext/phar/tests/zip/create_new_and_modify.phpt @@ -17,7 +17,7 @@ file_put_contents($pname . '/a.php', "brand new!\n"); if (function_exists("opcache_get_status")) { $status = opcache_get_status(); - if ($status["opcache_enabled"]) { + if ($status["opcache_enabled"] || (isset($status["file_cache_only"]) && $status["file_cache_only"])) { ini_set("opcache.revalidate_freq", "0"); sleep(2); } diff --git a/run-tests.php b/run-tests.php index 72781e9559..897500a58b 100755 --- a/run-tests.php +++ b/run-tests.php @@ -243,9 +243,11 @@ $ini_overwrites = array( 'opcache.file_update_protection=0', ); +$no_file_cache = '-d opcache.file_cache= -d opcache.file_cache_only=0'; + function write_information($show_html) { - global $cwd, $php, $php_cgi, $php_info, $user_tests, $ini_overwrites, $pass_options, $exts_to_test, $leak_check, $valgrind_header; + global $cwd, $php, $php_cgi, $php_info, $user_tests, $ini_overwrites, $pass_options, $exts_to_test, $leak_check, $valgrind_header, $no_file_cache; // Get info from php $info_file = __DIR__ . '/run-test-info.php'; @@ -261,11 +263,11 @@ More .INIs : " , (function_exists(\'php_ini_scanned_files\') ? str_replace("\n" $info_params = array(); settings2array($ini_overwrites, $info_params); settings2params($info_params); - $php_info = `$php $pass_options $info_params "$info_file"`; + $php_info = `$php $pass_options $info_params $no_file_cache "$info_file"`; define('TESTED_PHP_VERSION', `$php -n -r "echo PHP_VERSION;"`); if ($php_cgi && $php != $php_cgi) { - $php_info_cgi = `$php_cgi $pass_options $info_params -q "$info_file"`; + $php_info_cgi = `$php_cgi $pass_options $info_params $no_file_cache -q "$info_file"`; $php_info_sep = "\n---------------------------------------------------------------------"; $php_cgi_info = "$php_info_sep\nPHP : $php_cgi $php_info_cgi$php_info_sep"; } else { @@ -276,7 +278,7 @@ More .INIs : " , (function_exists(\'php_ini_scanned_files\') ? str_replace("\n" // load list of enabled extensions save_text($info_file, '<?php echo join(",", get_loaded_extensions()); ?>'); - $exts_to_test = explode(',',`$php $pass_options $info_params "$info_file"`); + $exts_to_test = explode(',',`$php $pass_options $info_params $no_file_cache "$info_file"`); // check for extensions that need special handling and regenerate $info_params_ex = array( 'session' => array('session.auto_start=0'), @@ -1195,6 +1197,7 @@ function run_test($php, $file, $env) global $valgrind_version; global $JUNIT; global $SHOW_ONLY_GROUPS; + global $no_file_cache; $temp_filenames = null; $org_file = $file; @@ -1519,7 +1522,7 @@ TEST $file junit_start_timer($shortname); - $output = system_with_timeout("$extra $php $pass_options -q $ini_settings -d display_errors=0 \"$test_skipif\"", $env); + $output = system_with_timeout("$extra $php $pass_options -q $ini_settings $no_file_cache -d display_errors=0 \"$test_skipif\"", $env); junit_finish_timer($shortname); @@ -1835,7 +1838,7 @@ COMMAND $cmd settings2params($clean_params); $extra = substr(PHP_OS, 0, 3) !== "WIN" ? "unset REQUEST_METHOD; unset QUERY_STRING; unset PATH_TRANSLATED; unset SCRIPT_FILENAME; unset REQUEST_METHOD;": ""; - system_with_timeout("$extra $php $pass_options -q $clean_params \"$test_clean\"", $env); + system_with_timeout("$extra $php $pass_options -q $clean_params $no_file_cache \"$test_clean\"", $env); } if (!$cfg['keep']['clean']) { |