From a860f85e80c48b821c8cb53fc1d98d5ac2f6f07a Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sat, 11 Apr 2015 21:12:49 +0200 Subject: function names are now turned into lowercase name when registering them with zend, this is necessary because all functions are lowercase, and function-table lookups were failing, and more importantly: the removal of functions was failing when a module was unloaded --- zend/hiddenpointer.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/zend/hiddenpointer.h b/zend/hiddenpointer.h index d2636e3..147886e 100644 --- a/zend/hiddenpointer.h +++ b/zend/hiddenpointer.h @@ -38,8 +38,14 @@ public: // copy pointer into the buffer memcpy(buffer, &pointer, sizeof(Type *)); - // copy the name into the buffer - memcpy(buffer + sizeof(Type *), text, size + 1); + // copy the name into the buffer, making it lower case at the same time + // (php function names are case insensitive, and must even be lowercase + // because all the lookups are done in lowercase) + for (int i = 0; i < size + 1; i++) + { + // convert char the lower case + buffer[sizeof(Type *) + i] = tolower(text[i]); + } // store in member _buffer = buffer; -- cgit v1.2.3 From a8f3d4c47b342ef1ce9c951239a1618a0912484c Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sun, 12 Apr 2015 12:11:13 +0200 Subject: Php::dl() function now gets an extra "persistent" parameter to load extensions persistently --- Examples/DlUnrestricted/dlunrestricted.cpp | 5 +- include/call.h | 6 +-- zend/eval.cpp | 9 +++- zend/module.cpp | 30 ++++++++++++ zend/module.h | 78 +++++++++++++++++++++++++++++- 5 files changed, 120 insertions(+), 8 deletions(-) create mode 100644 zend/module.cpp diff --git a/Examples/DlUnrestricted/dlunrestricted.cpp b/Examples/DlUnrestricted/dlunrestricted.cpp index bdc7cae..a7ec7ed 100644 --- a/Examples/DlUnrestricted/dlunrestricted.cpp +++ b/Examples/DlUnrestricted/dlunrestricted.cpp @@ -29,8 +29,11 @@ Php::Value dl_unrestricted(Php::Parameters ¶ms) // get extension name std::string pathname = params[0]; + // should it be opened persistently? + bool persistent = params.size() > 1 ? params[1].boolValue() : false; + // load the extension - return Php::dl(pathname); + return Php::dl(pathname, persistent); } /** diff --git a/include/call.h b/include/call.h index 36574ce..094c7d2 100644 --- a/include/call.h +++ b/include/call.h @@ -27,9 +27,9 @@ extern PHPCPP_EXPORT bool define(const std::string &name, const Value &value extern PHPCPP_EXPORT bool defined(const char *constant); extern PHPCPP_EXPORT bool defined(const char *constant, size_t size); extern PHPCPP_EXPORT bool defined(const std::string &constant); -extern PHPCPP_EXPORT bool dl(const char *filename); -inline PHPCPP_EXPORT bool dl(const std::string &filename) { return dl(filename.c_str()); } -inline PHPCPP_EXPORT bool dl(const Value &filename) { return dl(filename.rawValue()); } +extern PHPCPP_EXPORT bool dl(const char *filename, bool persistent = false); +inline PHPCPP_EXPORT bool dl(const std::string &filename, bool persistent = false) { return dl(filename.c_str(), persistent); } +inline PHPCPP_EXPORT bool dl(const Value &filename, bool persistent = false) { return dl(filename.rawValue(), persistent); } extern PHPCPP_EXPORT Value eval(const char *phpCode); inline PHPCPP_EXPORT Value eval(const std::string &phpCode) { return eval(phpCode.c_str()); } extern PHPCPP_EXPORT Value include(const char *filename); diff --git a/zend/eval.cpp b/zend/eval.cpp index 7ab4957..6d043b1 100644 --- a/zend/eval.cpp +++ b/zend/eval.cpp @@ -93,12 +93,17 @@ Value require_once(const char *filename) /** * Implementation of the dl() function - activate a different PHP extension + * + * If you open an extension persistently, the static variables inside it are + * kept open for as long as apache runs. + * * @param filename + * @param persistent */ -bool dl(const char *filename) +bool dl(const char *filename, bool persistent) { // create the module - Module module(filename); + Module module(filename, persistent); // start the module return module.start(); diff --git a/zend/module.cpp b/zend/module.cpp new file mode 100644 index 0000000..46d03d8 --- /dev/null +++ b/zend/module.cpp @@ -0,0 +1,30 @@ +/** + * Module.cpp + * + * Module implementation file + * + * @author Emiel Bruijntjes + * @copyright 2015 Copernica BV + */ + +/** + * Dependencies + */ +#include "includes.h" + +/** + * Set up namespace + */ +namespace Php { + +/** + * The persistent handles + * @var Module::Persistent + */ +Module::Persistent Module::_persistent; + +/** + * End of namespace + */ +} + diff --git a/zend/module.h b/zend/module.h index 4fa6bce..40cd6f7 100644 --- a/zend/module.h +++ b/zend/module.h @@ -43,7 +43,7 @@ private: * @var zend_module_entry */ zend_module_entry *_entry = nullptr; - + #ifdef ZTS /** * When in thread safety mode, we also keep track of the TSRM_LS var @@ -52,12 +52,82 @@ private: void ***tsrm_ls; #endif + /** + * Internal helper class with persistent modules + */ + class Persistent + { + private: + /** + * The set of handles + * @var std::set + */ + std::set _handles; + + public: + /** + * Constructor + */ + Persistent() {} + + /** + * Destructor + */ + virtual ~Persistent() + { + // remove all handles + while (!_handles.empty()) + { + // get first handle + auto iter = _handles.begin(); + + // remove the handle + DL_UNLOAD(*iter); + + // remove from set + _handles.erase(iter); + } + } + + /** + * Check whether a handle is already persistently opened + * @param handle + * @return bool + */ + bool contains(void *handle) const + { + return _handles.find(handle) != _handles.end(); + } + + /** + * Add a library persistently + * @param module + */ + void add(const char *module) + { + // insert the handle + _handles.insert(DL_LOAD(module)); + } + }; + + /** + * All persistent modules + * @var Persistent + */ + static Persistent _persistent; + public: /** * Constructor + * + * A module can be loaded persistently. This means that the variables in + * the module will keep in scope for as long as Apache runs, even though + * the extension is not active in other page views + * * @param module Name of the module + * @param persistent Should it be loaded persistently */ - Module(const char *module) + Module(const char *module, bool persistent) { #ifdef ZTS // fetch multi-threading thing @@ -76,6 +146,10 @@ public: // handle should be valid if (!_handle) return; + // if we have to open it persistently, we open it for a second time so that + // the refcounter always stays 1 or higher + if (persistent && !_persistent.contains(_handle)) _persistent.add(module); + // we have to call the get_module() function Symbol get_module(_handle, "get_module"); -- cgit v1.2.3 From 8ca556ab2a41a6c7245fada4628e4719cd99f8b3 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sun, 12 Apr 2015 12:35:23 +0200 Subject: Update documentation about persistent loading --- documentation/dynamic-loading.html | 101 +++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/documentation/dynamic-loading.html b/documentation/dynamic-loading.html index 12ae965..dbc7410 100644 --- a/documentation/dynamic-loading.html +++ b/documentation/dynamic-loading.html @@ -251,3 +251,104 @@ $object->methodFromMyExtension(); if you're the administrator of a shared hosting platform, you definitely do not want to install it!

+

Persistent extensions

+

+ A dynamically loaded extension is automatically unloaded at the end of the + page view. If a subsequent pageview also dynamically loads the same extension, + it will start with a completely new and fresh environment. If you want to + write an extension that uses static data or static resources (think of a persistent + database connection, or a worker thread that processes tasks) this may not + always be the desired behavior. You want to keep the database connection active, + or the thread running, also after the extension is unloaded. +

+

+ To overcome this, the Php::dl() function comes with a second boolean parameter + that you can use to specify whether you want to load the extension persistently, + or only for that specific pageview. +

+

+ Notice that the only thing that persists is the data in an extension. + In subsequent pageviews you must also load the extension to activate these + functions and classes, even if you had already loaded the extension persistently + in an earlier call. But because the extension was already loaded before, the + static data (like the database connection or the thread) are preserved. +

+

+ The dl_unrestricted() function that we used above can be modified to include + this persistent parameter: +

+

+


+/**
+ *  Function to load every possible extension by pathname
+ *
+ *  It takes two arguments: the filename of the PHP extension, and a boolean to
+ *  specify whether the extension data should be kept in memory. It returns a 
+ *  boolean to indicate whether the extension was correctly loaded.
+ *
+ *  This function goes further than the original PHP dl() fuction, because
+ *  it does not check whether the passed in extension object is stored in the
+ *  right directory, and because it allows persistent loading of extensions. 
+ *  Literally every possible extension, also local ones created by end users, 
+ *  can be loaded.
+ *
+ *  @param  params      Vector of parameters
+ *  @return boolean
+ */
+Php::Value dl_unrestricted(Php::Parameters &params)
+{
+    // get extension name
+    std::string pathname = params[0];
+    
+    // persistent setting
+    bool persistent = params.size() > 1 ? params[1].boolValue() : false;
+    
+    // load the extension
+    return Php::dl(pathname, persistent);
+}
+    
+/**
+ *  Switch to C context to ensure that the get_module() function
+ *  is callable by C programs (which the Zend engine is)
+ */
+extern "C" {
+    /**
+     *  Startup function that is called by the Zend engine 
+     *  to retrieve all information about the extension
+     *  @return void*
+     */
+    PHPCPP_EXPORT void *get_module() {
+        // create static instance of the extension object
+        static Php::Extension myExtension("load_extension", "1.0");
+        
+        // the extension has one method
+        myExtension.add("dl_unrestricted", dl_unrestricted, {
+            Php::ByVal("pathname", Php::Type::String),
+            Php::ByVal("persistent", Php::Type::Bool, false)
+        });
+        
+        // return the extension
+        return myExtension;
+    }
+}
+
+

+

+ And this extension allows us to dynamically load extensions, while preserving + persistent data inside the extension: +

+

+


+<?php
+// load the C++ extension stored in the same directory as this file, the
+// extension is persistently loaded, so it may use persistent data like
+// database connections and so on.
+if (!dl_unrestricted(__DIR__.'/MyExtension.so', true)) die("Extension could not be loaded");
+
+// from now on we can use classes and functions from the extension
+$object = new ClassFromMyExtension();
+$object->methodFromMyExtension();
+
+?>
+
+

-- cgit v1.2.3 From 8779ed80dd5d6794f9d3739a2c2cf10f6b2ee767 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sun, 12 Apr 2015 12:37:11 +0200 Subject: update docs about dynamic loading --- documentation/dynamic-loading.html | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/documentation/dynamic-loading.html b/documentation/dynamic-loading.html index dbc7410..f57a997 100644 --- a/documentation/dynamic-loading.html +++ b/documentation/dynamic-loading.html @@ -267,14 +267,15 @@ $object->methodFromMyExtension(); or only for that specific pageview.

- Notice that the only thing that persists is the data in an extension. - In subsequent pageviews you must also load the extension to activate these - functions and classes, even if you had already loaded the extension persistently - in an earlier call. But because the extension was already loaded before, the - static data (like the database connection or the thread) are preserved. + Notice that if you set this parameter to true, the only thing that persists is + the data in an extension. In subsequent pageviews you will still have to + load the extension to activate the functions and classes in it, even if you had + already loaded the extension persistently in an earlier pageview. But because + the extension was already loaded before, the static data in it (like the database + connection or the thread) is preserved.

- The dl_unrestricted() function that we used above can be modified to include + The dl_unrestricted() function that we demonstrated above can be modified to include this persistent parameter:

-- cgit v1.2.3 From e7d68e6bf1ceecdee6cb4df3f41edf6f94ad25a3 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sun, 12 Apr 2015 21:00:12 +0200 Subject: Complete magic! The compiler somehow optimizes the ExecuteClass out when called with no parameters whatsoever, by passing an extra argument this no longer happens. This fixes the problem that we had with Php::eval(), fix issue #183 --- zend/executestate.h | 18 ++++++++++++++++-- zend/opcodes.h | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/zend/executestate.h b/zend/executestate.h index 8e33b16..d71a252 100644 --- a/zend/executestate.h +++ b/zend/executestate.h @@ -26,11 +26,18 @@ class ExecuteState private: /** * All the original settings + * @var mixed */ zend_op_array *_active_op_array; zval **_return_value_ptr_ptr; zend_op **_opline_ptr; int _interactive; + + /** + * The new value for 'no-extensions' + * @var int + */ + int _no_extensions; #ifdef ZTS /** @@ -41,16 +48,23 @@ private: #endif public: + /** + * No trivial constructor + */ + ExecuteState() = delete; + /** * Constructor + * @param no_extensions */ - ExecuteState(TSRMLS_D) + ExecuteState(int no_extensions TSRMLS_DC) { // store all the original stuff _active_op_array = EG(active_op_array); _return_value_ptr_ptr = EG(return_value_ptr_ptr); _opline_ptr = EG(opline_ptr); _interactive = CG(interactive); + _no_extensions = no_extensions; #ifdef ZTS // copy tsrm_ls param @@ -65,7 +79,7 @@ public: { // restore all settings CG(interactive) = _interactive; - EG(no_extensions) = 0; + EG(no_extensions) = _no_extensions; EG(opline_ptr) = _opline_ptr; EG(active_op_array) = _active_op_array; EG(return_value_ptr_ptr) = _return_value_ptr_ptr; diff --git a/zend/opcodes.h b/zend/opcodes.h index 7e6d1ec..4e78083 100644 --- a/zend/opcodes.h +++ b/zend/opcodes.h @@ -74,7 +74,7 @@ public: // the zend engine is probably already busy processing opcodes, so we store // the current execute state before we're going to switch the runtime to // our own set of opcodes - ExecuteState oldstate(TSRMLS_C); + ExecuteState execState(0 TSRMLS_CC); // old execute state has been saved (and will automatically be restured when // the oldstate is destructed), so we can now safely overwrite all the settings -- cgit v1.2.3 From 5470a3d9556c04471660288a10098528ec9892e7 Mon Sep 17 00:00:00 2001 From: Emiel Bruijntjes Date: Sun, 12 Apr 2015 21:00:12 +0200 Subject: stop calling zend_next_free_module() -- some users complain that this function does not exist, this hopefully fixes issue #185 --- zend/executestate.h | 18 ++++++++++++++++-- zend/opcodes.h | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/zend/executestate.h b/zend/executestate.h index 8e33b16..d71a252 100644 --- a/zend/executestate.h +++ b/zend/executestate.h @@ -26,11 +26,18 @@ class ExecuteState private: /** * All the original settings + * @var mixed */ zend_op_array *_active_op_array; zval **_return_value_ptr_ptr; zend_op **_opline_ptr; int _interactive; + + /** + * The new value for 'no-extensions' + * @var int + */ + int _no_extensions; #ifdef ZTS /** @@ -41,16 +48,23 @@ private: #endif public: + /** + * No trivial constructor + */ + ExecuteState() = delete; + /** * Constructor + * @param no_extensions */ - ExecuteState(TSRMLS_D) + ExecuteState(int no_extensions TSRMLS_DC) { // store all the original stuff _active_op_array = EG(active_op_array); _return_value_ptr_ptr = EG(return_value_ptr_ptr); _opline_ptr = EG(opline_ptr); _interactive = CG(interactive); + _no_extensions = no_extensions; #ifdef ZTS // copy tsrm_ls param @@ -65,7 +79,7 @@ public: { // restore all settings CG(interactive) = _interactive; - EG(no_extensions) = 0; + EG(no_extensions) = _no_extensions; EG(opline_ptr) = _opline_ptr; EG(active_op_array) = _active_op_array; EG(return_value_ptr_ptr) = _return_value_ptr_ptr; diff --git a/zend/opcodes.h b/zend/opcodes.h index 7e6d1ec..4e78083 100644 --- a/zend/opcodes.h +++ b/zend/opcodes.h @@ -74,7 +74,7 @@ public: // the zend engine is probably already busy processing opcodes, so we store // the current execute state before we're going to switch the runtime to // our own set of opcodes - ExecuteState oldstate(TSRMLS_C); + ExecuteState execState(0 TSRMLS_CC); // old execute state has been saved (and will automatically be restured when // the oldstate is destructed), so we can now safely overwrite all the settings -- cgit v1.2.3