1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 5                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2014 The PHP Group                                |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Authors: Ard Biesheuvel <a.k.biesheuvel@its.tudelft.nl>              |
16   +----------------------------------------------------------------------+
17 */
18
19#ifdef HAVE_CONFIG_H
20#include "config.h"
21#endif
22
23#include "php.h"
24
25#if HAVE_IBASE
26
27#include "php_interbase.h"
28#include "php_ibase_includes.h"
29
30static int le_event;
31
32static void _php_ibase_event_free(char *event_buf, char *result_buf) /* {{{ */
33{
34    isc_free(event_buf);
35    isc_free(result_buf);
36}
37/* }}} */
38
39void _php_ibase_free_event(ibase_event *event TSRMLS_DC) /* {{{ */
40{
41    unsigned short i;
42
43    event->state = DEAD;
44
45    if (event->link != NULL) {
46        ibase_event **node;
47
48        if (event->link->handle != NULL &&
49                isc_cancel_events(IB_STATUS, &event->link->handle, &event->event_id)) {
50            _php_ibase_error(TSRMLS_C);
51        }
52
53        /* delete this event from the link struct */
54        for (node = &event->link->event_head; *node != event; node = &(*node)->event_next);
55        *node = event->event_next;
56    }
57
58    if (event->callback) {
59        zval_dtor(event->callback);
60        FREE_ZVAL(event->callback);
61        event->callback = NULL;
62
63        _php_ibase_event_free(event->event_buffer,event->result_buffer);
64
65        for (i = 0; i < event->event_count; ++i) {
66            efree(event->events[i]);
67        }
68        efree(event->events);
69    }
70}
71/* }}} */
72
73static void _php_ibase_free_event_rsrc(zend_rsrc_list_entry *rsrc TSRMLS_DC) /* {{{ */
74{
75    ibase_event *e = (ibase_event *) rsrc->ptr;
76
77    _php_ibase_free_event(e TSRMLS_CC);
78
79    efree(e);
80}
81/* }}} */
82
83void php_ibase_events_minit(INIT_FUNC_ARGS) /* {{{ */
84{
85    le_event = zend_register_list_destructors_ex(_php_ibase_free_event_rsrc, NULL,
86        "interbase event", module_number);
87}
88/* }}} */
89
90static void _php_ibase_event_block(ibase_db_link *ib_link, unsigned short count, /* {{{ */
91    char **events, unsigned short *l, char **event_buf, char **result_buf)
92{
93    ISC_STATUS dummy_result[20];
94    unsigned long dummy_count[15];
95
96    /**
97     * Unfortunately, there's no clean and portable way in C to pass arguments to
98     * a variadic function if you don't know the number of arguments at compile time.
99     * (And even if there were a way, the Interbase API doesn't provide a version of
100     * this function that takes a va_list as an argument)
101     *
102     * In this case, the number of arguments is limited to 18 by the underlying API,
103     * so we can work around it.
104     */
105
106    *l = (unsigned short) isc_event_block(event_buf, result_buf, count, events[0],
107        events[1], events[2], events[3], events[4], events[5], events[6], events[7],
108        events[8], events[9], events[10], events[11], events[12], events[13], events[14]);
109
110    /**
111     * Currently, this is the only way to correctly initialize an event buffer.
112     * This is clearly something that should be fixed, cause the semantics of
113     * isc_wait_for_event() indicate that it blocks until an event occurs.
114     * If the Firebird people ever fix this, these lines should be removed,
115     * otherwise, events will have to fire twice before ibase_wait_event() returns.
116     */
117
118    isc_wait_for_event(dummy_result, &ib_link->handle, *l, *event_buf, *result_buf);
119    isc_event_counts(dummy_count, *l, *event_buf, *result_buf);
120}
121/* }}} */
122
123/* {{{ proto string ibase_wait_event([resource link_identifier,] string event [, string event [, ...]])
124   Waits for any one of the passed Interbase events to be posted by the database, and returns its name */
125PHP_FUNCTION(ibase_wait_event)
126{
127    zval ***args;
128    ibase_db_link *ib_link;
129    int num_args;
130    char *event_buffer, *result_buffer, *events[15];
131    unsigned short i = 0, event_count = 0, buffer_size;
132    unsigned long occurred_event[15];
133
134    RESET_ERRMSG;
135
136    /* no more than 15 events */
137    if (ZEND_NUM_ARGS() < 1 || ZEND_NUM_ARGS() > 16) {
138        WRONG_PARAM_COUNT;
139    }
140
141    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "+", &args, &num_args) == FAILURE) {
142        return;
143    }
144
145    if (Z_TYPE_PP(args[0]) == IS_RESOURCE) {
146        if (!ZEND_FETCH_RESOURCE2_NO_RETURN(ib_link, ibase_db_link *, args[0], -1, "InterBase link", le_link, le_plink)) {
147            efree(args);
148            RETURN_FALSE;
149        }
150        i = 1;
151    } else {
152        if (ZEND_NUM_ARGS() > 15) {
153            efree(args);
154            WRONG_PARAM_COUNT;
155        }
156        if (!ZEND_FETCH_RESOURCE2_NO_RETURN(ib_link, ibase_db_link *, NULL, IBG(default_link), "InterBase link", le_link, le_plink)) {
157            efree(args);
158            RETURN_FALSE;
159        }
160    }
161
162    for (; i < ZEND_NUM_ARGS(); ++i) {
163        convert_to_string_ex(args[i]);
164        events[event_count++] = Z_STRVAL_PP(args[i]);
165    }
166
167    /* fills the required data structure with information about the events */
168    _php_ibase_event_block(ib_link, event_count, events, &buffer_size, &event_buffer, &result_buffer);
169
170    /* now block until an event occurs */
171    if (isc_wait_for_event(IB_STATUS, &ib_link->handle, buffer_size, event_buffer, result_buffer)) {
172        _php_ibase_error(TSRMLS_C);
173        _php_ibase_event_free(event_buffer,result_buffer);
174        efree(args);
175        RETURN_FALSE;
176    }
177
178    /* find out which event occurred */
179    isc_event_counts(occurred_event, buffer_size, event_buffer, result_buffer);
180    for (i = 0; i < event_count; ++i) {
181        if (occurred_event[i]) {
182            char *result = estrdup(events[i]);
183            _php_ibase_event_free(event_buffer,result_buffer);
184            efree(args);
185            RETURN_STRING(result,0);
186        }
187    }
188
189    /* If we reach this line, isc_wait_for_event() did return, but we don't know
190       which event fired. */
191    _php_ibase_event_free(event_buffer,result_buffer);
192    efree(args);
193    RETURN_FALSE;
194}
195/* }}} */
196
197static isc_callback _php_ibase_callback(ibase_event *event, /* {{{ */
198    unsigned short buffer_size, char *result_buf)
199{
200    /* this function is called asynchronously by the Interbase client library. */
201    TSRMLS_FETCH_FROM_CTX(event->thread_ctx);
202
203    /**
204     * The callback function is called when the event is first registered and when the event
205     * is cancelled. I consider this is a bug. By clearing event->callback first and setting
206     * it to -1 later, we make sure nothing happens if no event was actually posted.
207     */
208    switch (event->state) {
209        unsigned short i;
210        unsigned long occurred_event[15];
211        zval event_name, link_id, return_value, *args[2];
212
213        default: /* == DEAD */
214            break;
215        case ACTIVE:
216            args[0] = &event_name;
217            args[1] = &link_id;
218
219            /* copy the updated results into the result buffer */
220            memcpy(event->result_buffer, result_buf, buffer_size);
221
222            INIT_ZVAL(event_name);
223            INIT_ZVAL(link_id);
224            ZVAL_RESOURCE(&link_id, event->link_res_id);
225
226            /* find out which event occurred */
227            isc_event_counts(occurred_event, buffer_size, event->event_buffer, event->result_buffer);
228            for (i = 0; i < event->event_count; ++i) {
229                if (occurred_event[i]) {
230                    ZVAL_STRING(&event_name,event->events[i],0);
231                    break;
232                }
233            }
234
235            /* call the callback provided by the user */
236            if (SUCCESS != call_user_function(EG(function_table), NULL,
237                    event->callback, &return_value, 2, args TSRMLS_CC)) {
238                _php_ibase_module_error("Error calling callback %s" TSRMLS_CC, Z_STRVAL_P(event->callback));
239                break;
240            }
241
242            if (Z_TYPE(return_value) == IS_BOOL && !Z_BVAL(return_value)) {
243                event->state = DEAD;
244                break;
245            }
246        case NEW:
247            /* re-register the event */
248            if (isc_que_events(IB_STATUS, &event->link->handle, &event->event_id, buffer_size,
249                event->event_buffer,(isc_callback)_php_ibase_callback, (void *)event)) {
250
251                _php_ibase_error(TSRMLS_C);
252            }
253            event->state = ACTIVE;
254    }
255    return 0;
256}
257/* }}} */
258
259/* {{{ proto resource ibase_set_event_handler([resource link_identifier,] callback handler, string event [, string event [, ...]])
260   Register the callback for handling each of the named events */
261PHP_FUNCTION(ibase_set_event_handler)
262{
263    /**
264     * The callback passed to this function should take an event name (string) and a
265     * link resource id (int) as arguments. The value returned from the function is
266     * used to determine if the event handler should remain set.
267     */
268    char *cb_name;
269    zval ***args, **cb_arg;
270    ibase_db_link *ib_link;
271    ibase_event *event;
272    unsigned short i = 1, buffer_size;
273    int link_res_id, num_args;
274
275    RESET_ERRMSG;
276
277    /* Minimum and maximum number of arguments allowed */
278    if (ZEND_NUM_ARGS() < 2 || ZEND_NUM_ARGS() > 17) {
279        WRONG_PARAM_COUNT;
280    }
281
282    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "+", &args, &num_args) == FAILURE) {
283        return;
284    }
285
286    /* get a working link */
287    if (Z_TYPE_PP(args[0]) != IS_STRING) {
288        /* resource, callback, event_1 [, ... event_15]
289         * No more than 15 events
290         */
291        if (ZEND_NUM_ARGS() < 3 || ZEND_NUM_ARGS() > 17) {
292            efree(args);
293            WRONG_PARAM_COUNT;
294        }
295
296        cb_arg = args[1];
297        i = 2;
298
299        if (!ZEND_FETCH_RESOURCE2_NO_RETURN(ib_link, ibase_db_link *, args[0], -1, "InterBase link", le_link, le_plink)) {
300            efree(args);
301            RETURN_FALSE;
302        }
303
304        convert_to_long_ex(args[0]);
305        link_res_id = Z_LVAL_PP(args[0]);
306
307    } else {
308        /* callback, event_1 [, ... event_15]
309         * No more than 15 events
310         */
311        if (ZEND_NUM_ARGS() < 2 || ZEND_NUM_ARGS() > 16) {
312            efree(args);
313            WRONG_PARAM_COUNT;
314        }
315
316        cb_arg = args[0];
317
318        if (!ZEND_FETCH_RESOURCE2_NO_RETURN(ib_link, ibase_db_link *, NULL, IBG(default_link), "InterBase link", le_link, le_plink)) {
319            efree(args);
320            RETURN_FALSE;
321        }
322        link_res_id = IBG(default_link);
323    }
324
325    /* get the callback */
326    if (!zend_is_callable(*cb_arg, 0, &cb_name TSRMLS_CC)) {
327        _php_ibase_module_error("Callback argument %s is not a callable function" TSRMLS_CC, cb_name);
328        efree(cb_name);
329        efree(args);
330        RETURN_FALSE;
331    }
332    efree(cb_name);
333
334    /* allocate the event resource */
335    event = (ibase_event *) safe_emalloc(sizeof(ibase_event), 1, 0);
336    TSRMLS_SET_CTX(event->thread_ctx);
337    event->link_res_id = link_res_id;
338    event->link = ib_link;
339    event->event_count = 0;
340    event->state = NEW;
341    event->events = (char **) safe_emalloc(sizeof(char *),ZEND_NUM_ARGS()-i,0);
342
343    ALLOC_ZVAL(event->callback);
344    *event->callback = **cb_arg;
345    INIT_PZVAL(event->callback);
346    zval_copy_ctor(event->callback);
347
348    for (; i < ZEND_NUM_ARGS(); ++i) {
349        convert_to_string_ex(args[i]);
350        event->events[event->event_count++] = estrdup(Z_STRVAL_PP(args[i]));
351    }
352
353    /* fills the required data structure with information about the events */
354    _php_ibase_event_block(ib_link, event->event_count, event->events,
355        &buffer_size, &event->event_buffer, &event->result_buffer);
356
357    /* now register the events with the Interbase API */
358    if (isc_que_events(IB_STATUS, &ib_link->handle, &event->event_id, buffer_size,
359        event->event_buffer,(isc_callback)_php_ibase_callback, (void *)event)) {
360
361        _php_ibase_error(TSRMLS_C);
362        efree(event);
363        efree(args);
364        RETURN_FALSE;
365    }
366
367    event->event_next = ib_link->event_head;
368    ib_link->event_head = event;
369
370    ZEND_REGISTER_RESOURCE(return_value, event, le_event);
371    zend_list_addref(Z_LVAL_P(return_value));
372    efree(args);
373}
374/* }}} */
375
376/* {{{ proto bool ibase_free_event_handler(resource event)
377   Frees the event handler set by ibase_set_event_handler() */
378PHP_FUNCTION(ibase_free_event_handler)
379{
380    zval *event_arg;
381
382    RESET_ERRMSG;
383
384    if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &event_arg)) {
385        ibase_event *event;
386
387        ZEND_FETCH_RESOURCE(event, ibase_event *, &event_arg, -1, "Interbase event", le_event);
388
389        event->state = DEAD;
390
391        zend_list_delete(Z_LVAL_P(event_arg));
392        RETURN_TRUE;
393    } else {
394        RETURN_FALSE;
395    }
396}
397/* }}} */
398
399#endif /* HAVE_IBASE */
400
401/*
402 * Local variables:
403 * tab-width: 4
404 * c-basic-offset: 4
405 * End:
406 * vim600: sw=4 ts=4 fdm=marker
407 * vim<600: sw=4 ts=4
408 */
409