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