Download | Plain Text | Line Numbers


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 @@
    <file role="src" name="shared_alloc_mmap.c"/>
    <file role="src" name="zend_accelerator_debug.c"/>
    <file role="src" name="zend_accelerator_blacklist.c"/>
+   <file role="src" name="zend_accelerator_fsmonitor.c"/>
    <file role="src" name="shared_alloc_shm.c"/>
    <file role="src" name="zend_accelerator_util_funcs.h"/>
    <file role="src" name="zend_accelerator_module.h"/>
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 <signal.h>
+#include <fcntl.h>
+#include <sys/fanotify.h>
+
+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 */