xref: /PHP-7.3/ext/interbase/ibase_events.c (revision 169d4545)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 7                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2018 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_ptr_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 	zval *args, *cb_arg;
268 	ibase_db_link *ib_link;
269 	ibase_event *event;
270 	unsigned short i = 1, buffer_size;
271 	int num_args;
272 	zend_resource *link_res;
273 
274 	RESET_ERRMSG;
275 
276 	/* Minimum and maximum number of arguments allowed */
277 	if (ZEND_NUM_ARGS() < 2 || ZEND_NUM_ARGS() > 17) {
278 		WRONG_PARAM_COUNT;
279 	}
280 
281 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &args, &num_args) == FAILURE) {
282 		return;
283 	}
284 
285 	/* get a working link */
286 	if (Z_TYPE(args[0]) != IS_STRING) {
287 		/* resource, callback, event_1 [, ... event_15]
288 		 * No more than 15 events
289 		 */
290 		if (ZEND_NUM_ARGS() < 3 || ZEND_NUM_ARGS() > 17) {
291 			WRONG_PARAM_COUNT;
292 		}
293 
294 		cb_arg = &args[1];
295 		i = 2;
296 
297 		if ((ib_link = (ibase_db_link *)zend_fetch_resource2_ex(&args[0], "InterBase link", le_link, le_plink)) == NULL) {
298 			RETURN_FALSE;
299 		}
300 
301 		link_res = Z_RES(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 ((ib_link = (ibase_db_link *)zend_fetch_resource2(IBG(default_link), "InterBase link", le_link, le_plink)) == NULL) {
314 			RETURN_FALSE;
315 		}
316 		link_res = IBG(default_link);
317 	}
318 
319 	/* get the callback */
320 	if (!zend_is_callable(cb_arg, 0, NULL)) {
321 		zend_string *cb_name = zend_get_callable_name(cb_arg);
322 		_php_ibase_module_error("Callback argument %s is not a callable function", ZSTR_VAL(cb_name));
323 		zend_string_release_ex(cb_name, 0);
324 		RETURN_FALSE;
325 	}
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 = link_res;
331 	GC_ADDREF(link_res);
332 	event->link = ib_link;
333 	event->event_count = 0;
334 	event->state = NEW;
335 	event->events = (char **) safe_emalloc(sizeof(char *), 15, 0);
336 
337 	ZVAL_DUP(&event->callback, cb_arg);
338 
339 	for (; i < 15; ++i) {
340 		if (i < ZEND_NUM_ARGS()) {
341 			convert_to_string_ex(&args[i]);
342 			event->events[event->event_count++] = estrdup(Z_STRVAL(args[i]));
343 		} else {
344 			event->events[i] = NULL;
345 		}
346 	}
347 
348 	/* fills the required data structure with information about the events */
349 	_php_ibase_event_block(ib_link, event->event_count, event->events,
350 		&buffer_size, &event->event_buffer, &event->result_buffer);
351 
352 	/* now register the events with the Interbase API */
353 	if (isc_que_events(IB_STATUS, &ib_link->handle, &event->event_id, buffer_size,
354 		event->event_buffer,(PHP_ISC_CALLBACK)_php_ibase_callback, (void *)event)) {
355 
356 		_php_ibase_error();
357 		efree(event);
358 		RETURN_FALSE;
359 	}
360 
361 	event->event_next = ib_link->event_head;
362 	ib_link->event_head = event;
363 
364 	RETVAL_RES(zend_register_resource(event, le_event));
365 	Z_TRY_ADDREF_P(return_value);
366 }
367 /* }}} */
368 
369 /* {{{ proto bool ibase_free_event_handler(resource event)
370    Frees the event handler set by ibase_set_event_handler() */
PHP_FUNCTION(ibase_free_event_handler)371 PHP_FUNCTION(ibase_free_event_handler)
372 {
373 	zval *event_arg;
374 
375 	RESET_ERRMSG;
376 
377 	if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "r", &event_arg)) {
378 		ibase_event *event;
379 
380 		event = (ibase_event *)zend_fetch_resource_ex(event_arg, "Interbase event", le_event);
381 
382 		event->state = DEAD;
383 
384 		zend_list_delete(Z_RES_P(event_arg));
385 		RETURN_TRUE;
386 	} else {
387 		RETURN_FALSE;
388 	}
389 }
390 /* }}} */
391 
392 #endif /* HAVE_IBASE */
393 
394 /*
395  * Local variables:
396  * tab-width: 4
397  * c-basic-offset: 4
398  * End:
399  * vim600: sw=4 ts=4 fdm=marker
400  * vim<600: sw=4 ts=4
401  */
402