summaryrefslogtreecommitdiff
path: root/zend/module.h
blob: efb06af5278155d2d3a94f806dfdca91f3bd7ecc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
/**
 *  Module.h
 *
 *  In PHP scripts you can use the dl() function to open additional extensions.
 *  This builtin dl() function call is protected with all sorts of security measures,
 *  like checking whether a filename contains slashes or otherwise potentially
 *  dangerous things. This is all understandable, because you do not want a kid
 *  from an ~user directory to be able to make changes to the globalle running
 *  apache process, and for example make changes to catch data from other websites
 *  running on the same host.
 * 
 *  However, people who are in a position to write C++ and deploy code however, 
 *  already _ARE_ in a position to do dangerous things. For them we do not want
 *  these safety checks. In fact, we want to offer raw access, so that they can
 *  open modules - no matter what. So we had to make our own copy of the dl()
 *  function, and because we're here in C++ context, we do it a little nicer
 *  than the original C++ code. This module class is a utility class used by
 *  our own dl() implementation.
 * 
 *  @author Emiel Bruijntjes <emiel.bruijntjes@copernica.com>
 *  @copyright 2015 Copernica BV
 */

/**
 *  Set up namespace
 */
namespace Php {
    
/**
 *  Class definition
 */
class Module
{
private:
    /**
     *  The handle of the module
     *  @var void*
     */
    void *_handle;
    
    /**
     *  The module entry
     *  @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
     *  @var void***
     */
    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, bool persistent)
    {
#ifdef ZTS
        // fetch multi-threading thing
        TSRMLS_FETCH();

        // copy tsrm_ls param
        this->tsrm_ls = tsrm_ls;
#endif

        // the path we're going to load
        ExtensionPath path(module TSRMLS_CC);
        
        // load the module
        _handle = DL_LOAD(path);
        
        // 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");
        
        // was the get_module() function found
        if (!get_module) return;

        // retrieve the module entry
        _entry = get_module();
    }
    
    /**
     *  Destructor
     */
    virtual ~Module()
    {
        // if the handle is still valid, we have to unload it
      if (_handle) DL_UNLOAD((DL_HANDLE)_handle);
    }
    
    /**
     *  Check if the module is valid
     *  @return bool
     */
    bool valid() const
    {
        // module-entry must exist
        if (!_handle || !_entry) return false;
    
        // check api compatibility
        if (_entry->zend_api != ZEND_MODULE_API_NO) return false;
        
        // and other stuff
        return strcmp(_entry->build_id, ZEND_MODULE_BUILD_ID) == 0;
    }
    
    /**
     *  Start the module
     *  @return bool
     */
    bool start()
    {
        // this is not possible if the module is invalid in the first place
        if (!valid()) return false;
        
        // the Zend engine sets a number of properties in the entry class, we do that here too
        // note that it would be better to call zend_next_free_module() to find the next module
        // number, but some users complain that this function is not always available
        _entry->type = MODULE_TEMPORARY;
        _entry->module_number = zend_hash_num_elements(&module_registry) + 1;
        _entry->handle = _handle;
        
        // @todo does loading an extension even work in a multi-threading setup?
        
        // register the module, this apparently returns a copied entry pointer
        auto *entry = zend_register_module_ex(_entry TSRMLS_CC);

        // forget the entry, so that a new call to start() will fail too
        _entry = nullptr;
        
        // leap out on failure
        if (entry == NULL) return false;

        // startup the module
        if (zend_startup_module_ex(entry TSRMLS_CC) == FAILURE) return false;

        // was a startup-function defined? if not
        if (!entry->request_startup_func)
        {
            // call the startup function
            if (entry->request_startup_func(MODULE_TEMPORARY, entry->module_number TSRMLS_CC) == FAILURE) return false;
        }
        
        // all is ok, we can forget about the handle now, so that is won't get destructed
        _handle = nullptr;

        // enable full-table-cleanup, because inside the Zend-engine this is done too
        EG(full_tables_cleanup) = 1;
        
        // we're ready
        return true;
    }
};
    
/**
 *  End of namespace
 */
}