xref: /PHP-7.0/ext/interbase/ibase_events.c (revision 478f119a)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2017 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 
30 static int le_event;
31 
_php_ibase_event_free(char * event_buf,char * result_buf)32 static 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 
_php_ibase_free_event(ibase_event * event)39 void _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 
_php_ibase_free_event_rsrc(zend_resource * rsrc)75 static 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 
php_ibase_events_minit(INIT_FUNC_ARGS)85 void 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 
_php_ibase_event_block(ibase_db_link * ib_link,unsigned short count,char ** events,unsigned short * l,char ** event_buf,char ** result_buf)92 static 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 */
PHP_FUNCTION(ibase_wait_event)127 PHP_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
_php_ibase_callback(ibase_event * event,ISC_USHORT buffer_size,ISC_UCHAR * result_buf)195 static 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
199 static 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 */
PHP_FUNCTION(ibase_set_event_handler)260 PHP_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() */
PHP_FUNCTION(ibase_free_event_handler)372 PHP_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