diff options
-rw-r--r-- | Examples/DlUnrestricted/dlunrestricted.cpp | 5 | ||||
-rw-r--r-- | documentation/dynamic-loading.html | 102 | ||||
-rw-r--r-- | include/call.h | 6 | ||||
-rw-r--r-- | zend/eval.cpp | 9 | ||||
-rw-r--r-- | zend/executestate.h | 18 | ||||
-rw-r--r-- | zend/hiddenpointer.h | 10 | ||||
-rw-r--r-- | zend/module.cpp | 30 | ||||
-rw-r--r-- | zend/module.h | 78 | ||||
-rw-r--r-- | zend/opcodes.h | 2 |
9 files changed, 247 insertions, 13 deletions
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/documentation/dynamic-loading.html b/documentation/dynamic-loading.html index 12ae965..f57a997 100644 --- a/documentation/dynamic-loading.html +++ b/documentation/dynamic-loading.html @@ -251,3 +251,105 @@ $object->methodFromMyExtension(); if you're the administrator of a shared hosting platform, you definitely do not want to install it! </p> +<h2 id="persistent">Persistent extensions</h2> +<p> + 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. +</p> +<p> + 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. +</p> +<p> + Notice that if you set this parameter to true, the only thing that persists is + the data <i>in</i> 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. +</p> +<p> + The dl_unrestricted() function that we demonstrated above can be modified to include + this persistent parameter: +</p> +<p> +<pre class="language-c++"><code> +/** + * 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; + } +} +</code></pre> +</p> +<p> + And this extension allows us to dynamically load extensions, while preserving + persistent data <i>inside</i> the extension: +</p> +<p> +<pre class="language-php"><code> +<?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(); + +?> +</code></pre> +</p> 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/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 /** @@ -42,15 +49,22 @@ private: 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/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; 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 <emiel.bruijntjes@copernica.com> + * @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 bb36810..4431355 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<void*> _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<zend_module_entry*()> get_module(_handle, "get_module"); 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 |