Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ext/opcache/ZendAccelerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ typedef struct _zend_accel_directives {
zend_bool file_override_enabled;
zend_bool enable_cli;
zend_bool validate_permission;
zend_bool allow_direct_exec_opcode;
zend_bool prohibit_different_version_opcode;
#ifndef ZEND_WIN32
zend_bool validate_root;
#endif
Expand Down
2 changes: 1 addition & 1 deletion ext/opcache/opcache.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ function opcache_reset(): bool {}

function opcache_get_status(bool $include_scripts = true): array|false {}

function opcache_compile_file(string $filename): bool {}
function opcache_compile_file(string $filename, string $opcode_file = null): bool {}

function opcache_invalidate(string $filename, bool $force = false): bool {}

Expand Down
1 change: 1 addition & 0 deletions ext/opcache/opcache_arginfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_opcache_compile_file, 0, 1, _IS_BOOL, 0)
ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, opcode_file, IS_STRING, 0, "null")
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_opcache_invalidate, 0, 1, _IS_BOOL, 0)
Expand Down
62 changes: 62 additions & 0 deletions ext/opcache/tests/opcode_store_specified_file.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
--TEST--
Compile php and store opcode to the specified file
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.file_cache_only=1
opcache.optimization_level=-1
opcache.jit=0
opcache.file_cache={PWD}/opcode_store_specified_file_tmp
opcache.allow_direct_exec_opcode=1
--SKIPIF--
<?php require_once('skipif.inc');
$cache_dir = __DIR__ . DIRECTORY_SEPARATOR. 'opcode_store_specified_file_tmp';
if(!file_exists($cache_dir)) {
mkdir($cache_dir);
}
?>
--FILE--
<?php

if(!isset($is_include)) {
$file = __FILE__;
$ret = opcache_compile_file($file, __DIR__ .DIRECTORY_SEPARATOR. 'opcode_store_specified_file.bin.php');
var_dump($ret);
if(!$ret) {
exit;
}
$is_include = 1;
include_once __DIR__ . DIRECTORY_SEPARATOR. 'opcode_store_specified_file.bin.php';
var_dump('include end');
exit;
} else {
function message() {
var_dump('opcode include');
}
message();
}
?>
--CLEAN--
<?php
function rmdirr($dir_path) {
$d = dir($dir_path);
while(false !== ($f = $d->read())) {
if($f === '.' || $f === '..') {
continue;
}
$path = $dir_path. DIRECTORY_SEPARATOR. $f;
if(is_dir($path)) {
rmdirr($path);
} else {
unlink($path);
}
}
rmdir($dir_path);
}
rmdirr(__DIR__.DIRECTORY_SEPARATOR.'opcode_store_specified_file_tmp');
unlink(__DIR__ .DIRECTORY_SEPARATOR. 'opcode_store_specified_file.bin.php');
?>
--EXPECT--
bool(true)
string(14) "opcode include"
string(11) "include end"
35 changes: 32 additions & 3 deletions ext/opcache/zend_accelerator_module.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
#include "ext/standard/info.h"
#include "ext/standard/php_filestat.h"
#include "opcache_arginfo.h"

#include "zend_file_cache.h"
#if HAVE_JIT
#include "jit/zend_jit.h"
#endif
Expand Down Expand Up @@ -264,6 +264,9 @@ ZEND_INI_BEGIN()
STD_PHP_INI_ENTRY("opcache.error_log" , "" , PHP_INI_SYSTEM, OnUpdateString, accel_directives.error_log, zend_accel_globals, accel_globals)
STD_PHP_INI_ENTRY("opcache.restrict_api" , "" , PHP_INI_SYSTEM, OnUpdateString, accel_directives.restrict_api, zend_accel_globals, accel_globals)

STD_PHP_INI_ENTRY("opcache.allow_direct_exec_opcode" , "0" , PHP_INI_SYSTEM, OnUpdateBool, accel_directives.allow_direct_exec_opcode, zend_accel_globals, accel_globals)
STD_PHP_INI_ENTRY("opcache.prohibit_different_version_opcode" , "1" , PHP_INI_SYSTEM, OnUpdateBool, accel_directives.prohibit_different_version_opcode, zend_accel_globals, accel_globals)

#ifndef ZEND_WIN32
STD_PHP_INI_ENTRY("opcache.lockfile_path" , "/tmp" , PHP_INI_SYSTEM, OnUpdateString, accel_directives.lockfile_path, zend_accel_globals, accel_globals)
#else
Expand Down Expand Up @@ -719,6 +722,8 @@ ZEND_FUNCTION(opcache_get_configuration)
array_init(&directives);
add_assoc_bool(&directives, "opcache.enable", ZCG(enabled));
add_assoc_bool(&directives, "opcache.enable_cli", ZCG(accel_directives).enable_cli);
add_assoc_bool(&directives, "opcache.allow_direct_exec_opcode", ZCG(accel_directives).allow_direct_exec_opcode);
add_assoc_bool(&directives, "opcache.prohibit_different_version_opcode", ZCG(accel_directives).prohibit_different_version_opcode);
add_assoc_bool(&directives, "opcache.use_cwd", ZCG(accel_directives).use_cwd);
add_assoc_bool(&directives, "opcache.validate_timestamps", ZCG(accel_directives).validate_timestamps);
add_assoc_bool(&directives, "opcache.validate_permission", ZCG(accel_directives).validate_permission);
Expand Down Expand Up @@ -860,12 +865,14 @@ ZEND_FUNCTION(opcache_compile_file)
{
char *script_name;
size_t script_name_len;
char *opcode_file = NULL;
size_t opecode_file_len;
zend_file_handle handle;
zend_op_array *op_array = NULL;
zend_execute_data *orig_execute_data = NULL;
uint32_t orig_compiler_options;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &script_name, &script_name_len) == FAILURE) {
if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|s", &script_name, &script_name_len, &opcode_file, &opecode_file_len) == FAILURE) {
RETURN_THROWS();
}

Expand All @@ -874,6 +881,23 @@ ZEND_FUNCTION(opcache_compile_file)
RETURN_FALSE;
}

if(opcode_file != NULL) {
if(!file_cache_only) {
zend_error(E_NOTICE, ACCELERATOR_PRODUCT_NAME " has been disable 'opcache.file_cache_only' option in php.ini, can't store opcode to file");
RETURN_FALSE;
}
if(access(opcode_file, F_OK) != -1) {
zend_error(E_WARNING, "File %s already exists", opcode_file);
RETURN_FALSE;
}
#ifdef HAVE_JIT
if (JIT_G(on)) {
zend_error(E_NOTICE, ACCELERATOR_PRODUCT_NAME " has been enabled JIT, can't store opcode to file");
RETURN_FALSE;
}
#endif
}

zend_stream_init_filename(&handle, script_name);

orig_execute_data = EG(current_execute_data);
Expand All @@ -899,7 +923,12 @@ ZEND_FUNCTION(opcache_compile_file)
if(op_array != NULL) {
destroy_op_array(op_array);
efree(op_array);
RETVAL_TRUE;
if(opcode_file != NULL &&
zend_opcache_copy_opcode_cache_file(script_name, script_name_len, opcode_file) == FAILURE) {
RETVAL_FALSE;
} else {
RETVAL_TRUE;
}
} else {
RETVAL_FALSE;
}
Expand Down
138 changes: 133 additions & 5 deletions ext/opcache/zend_file_cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ static int zend_file_cache_flock(int fd, int type)
#endif

#define SUFFIX ".bin"
#define T_PHP_OPCODE "<?phpo"
#define T_PHP_OPCODE_LEN 6

#define IS_SERIALIZED_INTERNED(ptr) \
((size_t)(ptr) & Z_UL(1))
Expand Down Expand Up @@ -196,6 +198,11 @@ typedef struct _zend_file_cache_metainfo {
uint32_t checksum;
} zend_file_cache_metainfo;

typedef struct _zend_opcode_file_taginfo {
char tagname[T_PHP_OPCODE_LEN];
uint32_t php_version_id;
} zend_opcode_file_taginfo;

static int zend_file_cache_mkdir(char *filename, size_t start)
{
char *s = filename + start;
Expand Down Expand Up @@ -1641,6 +1648,109 @@ static void zend_file_cache_unserialize(zend_persistent_script *script,
UNSERIALIZE_PTR(script->arena_mem);
}

static int zend_opcache_check_opcode_file_tag(int fd, char* filename) {
zend_opcode_file_taginfo taginfo;
uint32_t php_version_id = (uint32_t)PHP_VERSION_ID;

int rn = read(fd, &taginfo, sizeof(zend_opcode_file_taginfo));
if(rn != sizeof(taginfo)) {
return -1;
}

if(memcmp(taginfo.tagname, T_PHP_OPCODE, T_PHP_OPCODE_LEN) != 0) {
return -1;
}

if(memcmp(&taginfo.php_version_id, &php_version_id, sizeof(php_version_id)) != 0) {
int version_errno = E_WARNING;
if(ZCG(accel_directives).prohibit_different_version_opcode) {
version_errno = E_ERROR;
}
zend_error(version_errno, "Opcode file '%s' (version id: %d) is compiled by different versions of PHP (version id: %d), it's not safe and not suggest", filename, taginfo.php_version_id, php_version_id);
}
return 0;
}

void zend_opcache_copy_file_error(char* msg, char* file1, char *file2, int free) {
zend_error(E_WARNING, msg, file1);
zend_file_cache_unlink(file2);
zend_file_cache_unlink(file1);
if(free) {
efree(file1);
} else {
efree(file2);
}
}

int zend_opcache_copy_opcode_cache_file(char *src_filename, size_t src_filename_len, char* opcode_file) {
zend_string *full_path;
char full_path_buff[MAXPATHLEN];
char *cache_file;
int fd_new;
int fd_cache;
char buf[8192];
int buf_len;
int chunksize = sizeof(buf);
int write_size = 0;

zend_opcode_file_taginfo taginfo = {.tagname=T_PHP_OPCODE, .php_version_id= (uint32_t)PHP_VERSION_ID};

VCWD_REALPATH(src_filename,full_path_buff);
full_path = zend_string_init(full_path_buff, strlen(full_path_buff), 0);
cache_file = zend_file_cache_get_bin_file_path(full_path);
efree(full_path);
fd_new = zend_file_cache_open(opcode_file, O_CREAT | O_EXCL | O_RDWR | O_BINARY, S_IRUSR | S_IWUSR);
if(fd_new < 0) {
zend_opcache_copy_file_error("create file '%s' error\n", opcode_file, cache_file, 0);
return FAILURE;
}

if(write(fd_new, &taginfo, sizeof(taginfo)) != sizeof(taginfo)) {
zend_opcache_copy_file_error("write taginfo to file '%s' error\n", opcode_file, cache_file, 0);
close(fd_new);
return FAILURE;
}

fd_cache = zend_file_cache_open(cache_file, O_RDONLY | O_BINARY);
if(fd_cache < 0) {
zend_opcache_copy_file_error("can not open opcode cache file '%s' error\n",cache_file, opcode_file, 1);
close(fd_new);
return FAILURE;
}

while(1) {
buf_len = read(fd_cache, buf, chunksize);
if(buf_len < 0) {
zend_opcache_copy_file_error("read opcode cache file '%s' error\n", cache_file, opcode_file, 1);
goto cleanup;
}
if(buf_len == 0) {
break;
}
write_size = write(fd_new, buf, buf_len);
if(write_size != buf_len) {
zend_opcache_copy_file_error("write file '%s' error\n", opcode_file, cache_file, 0);
goto cleanup;
}
if(buf_len < chunksize) {
break;
}
}

close(fd_new);
close(fd_cache);

zend_file_cache_unlink(cache_file);
efree(cache_file);

return SUCCESS;

cleanup:
close(fd_new);
close(fd_cache);
return FAILURE;
}

zend_persistent_script *zend_file_cache_script_load(zend_file_handle *file_handle)
{
zend_string *full_path = file_handle->opened_path;
Expand All @@ -1653,13 +1763,29 @@ zend_persistent_script *zend_file_cache_script_load(zend_file_handle *file_handl
int cache_it = 1;
unsigned int actual_checksum;
int ok;
int is_opfile = 0;

if (!full_path) {
return NULL;
}
filename = zend_file_cache_get_bin_file_path(full_path);

fd = zend_file_cache_open(filename, O_RDONLY | O_BINARY);
if(ZCG(accel_directives).allow_direct_exec_opcode) {
fd = zend_file_cache_open(ZSTR_VAL(full_path), O_RDONLY | O_BINARY);

if(fd < 0 || zend_opcache_check_opcode_file_tag(fd, ZSTR_VAL(full_path)) < 0) {
close(fd);
fd = -1;
} else {
is_opfile = 1;
}
} else {
fd = -1;
}

if(fd < 0) {
filename = zend_file_cache_get_bin_file_path(full_path);
fd = zend_file_cache_open(filename, O_RDONLY | O_BINARY);
}
if (fd < 0) {
efree(filename);
return NULL;
Expand Down Expand Up @@ -1689,7 +1815,7 @@ zend_persistent_script *zend_file_cache_script_load(zend_file_handle *file_handl
efree(filename);
return NULL;
}
if (memcmp(info.system_id, zend_system_id, 32) != 0) {
if (!is_opfile && memcmp(info.system_id, zend_system_id, 32) != 0) {
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);
Expand All @@ -1699,7 +1825,7 @@ zend_persistent_script *zend_file_cache_script_load(zend_file_handle *file_handl
}

/* verify timestamp */
if (ZCG(accel_directives).validate_timestamps &&
if (!is_opfile && 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);
Expand Down Expand Up @@ -1828,7 +1954,9 @@ zend_persistent_script *zend_file_cache_script_load(zend_file_handle *file_handl

zend_arena_release(&CG(arena), checkpoint);
}
efree(filename);
if(!is_opfile) {
efree(filename);
}

return script;
}
Expand Down
2 changes: 1 addition & 1 deletion ext/opcache/zend_file_cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@
int zend_file_cache_script_store(zend_persistent_script *script, int in_shm);
zend_persistent_script *zend_file_cache_script_load(zend_file_handle *file_handle);
void zend_file_cache_invalidate(zend_string *full_path);

int zend_opcache_copy_opcode_cache_file(char *src_filename, size_t src_filename_len, char* opcode_file);
#endif /* ZEND_FILE_CACHE_H */