1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 7                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2016 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) /* {{{ */
40{
41	unsigned short i;
42
43	event->state = DEAD;
44
45	if (event->link != NULL) {
46		ibase_event **node;
47
48		zend_list_delete(event->link_res);
49		if (event->link->handle != 0 &&
50				isc_cancel_events(IB_STATUS, &event->link->handle, &event->event_id)) {
51			_php_ibase_error();
52		}
53
54		/* delete this event from the link struct */
55		for (node = &event->link->event_head; *node != event; node = &(*node)->event_next);
56		*node = event->event_next;
57	}
58
59	if (Z_TYPE(event->callback) != IS_UNDEF) {
60		zval_dtor(&event->callback);
61		ZVAL_UNDEF(&event->callback);
62
63		_php_ibase_event_free(event->event_buffer,event->result_buffer);
64
65		for (i = 0; i < event->event_count; ++i) {
66			if (event->events[i]) {
67				efree(event->events[i]);
68			}
69		}
70		efree(event->events);
71	}
72}
73/* }}} */
74
75static void _php_ibase_free_event_rsrc(zend_resource *rsrc) /* {{{ */
76{
77	ibase_event *e = (ibase_event *) rsrc->ptr;
78
79	_php_ibase_free_event(e);
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	ISC_ULONG 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	ISC_ULONG 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(), "+", &args, &num_args) == FAILURE) {
144		return;
145	}
146
147	if (Z_TYPE(args[0]) == IS_RESOURCE) {
148		if ((ib_link = (ibase_db_link *)zend_fetch_resource2_ex(&args[0], "InterBase link", le_link, le_plink)) == NULL) {
149			RETURN_FALSE;
150		}
151		i = 1;
152	} else {
153		if (ZEND_NUM_ARGS() > 15) {
154			WRONG_PARAM_COUNT;
155		}
156		if ((ib_link = (ibase_db_link *)zend_fetch_resource2(IBG(default_link), "InterBase link", le_link, le_plink)) == NULL) {
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();
172		_php_ibase_event_free(event_buffer,result_buffer);
173		RETURN_FALSE;
174	}
175
176	/* find out which event occurred */
177	isc_event_counts(occurred_event, buffer_size, event_buffer, result_buffer);
178	for (i = 0; i < event_count; ++i) {
179		if (occurred_event[i]) {
180			zend_string *result = zend_string_init(events[i], strlen(events[i]), 0);
181			_php_ibase_event_free(event_buffer,result_buffer);
182			RETURN_STR(result);
183		}
184	}
185
186	/* If we reach this line, isc_wait_for_event() did return, but we don't know
187	   which event fired. */
188	_php_ibase_event_free(event_buffer,result_buffer);
189	RETURN_FALSE;
190}
191/* }}} */
192
193#if FB_API_VER >= 20
194#define PHP_ISC_CALLBACK ISC_EVENT_CALLBACK
195static ISC_EVENT_CALLBACK _php_ibase_callback(ibase_event *event, /* {{{ */
196	ISC_USHORT buffer_size, ISC_UCHAR *result_buf)
197#else
198#define PHP_ISC_CALLBACK isc_callback
199static isc_callback  _php_ibase_callback(ibase_event *event, /* {{{ */
200	unsigned short buffer_size, char *result_buf)
201#endif
202{
203	/* this function is called asynchronously by the Interbase client library. */
204	TSRMLS_FETCH_FROM_CTX(event->thread_ctx);
205
206	/**
207	 * The callback function is called when the event is first registered and when the event
208	 * is cancelled. I consider this is a bug. By clearing event->callback first and setting
209	 * it to -1 later, we make sure nothing happens if no event was actually posted.
210	 */
211	switch (event->state) {
212		unsigned short i;
213		ISC_ULONG occurred_event[15];
214		zval return_value, args[2];
215
216		default: /* == DEAD */
217			break;
218		case ACTIVE:
219			/* copy the updated results into the result buffer */
220			memcpy(event->result_buffer, result_buf, buffer_size);
221
222			ZVAL_RES(&args[1], event->link_res);
223
224			/* find out which event occurred */
225			isc_event_counts(occurred_event, buffer_size, event->event_buffer, event->result_buffer);
226			for (i = 0; i < event->event_count; ++i) {
227				if (occurred_event[i]) {
228					ZVAL_STRING(&args[0], event->events[i]);
229					//efree(event->events[i]);
230					break;
231				}
232			}
233
234			/* call the callback provided by the user */
235			if (SUCCESS != call_user_function(EG(function_table), NULL,
236					&event->callback, &return_value, 2, args)) {
237				_php_ibase_module_error("Error calling callback %s", Z_STRVAL(event->callback));
238				break;
239			}
240
241			if (Z_TYPE(return_value) == IS_FALSE) {
242				event->state = DEAD;
243				break;
244			}
245		case NEW:
246			/* re-register the event */
247			if (isc_que_events(IB_STATUS, &event->link->handle, &event->event_id, buffer_size,
248				event->event_buffer,(PHP_ISC_CALLBACK)_php_ibase_callback, (void *)event)) {
249
250				_php_ibase_error();
251			}
252			event->state = ACTIVE;
253	}
254	return 0;
255}
256/* }}} */
257
258/* {{{ proto resource ibase_set_event_handler([resource link_identifier,] callback handler, string event [, string event [, ...]])
259   Register the callback for handling each of the named events */
260PHP_FUNCTION(ibase_set_event_handler)
261{
262	/**
263	 * The callback passed to this function should take an event name (string) and a
264	 * link resource id (int) as arguments. The value returned from the function is
265	 * used to determine if the event handler should remain set.
266	 */
267	zend_string *cb_name;
268	zval *args, *cb_arg;
269	ibase_db_link *ib_link;
270	ibase_event *event;
271	unsigned short i = 1, buffer_size;
272	int num_args;
273	zend_resource *link_res;
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(), "+", &args, &num_args) == FAILURE) {
283		return;
284	}
285
286	/* get a working link */
287	if (Z_TYPE(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			WRONG_PARAM_COUNT;
293		}
294
295		cb_arg = &args[1];
296		i = 2;
297
298		if ((ib_link = (ibase_db_link *)zend_fetch_resource2_ex(&args[0], "InterBase link", le_link, le_plink)) == NULL) {
299			RETURN_FALSE;
300		}
301
302		link_res = Z_RES(args[0]);
303
304	} else {
305		/* callback, event_1 [, ... event_15]
306		 * No more than 15 events
307		 */
308		if (ZEND_NUM_ARGS() < 2 || ZEND_NUM_ARGS() > 16) {
309			WRONG_PARAM_COUNT;
310		}
311
312		cb_arg = &args[0];
313
314		if ((ib_link = (ibase_db_link *)zend_fetch_resource2(IBG(default_link), "InterBase link", le_link, le_plink)) == NULL) {
315			RETURN_FALSE;
316		}
317		link_res = IBG(default_link);
318	}
319
320	/* get the callback */
321	if (!zend_is_callable(cb_arg, 0, &cb_name)) {
322		_php_ibase_module_error("Callback argument %s is not a callable function", ZSTR_VAL(cb_name));
323		zend_string_release(cb_name);
324		RETURN_FALSE;
325	}
326	zend_string_release(cb_name);
327
328	/* allocate the event resource */
329	event = (ibase_event *) safe_emalloc(sizeof(ibase_event), 1, 0);
330	TSRMLS_SET_CTX(event->thread_ctx);
331	event->link_res = link_res;
332	GC_REFCOUNT(link_res)++;
333	event->link = ib_link;
334	event->event_count = 0;
335	event->state = NEW;
336	event->events = (char **) safe_emalloc(sizeof(char *), 15, 0);
337
338	ZVAL_DUP(&event->callback, cb_arg);
339
340	for (; i < 15; ++i) {
341		if (i < ZEND_NUM_ARGS()) {
342			convert_to_string_ex(&args[i]);
343			event->events[event->event_count++] = estrdup(Z_STRVAL(args[i]));
344		} else {
345			event->events[i] = NULL;
346		}
347	}
348
349	/* fills the required data structure with information about the events */
350	_php_ibase_event_block(ib_link, event->event_count, event->events,
351		&buffer_size, &event->event_buffer, &event->result_buffer);
352
353	/* now register the events with the Interbase API */
354	if (isc_que_events(IB_STATUS, &ib_link->handle, &event->event_id, buffer_size,
355		event->event_buffer,(PHP_ISC_CALLBACK)_php_ibase_callback, (void *)event)) {
356
357		_php_ibase_error();
358		efree(event);
359		RETURN_FALSE;
360	}
361
362	event->event_next = ib_link->event_head;
363	ib_link->event_head = event;
364
365	RETVAL_RES(zend_register_resource(event, le_event));
366	Z_TRY_ADDREF_P(return_value);
367}
368/* }}} */
369
370/* {{{ proto bool ibase_free_event_handler(resource event)
371   Frees the event handler set by ibase_set_event_handler() */
372PHP_FUNCTION(ibase_free_event_handler)
373{
374	zval *event_arg;
375
376	RESET_ERRMSG;
377
378	if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "r", &event_arg)) {
379		ibase_event *event;
380
381		event = (ibase_event *)zend_fetch_resource_ex(event_arg, "Interbase event", le_event);
382
383		event->state = DEAD;
384
385		zend_list_delete(Z_RES_P(event_arg));
386		RETURN_TRUE;
387	} else {
388		RETURN_FALSE;
389	}
390}
391/* }}} */
392
393#endif /* HAVE_IBASE */
394
395/*
396 * Local variables:
397 * tab-width: 4
398 * c-basic-offset: 4
399 * End:
400 * vim600: sw=4 ts=4 fdm=marker
401 * vim<600: sw=4 ts=4
402 */
403