diff --git a/UPGRADING b/UPGRADING index a336173ffd8b0..06e2412983694 100644 --- a/UPGRADING +++ b/UPGRADING @@ -113,6 +113,14 @@ PHP 8.5 UPGRADE NOTES readline_callback_handler_install() have been changed to true, rather than bool. +- Opcache: + . Introduce opcache.file_cache_read_only to support a read-only + `opcache.file_cache` directory, for use with read-only Docker containers. + Best used with `opcache.validate_timestamps=0`, `opcache.enable_file_override=1` + and `opcache.file_cache_consistency_checks=0`. + Note: A cache generated with a different build of PHP, a different file path, + or different settings (including which extensions are loaded), may be ignored. + ======================================== 10. New Global Constants ======================================== diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index 406c539f44a27..678a90ce05686 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -46,6 +46,7 @@ #include "zend_accelerator_util_funcs.h" #include "zend_accelerator_hash.h" #include "zend_file_cache.h" +#include "zend_system_id.h" #include "ext/pcre/php_pcre.h" #include "ext/standard/basic_functions.h" @@ -3300,6 +3301,54 @@ static zend_result accel_post_startup(void) #endif accel_shared_globals = calloc(1, sizeof(zend_accel_shared_globals)); } + + /* opcache.file_cache_read_only should only be enabled when all script files are read-only */ + int file_cache_access_mode = 0; + + if (ZCG(accel_directives).file_cache_read_only) { + zend_accel_error(ACCEL_LOG_INFO, "opcache.file_cache is in read-only mode"); + + if (!ZCG(accel_directives).file_cache) { + accel_startup_ok = false; + zend_accel_error_noreturn(ACCEL_LOG_FATAL, "opcache.file_cache_read_only is set without a proper setting of opcache.file_cache"); + return SUCCESS; + } + + /* opcache.file_cache is read only, so ensure the directory is readable */ +#ifndef ZEND_WIN32 + file_cache_access_mode = R_OK | X_OK; +#else + file_cache_access_mode = 04; // Read access +#endif + } else { + /* opcache.file_cache isn't read only, so ensure the directory is writable */ +#ifndef ZEND_WIN32 + file_cache_access_mode = R_OK | W_OK | X_OK; +#else + file_cache_access_mode = 06; // Read and write access +#endif + } + + if ( ZCG(accel_directives).file_cache ) { + zend_accel_error(ACCEL_LOG_INFO, "opcache.file_cache running with PHP build ID: %s", zend_system_id); + + zend_stat_t buf = {0}; + + if (!IS_ABSOLUTE_PATH(ZCG(accel_directives).file_cache, strlen(ZCG(accel_directives).file_cache)) || + zend_stat(ZCG(accel_directives).file_cache, &buf) != 0 || + !S_ISDIR(buf.st_mode) || +#ifndef ZEND_WIN32 + access(ZCG(accel_directives).file_cache, file_cache_access_mode) != 0 +#else + _access(ZCG(accel_directives).file_cache, file_cache_access_mode) != 0 +#endif + ) { + accel_startup_ok = false; + zend_accel_error_noreturn(ACCEL_LOG_FATAL, "opcache.file_cache must be a full path of an accessible directory"); + return SUCCESS; + } + } + #if ENABLE_FILE_CACHE_FALLBACK file_cache_fallback: #endif diff --git a/ext/opcache/ZendAccelerator.h b/ext/opcache/ZendAccelerator.h index 162892bf2c279..3f7eb3bdf008f 100644 --- a/ext/opcache/ZendAccelerator.h +++ b/ext/opcache/ZendAccelerator.h @@ -177,6 +177,7 @@ typedef struct _zend_accel_directives { char *lockfile_path; #endif char *file_cache; + bool file_cache_read_only; bool file_cache_only; bool file_cache_consistency_checks; #if ENABLE_FILE_CACHE_FALLBACK diff --git a/ext/opcache/tests/zzz_basic_logging.phpt b/ext/opcache/tests/zzz_basic_logging.phpt index 7490fadc62ef6..a53c6a8db9de1 100644 --- a/ext/opcache/tests/zzz_basic_logging.phpt +++ b/ext/opcache/tests/zzz_basic_logging.phpt @@ -6,6 +6,7 @@ outputs the correct logging at the highest log_verbosity_level --INI-- opcache.enable=1 opcache.enable_cli=1 +opcache.file_cache= opcache.file_cache_only=0 opcache.error_log= opcache.log_verbosity_level=4 diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c index 2ed8155eff773..1b62b757b87d2 100644 --- a/ext/opcache/zend_accelerator_module.c +++ b/ext/opcache/zend_accelerator_module.c @@ -165,20 +165,6 @@ static ZEND_INI_MH(OnUpdateFileCache) if (new_value) { if (!ZSTR_LEN(new_value)) { new_value = NULL; - } else { - zend_stat_t buf = {0}; - - if (!IS_ABSOLUTE_PATH(ZSTR_VAL(new_value), ZSTR_LEN(new_value)) || - zend_stat(ZSTR_VAL(new_value), &buf) != 0 || - !S_ISDIR(buf.st_mode) || -#ifndef ZEND_WIN32 - access(ZSTR_VAL(new_value), R_OK | W_OK | X_OK) != 0) { -#else - _access(ZSTR_VAL(new_value), 06) != 0) { -#endif - zend_accel_error(ACCEL_LOG_WARNING, "opcache.file_cache must be a full path of accessible directory.\n"); - new_value = NULL; - } } } OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage); @@ -312,6 +298,7 @@ ZEND_INI_BEGIN() #endif STD_PHP_INI_ENTRY("opcache.file_cache" , NULL , PHP_INI_SYSTEM, OnUpdateFileCache, accel_directives.file_cache, zend_accel_globals, accel_globals) + STD_PHP_INI_BOOLEAN("opcache.file_cache_read_only" , "0" , PHP_INI_SYSTEM, OnUpdateBool, accel_directives.file_cache_read_only, zend_accel_globals, accel_globals) STD_PHP_INI_BOOLEAN("opcache.file_cache_only" , "0" , PHP_INI_SYSTEM, OnUpdateBool, accel_directives.file_cache_only, zend_accel_globals, accel_globals) STD_PHP_INI_BOOLEAN("opcache.file_cache_consistency_checks" , "1" , PHP_INI_SYSTEM, OnUpdateBool, accel_directives.file_cache_consistency_checks, zend_accel_globals, accel_globals) #if ENABLE_FILE_CACHE_FALLBACK @@ -823,6 +810,7 @@ ZEND_FUNCTION(opcache_get_configuration) #endif add_assoc_string(&directives, "opcache.file_cache", ZCG(accel_directives).file_cache ? ZCG(accel_directives).file_cache : ""); + add_assoc_bool(&directives, "opcache.file_cache_read_only", ZCG(accel_directives).file_cache_read_only); 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); #if ENABLE_FILE_CACHE_FALLBACK diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index 736d644516a04..fe54c477cea77 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -1100,6 +1100,10 @@ int zend_file_cache_script_store(zend_persistent_script *script, bool in_shm) } #endif + if (ZCG(accel_directives).file_cache_read_only) { + return FAILURE; + } + filename = zend_file_cache_get_bin_file_path(script->script.filename); if (zend_file_cache_mkdir(filename, strlen(ZCG(accel_directives).file_cache)) != SUCCESS) { @@ -1843,7 +1847,9 @@ zend_persistent_script *zend_file_cache_script_load(zend_file_handle *file_handl zend_accel_error(ACCEL_LOG_WARNING, "opcache cannot read from file '%s' (info)\n", filename); zend_file_cache_flock(fd, LOCK_UN); close(fd); - zend_file_cache_unlink(filename); + if (!ZCG(accel_directives).file_cache_read_only) { + zend_file_cache_unlink(filename); + } efree(filename); return NULL; } @@ -1853,7 +1859,9 @@ zend_persistent_script *zend_file_cache_script_load(zend_file_handle *file_handl zend_accel_error(ACCEL_LOG_WARNING, "opcache cannot read from file '%s' (wrong header)\n", filename); zend_file_cache_flock(fd, LOCK_UN); close(fd); - zend_file_cache_unlink(filename); + if (!ZCG(accel_directives).file_cache_read_only) { + zend_file_cache_unlink(filename); + } efree(filename); return NULL; } @@ -1861,7 +1869,9 @@ zend_persistent_script *zend_file_cache_script_load(zend_file_handle *file_handl zend_accel_error(ACCEL_LOG_WARNING, "opcache cannot read from file '%s' (wrong \"system_id\")\n", filename); zend_file_cache_flock(fd, LOCK_UN); close(fd); - zend_file_cache_unlink(filename); + if (!ZCG(accel_directives).file_cache_read_only) { + zend_file_cache_unlink(filename); + } efree(filename); return NULL; } @@ -1873,7 +1883,9 @@ zend_persistent_script *zend_file_cache_script_load(zend_file_handle *file_handl zend_accel_error(ACCEL_LOG_WARNING, "opcache cannot unlock file '%s'\n", filename); } close(fd); - zend_file_cache_unlink(filename); + if (!ZCG(accel_directives).file_cache_read_only) { + zend_file_cache_unlink(filename); + } efree(filename); return NULL; } @@ -1891,7 +1903,9 @@ zend_persistent_script *zend_file_cache_script_load(zend_file_handle *file_handl zend_accel_error(ACCEL_LOG_WARNING, "opcache cannot read from file '%s' (mem)\n", filename); zend_file_cache_flock(fd, LOCK_UN); close(fd); - zend_file_cache_unlink(filename); + if (!ZCG(accel_directives).file_cache_read_only) { + zend_file_cache_unlink(filename); + } zend_arena_release(&CG(arena), checkpoint); efree(filename); return NULL; @@ -1905,7 +1919,9 @@ zend_persistent_script *zend_file_cache_script_load(zend_file_handle *file_handl if (ZCG(accel_directives).file_cache_consistency_checks && (actual_checksum = zend_adler32(ADLER32_INIT, mem, info.mem_size + info.str_size)) != info.checksum) { zend_accel_error(ACCEL_LOG_WARNING, "corrupted file '%s' excepted checksum: 0x%08x actual checksum: 0x%08x\n", filename, info.checksum, actual_checksum); - zend_file_cache_unlink(filename); + if (!ZCG(accel_directives).file_cache_read_only) { + zend_file_cache_unlink(filename); + } zend_arena_release(&CG(arena), checkpoint); efree(filename); return NULL; @@ -1997,6 +2013,10 @@ zend_persistent_script *zend_file_cache_script_load(zend_file_handle *file_handl void zend_file_cache_invalidate(zend_string *full_path) { + if (ZCG(accel_directives).file_cache_read_only) { + return; + } + char *filename; filename = zend_file_cache_get_bin_file_path(full_path); diff --git a/php.ini-development b/php.ini-development index 5adb11d215697..31353facdb394 100644 --- a/php.ini-development +++ b/php.ini-development @@ -1766,6 +1766,15 @@ ldap.max_links = -1 ; SHM reset. The default "" disables file based caching. ;opcache.file_cache= +; Enables or disables read-only mode for the second level cache directory. +; It should improve performance for read-only containers, +; when the cache is pre-warmed and packaged alongside the application. +; Best used with `opcache.validate_timestamps=0`, `opcache.enable_file_override=1` +; and `opcache.file_cache_consistency_checks=0`. +; Note: A cache generated with a different build of PHP, a different file path, +; or different settings (including which extensions are loaded), may be ignored. +;opcache.file_cache_read_only=0 + ; Enables or disables opcode caching in shared memory. ;opcache.file_cache_only=0 diff --git a/php.ini-production b/php.ini-production index e4ffc1084b13a..e4fbd6e785f86 100644 --- a/php.ini-production +++ b/php.ini-production @@ -1768,6 +1768,15 @@ ldap.max_links = -1 ; SHM reset. The default "" disables file based caching. ;opcache.file_cache= +; Enables or disables read-only mode for the second level cache directory. +; It should improve performance for read-only containers, +; when the cache is pre-warmed and packaged alongside the application. +; Best used with `opcache.validate_timestamps=0`, `opcache.enable_file_override=1` +; and `opcache.file_cache_consistency_checks=0`. +; Note: A cache generated with a different build of PHP, a different file path, +; or different settings (including which extensions are loaded), may be ignored. +;opcache.file_cache_read_only=0 + ; Enables or disables opcode caching in shared memory. ;opcache.file_cache_only=0