summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2015-04-01 22:51:38 +0200
committerEmiel Bruijntjes <emiel.bruijntjes@copernica.com>2015-04-01 22:51:38 +0200
commit1a44e9fb96287b2ac11d99fec46f2e134b1b5712 (patch)
tree9bec23f112da4e9178e724cbb98799c9affc2a86
parent02809d4b89385fba1660b77609a40be5df5a8013 (diff)
added article about dynamic loading
-rw-r--r--dynamic-loading.html253
1 files changed, 253 insertions, 0 deletions
diff --git a/dynamic-loading.html b/dynamic-loading.html
new file mode 100644
index 0000000..12ae965
--- /dev/null
+++ b/dynamic-loading.html
@@ -0,0 +1,253 @@
+<h1>Dynamic loading</h1>
+<p>
+ Users who switch from PHP to C++ often ask whether it is more difficult
+ to manage a system if you use C++ code instead of PHP code. We must be honest
+ here: working with PHP is easier than working with C++. To activate a PHP
+ script, you for example don't need root access, and you can simply copy the script
+ to the web server. Deploying a native C++ extension requires more work than that:
+ you need to stop the webserver first, compile the extension, install it, and
+ then restart the web server.
+</p>
+<p>
+ On top of that, when an extension is deployed it is immediately active for
+ all websites that are hosted on the webserver. A deployed PHP script only
+ changes the behavior of a single website, but a deployed C++ extension affects
+ all sites. It is not really possible to activate an extension for only specific sites,
+ or to test a new version of an extension for just a single website, as the
+ extensions are shared by all PHP processes. If you really want to use
+ different extensions for different sites, you need multiple servers that all
+ have their own configuration.
+</p>
+<p>
+ Or you can use dynamic loading.
+</p>
+<p>
+ PHP has a builtin <a href="http://php.net/dl">dl()</a>
+ function that you can use to load extensions on the fly. This allows you
+ to call the 'dl("myextension.so")' function from a PHP script to load an
+ extension, so that an extension only works for a specific site. This
+ builtin 'dl()' function has some restrictions because of security
+ considerations (it would otherwise allow users to run arbitrary native code),
+ but if you're the only one who is in charge of a system, or when a server is not
+ shared by multiple organizations, you can use PHP-CPP to create a function
+ similar to 'dl()' that does not have this restriction.
+</p>
+<h2>Why is dl() restricted?</h2>
+<p>
+ The dl() function is restricted because of security issues. When you use
+ dl(), you can only load extensions that are stored in the system
+ wide extensions directory, and not for loading extensions that users have
+ placed in other locations. A call to dl("/home/user/myextension.so") will
+ therefore fail, because "/home/user" is not the official extensions
+ directory. Why this restriction?
+</p>
+<p>
+ To understand this, one must first realize that in a normal PHP installation
+ PHP scripts are edited by users who do not have root access. On shared hosting
+ environments different users all run their own website on the same system. In
+ such a setup, it is an absolute no-go if one user can write a script or program
+ that has access to the data of others. With an unrestricted dl() function
+ however, exactly this would be possible. An unrestricted dl() call would
+ allow PHP programmers to write a native extension, store that in their home
+ directory or in the /tmp directory, and have it loaded by the webserver process.
+ They could then execute arbitrary code, and possibly
+ install loggers or other malicious code inside other people's websites. By
+ only allowing extensions from the system wide extensions directory to be loaded,
+ PHP ensures every dynamically loaded extension must at least have been installed
+ by the system administrator.
+</p>
+<p>
+ When you write your own extensions however - either directly on top of the Zend
+ API, or by using the PHP-CPP library - you already are in a position to write and execute
+ arbitrary code anyway. No need for security checks here. From you C/C++ code
+ you can do whatever you want. Would it not be cool if you could dynamically
+ load an extension based on the requirements of a site? One website needs
+ to be stable, and loads the well tested version 1.0 of your extension, while
+ a second website is more experimental, and loads version 2.0. You could be
+ running two versions of your extension on the same machine.
+</p>
+<h2>Thin loader extension</h2>
+<p>
+ Imagine you're writing your own extension "MyExtension" that has many different
+ classes and functions, and for which you plan to bring out new releases all the time.
+ You do not want to deploy a new release in a "big bang" style, but you want to roll out
+ new versions slowly, one customer or one website at a time. How would you do this?
+</p>
+<p>
+ You start by developing a thin loader extension: the ExtensionLoader
+ extension. This extension has only one function: enable_my_extension() - which
+ takes the version number of your actual extension, and dynamically loads that version
+ of your extension. This is a simple extension (it only has one function)
+ that you install globally, and that you will probably never have to update.
+</p>
+<p>
+<pre class="language-c++"><code>
+/**
+ * Function to load an extension by its version number
+ *
+ * It takes one argument: the version number of your extension,
+ * and returns a boolean to indicate whether the extension was
+ * correctly loaded.
+ *
+ * @param params Vector of parameters
+ * @return boolean
+ */
+Php::Value enable_my_extension(Php::Parameters &amp;params)
+{
+ // get version number
+ int version = params[0];
+
+ // construct pathname to your extension (this is for example
+ // /path/to/MyExtension.so.1 or /path/to/MyExtension.so.2)
+ std::string path = "/path/to/MyExtension.so." + std::to_string(version);
+
+ // load the extension
+ return Php::dl(path);
+}
+
+/**
+ * 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("ExtensionLoader", "1.0");
+
+ // the extension has one method
+ myExtension.add("enable_my_extension", enable_my_extension, {
+ Php::ByVal("version", Php::Type::Numeric)
+ });
+
+ // return the extension
+ return myExtension;
+ }
+}
+</code></pre>
+</p>
+<p>
+ The above code holds the full source code of the ExtensionLoader extension.
+ You can install this extension on your system, by copy'ing it to the global
+ php extensions directory and updating the php.ini files.
+</p>
+<p>
+ After you've installed this thin loader extension, you can write your actual
+ big extension full with classes and functions, and compile this extension
+ into *.so files: the first version you compile into MyExtension.so.1,
+ and later versions into MyExtension.so.2, MyExtension.so.3, and so on. For every new
+ release you introduce a new version number, and you copy these shared objects
+ to the /path/to directory (the same path hardcoded in the 'loader' extension
+ showed above). And although this is not the official PHP extensions directory,
+ such extensions can nevertheless be loaded byt the enable_my_extension()
+ function.
+</p>
+<p>
+ You do not have to copy the extensions into the PHP extension directory, nor
+ do you have to update the php.ini configuration. To activate an extension,
+ you simply need to call the enable_my_extension() function that was introduced
+ but he thin loader:
+</p>
+<p>
+<pre class="language-php"><code>
+&lt;?php
+// enable version 2 of the extension (this will load MyExtension.so.2)
+if (!enable_my_extension(2)) die("Version 2 of extension is missing");
+
+// from now on we can use classes and functions from version 2 of the extension
+$object = new ClassFromMyExtension();
+$object->methodFromMyExtension();
+
+// you get the idea...
+
+?&gt;
+</code></pre>
+</p>
+<p>
+ The thin loader that we showed above is still pretty secure. It is not possible
+ to run arbitrary code, or to open arbitrary *.so files. The worst thing that can
+ happen is that someone opens an extension with a wrong version number - which is
+ not disastrous at all.
+</p>
+<p>
+ But if you really trust the users on your system, you can easily adjust the thin
+ loader extension to allow other types of parameters too. In the most open
+ scenario, you could even write a function that allows users to literally
+ open every possible shared object file:
+</p>
+<p>
+<pre class="language-c++"><code>
+/**
+ * Function to load every possible extension by pathname
+ *
+ * It takes one argument: the filename of the PHP extension, and 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. 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 &amp;params)
+{
+ // get extension name
+ std::string pathname = params[0];
+
+ // load the extension
+ return Php::dl(pathname);
+}
+
+/**
+ * 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)
+ });
+
+ // return the extension
+ return myExtension;
+ }
+}
+</code></pre>
+</p>
+<p>
+ The above code would allow PHP scripts to dynamically load PHP extensions,
+ no matter where they are stored on the system:
+</p>
+<p>
+<pre class="language-php"><code>
+&lt;?php
+// load the C++ extension stored in the same directory as this file
+if (!dl_unrestricted(__DIR__.'/MyExtension.so')) die("Extension could not be loaded");
+
+// from now on we can use classes and functions from the extension
+$object = new ClassFromMyExtension();
+$object->methodFromMyExtension();
+
+?&gt;
+</code></pre>
+</p>
+<p>
+ The dl_unrestricted() function is an awesome function, but be careful here:
+ if you're the administrator of a shared hosting platform, you definitely
+ do not want to install it!
+</p>