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