This is proof of concept code to invalidate the opcode cache entries using fanotify events. This fails because fanotify doesn't support rename events. diff --git a/ZendAccelerator.c b/ZendAccelerator.c index 4406400..17f7cff 100644 --- a/ZendAccelerator.c +++ b/ZendAccelerator.c @@ -29,6 +29,7 @@ #include "zend_shared_alloc.h" #include "zend_accelerator_module.h" #include "zend_accelerator_blacklist.h" +#include "zend_accelerator_fsmonitor.h" #include "zend_list.h" #include "zend_execute.h" #include "main/SAPI.h" @@ -1048,32 +1049,21 @@ static inline char *accel_make_persistent_key(zend_file_handle *file_handle, int return accel_make_persistent_key_ex(file_handle, strlen(file_handle->filename), key_len TSRMLS_CC); } -int zend_accel_invalidate(const char *filename, int filename_len, zend_bool force TSRMLS_DC) +int zend_accel_invalidate_real(char *filename, int filename_len, zend_bool force TSRMLS_DC) { - char *realpath; zend_persistent_script *persistent_script; if (!ZCG(enabled) || !accel_startup_ok || !ZCSG(accelerator_enabled) || accelerator_shm_read_lock(TSRMLS_C) != SUCCESS) { return FAILURE; } -#if ZEND_EXTENSION_API_NO < PHP_5_3_X_API_NO - realpath = accel_php_resolve_path(filename, filename_len, ZCG(include_path) TSRMLS_CC); -#else - realpath = accelerator_orig_zend_resolve_path(filename, filename_len TSRMLS_CC); -#endif - - if (!realpath) { - return FAILURE; - } - - persistent_script = zend_accel_hash_find(&ZCSG(hash), realpath, strlen(realpath) + 1); + persistent_script = zend_accel_hash_find(&ZCSG(hash), filename, filename_len + 1); if (persistent_script && !persistent_script->corrupted) { zend_file_handle file_handle; file_handle.type = ZEND_HANDLE_FILENAME; - file_handle.filename = realpath; - file_handle.opened_path = realpath; + file_handle.filename = filename; + file_handle.opened_path = filename; if (force || !ZCG(accel_directives).validate_timestamps || @@ -1096,11 +1086,34 @@ int zend_accel_invalidate(const char *filename, int filename_len, zend_bool forc } accelerator_shm_read_unlock(TSRMLS_C); - efree(realpath); return SUCCESS; } +int zend_accel_invalidate(const char *filename, int filename_len, zend_bool force TSRMLS_DC) +{ + char *realpath; + zend_persistent_script *persistent_script; + + if (!ZCG(enabled) || !accel_startup_ok || !ZCSG(accelerator_enabled) || accelerator_shm_read_lock(TSRMLS_C) != SUCCESS) { + return FAILURE; + } + +#if ZEND_EXTENSION_API_NO < PHP_5_3_X_API_NO + realpath = accel_php_resolve_path(filename, filename_len, ZCG(include_path) TSRMLS_CC); +#else + realpath = accelerator_orig_zend_resolve_path(filename, filename_len TSRMLS_CC); +#endif + + if (!realpath) { + return FAILURE; + } + + zend_accel_invalidate_real(realpath, strlen(realpath), force); + + efree(realpath); +} + /* Adds another key for existing cached script */ static void zend_accel_add_key(char *key, unsigned int key_length, zend_accel_hash_entry *bucket TSRMLS_DC) { @@ -2690,6 +2703,8 @@ static int accel_startup(zend_extension *extension) zend_accel_copy_internal_functions(TSRMLS_C); #endif + zend_accel_fsmonitor_startup(); + return SUCCESS; } @@ -2706,6 +2721,8 @@ void accel_shutdown(TSRMLS_D) { zend_ini_entry *ini_entry; + zend_accel_fsmonitor_signal(); + zend_accel_blacklist_shutdown(&accel_blacklist); if (!ZCG(enabled) || !accel_startup_ok) { diff --git a/ZendAccelerator.h b/ZendAccelerator.h index bba3631..a8bdf96 100644 --- a/ZendAccelerator.h +++ b/ZendAccelerator.h @@ -332,6 +332,7 @@ void accel_shutdown(TSRMLS_D); void zend_accel_schedule_restart(zend_accel_restart_reason reason TSRMLS_DC); void zend_accel_schedule_restart_if_necessary(zend_accel_restart_reason reason TSRMLS_DC); int validate_timestamp_and_record(zend_persistent_script *persistent_script, zend_file_handle *file_handle TSRMLS_DC); +int zend_accel_invalidate_real(char *filename, int filename_len, zend_bool force TSRMLS_DC); int zend_accel_invalidate(const char *filename, int filename_len, zend_bool force TSRMLS_DC); int accelerator_shm_read_lock(TSRMLS_D); void accelerator_shm_read_unlock(TSRMLS_D); diff --git a/config.m4 b/config.m4 index f6e6ca9..fd11e99 100644 --- a/config.m4 +++ b/config.m4 @@ -366,6 +366,7 @@ fi PHP_NEW_EXTENSION(opcache, ZendAccelerator.c \ zend_accelerator_blacklist.c \ + zend_accelerator_fsmonitor.c \ zend_accelerator_debug.c \ zend_accelerator_hash.c \ zend_accelerator_module.c \ diff --git a/package.xml b/package.xml index bdd7891..3852e2c 100644 --- a/package.xml +++ b/package.xml @@ -88,6 +88,7 @@ + diff --git a/zend_accelerator_fsmonitor.c b/zend_accelerator_fsmonitor.c new file mode 100644 index 0000000..d66228c --- /dev/null +++ b/zend_accelerator_fsmonitor.c @@ -0,0 +1,182 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2014 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. | + +----------------------------------------------------------------------+ +*/ + +#include "main/php.h" +#include "main/php_globals.h" +#include "ZendAccelerator.h" +#include "zend_accelerator_fsmonitor.h" + +#include +#include +#include + +pid_t fsm_pid = 0; +int fsm_fd = -1; + +static void zend_accel_fsmonitor_shutdown() +{ + zend_accel_error(ACCEL_LOG_DEBUG, "fsmonitor: shutdown"); + close(fsm_fd); +} + +static void sig_handler(int signo) +{ + int saved_errno = errno; + zend_accel_fsmonitor_shutdown(); + errno = saved_errno; +} + +static int zend_accel_fsmonitor_init_signals() +{ + struct sigaction act; + + memset(&act, 0, sizeof(act)); + act.sa_handler = sig_handler; + sigfillset(&act.sa_mask); + + if (sigaction(SIGTERM, &act, 0) < 0) { + zend_accel_error(ACCEL_LOG_WARNING, "failed to init signals: sigaction()"); + return FAILURE; + } + + return SUCCESS; +} + +static int zend_accel_fsmonitor_init() +{ + zend_accel_error(ACCEL_LOG_DEBUG, "fsmonitor: init"); + fsm_fd = fanotify_init(FAN_CLOEXEC, O_RDONLY | O_LARGEFILE); + if (fsm_fd == -1) { + zend_accel_error(ACCEL_LOG_WARNING, "Unable to initialize fsmonitor structure"); + return FAILURE; + } + + //FIXME + if (fanotify_mark(fsm_fd, FAN_MARK_ADD | FAN_MARK_MOUNT, FAN_MODIFY, -1, "/var/www") == -1) { + zend_accel_error(ACCEL_LOG_WARNING, "Unable to watch filesystem"); + close(fsm_fd); + return FAILURE; + } + + return zend_accel_fsmonitor_init_signals(); +} + +static void zend_accel_fsmonitor_invalidate(char *filename, int filename_len) +{ + /* only accept .php-files + * this is the full path so '.php' is not valid + */ + if (filename_len <= 4 || strcmp(&filename[filename_len - 4], ".php") != 0) + return; + zend_accel_error(ACCEL_LOG_DEBUG, "file: %s", filename); + zend_accel_invalidate_real(filename, filename_len, 1); +} + +static int zend_accel_fsmonitor_read() +{ + const struct fanotify_event_metadata *metadata; + struct fanotify_event_metadata buf[200]; + char procfd_path[PATH_MAX], path[PATH_MAX]; + ssize_t len, path_len; + + len = read(fsm_fd, (void *)&buf, sizeof(buf)); + if (len == -1 && errno != EAGAIN) { + zend_accel_error(ACCEL_LOG_ERROR, "Read error: %s", strerror(errno)); + return FAILURE; + } + + metadata = buf; + while (FAN_EVENT_OK(metadata, len)) { + if (metadata->vers != FANOTIFY_METADATA_VERSION) { + zend_accel_error(ACCEL_LOG_WARNING, "Mismatch of fanotify metadata version"); + return FAILURE; + } + + /* metadata->fd contains either FAN_NOFD, indicating a + * queue overflow, or a file descriptor (a nonnegative + * integer). we simply ignore queue overflow. + */ + if (metadata->fd >= 0) { + /* retrieve and print pathname of the accessed file */ + snprintf(procfd_path, sizeof(procfd_path), + "/proc/self/fd/%d", metadata->fd); + path_len = readlink(procfd_path, path, sizeof(path) - 1); + if (path_len == -1) { + zend_accel_error(ACCEL_LOG_DEBUG, "readlink error"); + return FAILURE; + } + + path[path_len] = '\0'; + zend_accel_fsmonitor_invalidate(path, path_len); + + close(metadata->fd); + } + + /* advance to next event */ + metadata = FAN_EVENT_NEXT(metadata, len); + } + + return SUCCESS; +} + + +void zend_accel_fsmonitor_startup() +{ + if (fsm_pid > 0) + return; + + fsm_pid = fork(); + switch (fsm_pid) { + case -1: /* error */ + zend_accel_error(ACCEL_LOG_WARNING, "Unable to start filesystem monitoring process"); + return; + + case 0: /* child */ + if (zend_accel_fsmonitor_init() == SUCCESS) { + for(;;) { + if (zend_accel_fsmonitor_read() != SUCCESS) { + break; + } + } + zend_accel_fsmonitor_shutdown(); + } + exit(0); + break; + + default: /* parent */ + zend_accel_error(ACCEL_LOG_DEBUG, "I'm the parent. The childs pid is: %ld", fsm_pid); + break; + } +} + +void zend_accel_fsmonitor_signal() +{ + pid_t w; + int status; + + if (fsm_pid) { + zend_accel_error(ACCEL_LOG_DEBUG, "Signaling child %ld", fsm_pid); + if (kill(fsm_pid, SIGTERM) == 0) { + sleep(1); //FIXME ugly + w = waitpid(fsm_pid, &status, WNOHANG | WUNTRACED); + if (w > 0 && !WIFEXITED(status) && !WIFSIGNALED(status)) { + zend_accel_error(ACCEL_LOG_WARNING, "Need to kill fsmonitor process..."); + kill(fsm_pid, SIGKILL); + } + } + fsm_pid = 0; + } +} diff --git a/zend_accelerator_fsmonitor.h b/zend_accelerator_fsmonitor.h new file mode 100644 index 0000000..6c292a7 --- /dev/null +++ b/zend_accelerator_fsmonitor.h @@ -0,0 +1,23 @@ +/* + +----------------------------------------------------------------------+ + | Zend OPcache | + +----------------------------------------------------------------------+ + | Copyright (c) 1998-2014 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. | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_ACCELERATOR_FSMONITOR_H +#define ZEND_ACCELERATOR_FSMONTIOR_H + +void zend_accel_fsmonitor_startup(); +void zend_accel_fsmonitor_signal(); + +#endif /* ZEND_ACCELERATOR_FSMONITOR_H */