xref: /PHP-8.3/ext/dba/dba.c (revision d3b0efe9)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | https://www.php.net/license/3_01.txt                                 |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Authors: Sascha Schumann <sascha@schumann.cx>                        |
14    |          Marcus Boerger <helly@php.net>                              |
15    +----------------------------------------------------------------------+
16  */
17 
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21 
22 #include "php.h"
23 
24 #ifdef HAVE_DBA
25 
26 #include "php_ini.h"
27 #include <stdio.h>
28 #include <fcntl.h>
29 #ifdef HAVE_SYS_FILE_H
30 #include <sys/file.h>
31 #endif
32 
33 #include "php_dba.h"
34 #include "ext/standard/info.h"
35 #include "ext/standard/php_string.h"
36 #include "ext/standard/flock_compat.h"
37 
38 #include "php_gdbm.h"
39 #include "php_ndbm.h"
40 #include "php_dbm.h"
41 #include "php_cdb.h"
42 #include "php_db1.h"
43 #include "php_db2.h"
44 #include "php_db3.h"
45 #include "php_db4.h"
46 #include "php_flatfile.h"
47 #include "php_inifile.h"
48 #include "php_qdbm.h"
49 #include "php_tcadb.h"
50 #include "php_lmdb.h"
51 #include "dba_arginfo.h"
52 
53 PHP_MINIT_FUNCTION(dba);
54 PHP_MSHUTDOWN_FUNCTION(dba);
55 PHP_MINFO_FUNCTION(dba);
56 
57 ZEND_BEGIN_MODULE_GLOBALS(dba)
58 	const char *default_handler;
59 	const dba_handler *default_hptr;
60 ZEND_END_MODULE_GLOBALS(dba)
61 
62 ZEND_DECLARE_MODULE_GLOBALS(dba)
63 
64 #define DBA_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(dba, v)
65 
66 static PHP_GINIT_FUNCTION(dba);
67 
68 zend_module_entry dba_module_entry = {
69 	STANDARD_MODULE_HEADER,
70 	"dba",
71 	ext_functions,
72 	PHP_MINIT(dba),
73 	PHP_MSHUTDOWN(dba),
74 	NULL,
75 	NULL,
76 	PHP_MINFO(dba),
77 	PHP_DBA_VERSION,
78 	PHP_MODULE_GLOBALS(dba),
79 	PHP_GINIT(dba),
80 	NULL,
81 	NULL,
82 	STANDARD_MODULE_PROPERTIES_EX
83 };
84 
85 #ifdef COMPILE_DL_DBA
86 #ifdef ZTS
87 ZEND_TSRMLS_CACHE_DEFINE()
88 #endif
ZEND_GET_MODULE(dba)89 ZEND_GET_MODULE(dba)
90 #endif
91 
92 /* {{{ php_dba_make_key */
93 static zend_string* php_dba_make_key(HashTable *key)
94 {
95 	zval *group, *name;
96 	zend_string *group_str, *name_str;
97 	HashPosition pos;
98 
99 	if (zend_hash_num_elements(key) != 2) {
100 		zend_argument_error(NULL, 1, "must have exactly two elements: \"key\" and \"name\"");
101 		return NULL;
102 	}
103 
104 	// TODO: Use ZEND_HASH_FOREACH_VAL() API?
105 	zend_hash_internal_pointer_reset_ex(key, &pos);
106 	group = zend_hash_get_current_data_ex(key, &pos);
107 	group_str = zval_try_get_string(group);
108 	if (!group_str) {
109 		return NULL;
110 	}
111 
112 	zend_hash_move_forward_ex(key, &pos);
113 	name = zend_hash_get_current_data_ex(key, &pos);
114 	name_str = zval_try_get_string(name);
115 	if (!name_str) {
116 		zend_string_release_ex(group_str, false);
117 		return NULL;
118 	}
119 
120 	// TODO: Check ZSTR_LEN(name) != 0
121 	if (ZSTR_LEN(group_str) == 0) {
122 		zend_string_release_ex(group_str, false);
123 		return name_str;
124 	}
125 
126 	zend_string *key_str = zend_strpprintf(0, "[%s]%s", ZSTR_VAL(group_str), ZSTR_VAL(name_str));
127 	zend_string_release_ex(group_str, false);
128 	zend_string_release_ex(name_str, false);
129 	return key_str;
130 }
131 /* }}} */
132 
133 #define DBA_RELEASE_HT_KEY_CREATION() if (key_ht) {zend_string_release_ex(key_str, false);}
134 
135 #define DBA_FETCH_RESOURCE(info, id)	\
136 	if ((info = (dba_info *)zend_fetch_resource2(Z_RES_P(id), "DBA identifier", le_db, le_pdb)) == NULL) { \
137 		RETURN_THROWS(); \
138 	}
139 
140 /* check whether the user has write access */
141 #define DBA_WRITE_CHECK(info) \
142 	if ((info)->mode != DBA_WRITER && (info)->mode != DBA_TRUNC && (info)->mode != DBA_CREAT) { \
143 		php_error_docref(NULL, E_WARNING, "Cannot perform a modification on a readonly database"); \
144 		RETURN_FALSE; \
145 	}
146 
147 /* a DBA handler must have specific routines */
148 
149 #define DBA_NAMED_HND(alias, name, flags) \
150 {\
151 	#alias, flags, dba_open_##name, dba_close_##name, dba_fetch_##name, dba_update_##name, \
152 	dba_exists_##name, dba_delete_##name, dba_firstkey_##name, dba_nextkey_##name, \
153 	dba_optimize_##name, dba_sync_##name, dba_info_##name \
154 },
155 
156 #define DBA_HND(name, flags) DBA_NAMED_HND(name, name, flags)
157 
158 /* }}} */
159 
160 /* {{{ globals */
161 
162 static const dba_handler handler[] = {
163 #ifdef DBA_GDBM
164 	DBA_HND(gdbm, DBA_LOCK_EXT) /* Locking done in library if set */
165 #endif
166 #ifdef DBA_DBM
167 	DBA_HND(dbm, DBA_LOCK_ALL) /* No lock in lib */
168 #endif
169 #ifdef DBA_NDBM
170 	DBA_HND(ndbm, DBA_LOCK_ALL) /* Could be done in library: filemode = 0644 + S_ENFMT */
171 #endif
172 #ifdef DBA_CDB
173 	DBA_HND(cdb, DBA_STREAM_OPEN|DBA_LOCK_ALL) /* No lock in lib */
174 #endif
175 #ifdef DBA_CDB_BUILTIN
176 	DBA_NAMED_HND(cdb_make, cdb, DBA_STREAM_OPEN|DBA_LOCK_ALL) /* No lock in lib */
177 #endif
178 #ifdef DBA_DB1
179 	DBA_HND(db1, DBA_LOCK_ALL) /* No lock in lib */
180 #endif
181 #ifdef DBA_DB2
182 	DBA_HND(db2, DBA_LOCK_ALL) /* No lock in lib */
183 #endif
184 #ifdef DBA_DB3
185 	DBA_HND(db3, DBA_LOCK_ALL) /* No lock in lib */
186 #endif
187 #ifdef DBA_DB4
188 	DBA_HND(db4, DBA_LOCK_ALL) /* No lock in lib */
189 #endif
190 #ifdef DBA_INIFILE
191 	DBA_HND(inifile, DBA_STREAM_OPEN|DBA_LOCK_ALL|DBA_CAST_AS_FD) /* No lock in lib */
192 #endif
193 #ifdef DBA_FLATFILE
194 	DBA_HND(flatfile, DBA_STREAM_OPEN|DBA_LOCK_ALL|DBA_NO_APPEND) /* No lock in lib */
195 #endif
196 #ifdef DBA_QDBM
197 	DBA_HND(qdbm, DBA_LOCK_EXT)
198 #endif
199 #ifdef DBA_TCADB
200 	DBA_HND(tcadb, DBA_LOCK_ALL)
201 #endif
202 #ifdef DBA_LMDB
203 	DBA_HND(lmdb, DBA_LOCK_EXT)
204 #endif
205 	{ NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
206 };
207 
208 #ifdef DBA_FLATFILE
209 #define DBA_DEFAULT "flatfile"
210 #elif defined(DBA_DB4)
211 #define DBA_DEFAULT "db4"
212 #elif defined(DBA_DB3)
213 #define DBA_DEFAULT "db3"
214 #elif defined(DBA_DB2)
215 #define DBA_DEFAULT "db2"
216 #elif defined(DBA_DB1)
217 #define DBA_DEFAULT "db1"
218 #elif defined(DBA_GDBM)
219 #define DBA_DEFAULT "gdbm"
220 #elif defined(DBA_NBBM)
221 #define DBA_DEFAULT "ndbm"
222 #elif defined(DBA_DBM)
223 #define DBA_DEFAULT "dbm"
224 #elif defined(DBA_QDBM)
225 #define DBA_DEFAULT "qdbm"
226 #elif defined(DBA_TCADB)
227 #define DBA_DEFAULT "tcadb"
228 #elif defined(DBA_LMDB)
229 #define DBA_DEFAULT "lmdb"
230 #else
231 #define DBA_DEFAULT ""
232 #endif
233 /* cdb/cdb_make and ini are no option here */
234 
235 static int le_db;
236 static int le_pdb;
237 /* }}} */
238 
239 /* {{{ dba_fetch_resource
240 PHPAPI void dba_fetch_resource(dba_info **pinfo, zval **id)
241 {
242 	dba_info *info;
243 	DBA_ID_FETCH
244 	*pinfo = info;
245 }
246 */
247 /* }}} */
248 
249 /* {{{ dba_get_handler
250 PHPAPI dba_handler *dba_get_handler(const char* handler_name)
251 {
252 	const dba_handler *hptr;
253 	for (hptr = handler; hptr->name && strcasecmp(hptr->name, handler_name); hptr++);
254 	return hptr;
255 }
256 */
257 /* }}} */
258 
259 /* {{{ dba_close */
dba_close(dba_info * info)260 static void dba_close(dba_info *info)
261 {
262 	if (info->hnd) {
263 		info->hnd->close(info);
264 	}
265 	ZEND_ASSERT(info->path);
266 	zend_string_release_ex(info->path, info->flags&DBA_PERSISTENT);
267 	info->path = NULL;
268 
269 	if (info->fp && info->fp != info->lock.fp) {
270 		if (info->flags & DBA_PERSISTENT) {
271 			php_stream_pclose(info->fp);
272 		} else {
273 			php_stream_close(info->fp);
274 		}
275 	}
276 	if (info->lock.fp) {
277 		if (info->flags & DBA_PERSISTENT) {
278 			php_stream_pclose(info->lock.fp);
279 		} else {
280 			php_stream_close(info->lock.fp);
281 		}
282 	}
283 	pefree(info, info->flags&DBA_PERSISTENT);
284 }
285 /* }}} */
286 
287 /* {{{ dba_close_rsrc */
dba_close_rsrc(zend_resource * rsrc)288 static void dba_close_rsrc(zend_resource *rsrc)
289 {
290 	dba_info *info = (dba_info *)rsrc->ptr;
291 
292 	dba_close(info);
293 }
294 /* }}} */
295 
296 /* {{{ dba_close_pe_rsrc_deleter */
dba_close_pe_rsrc_deleter(zval * el,void * pDba)297 static int dba_close_pe_rsrc_deleter(zval *el, void *pDba)
298 {
299 	if (Z_RES_P(el)->ptr == pDba) {
300 		if (Z_DELREF_P(el) == 0) {
301 			return ZEND_HASH_APPLY_REMOVE;
302 		} else {
303 			return ZEND_HASH_APPLY_KEEP | ZEND_HASH_APPLY_STOP;
304 		}
305 	} else {
306 		return ZEND_HASH_APPLY_KEEP;
307 	}
308 }
309 /* }}} */
310 
311 /* {{{ dba_close_pe_rsrc */
dba_close_pe_rsrc(zend_resource * rsrc)312 static void dba_close_pe_rsrc(zend_resource *rsrc)
313 {
314 	dba_info *info = (dba_info *)rsrc->ptr;
315 
316 	/* closes the resource by calling dba_close_rsrc() */
317 	zend_hash_apply_with_argument(&EG(persistent_list), dba_close_pe_rsrc_deleter, info);
318 }
319 /* }}} */
320 
321 /* {{{ PHP_INI */
ZEND_INI_MH(OnUpdateDefaultHandler)322 static ZEND_INI_MH(OnUpdateDefaultHandler)
323 {
324 	const dba_handler *hptr;
325 
326 	if (!ZSTR_LEN(new_value)) {
327 		DBA_G(default_hptr) = NULL;
328 		return OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage);
329 	}
330 
331 	for (hptr = handler; hptr->name && strcasecmp(hptr->name, ZSTR_VAL(new_value)); hptr++);
332 
333 	if (!hptr->name) {
334 		php_error_docref(NULL, E_WARNING, "No such handler: %s", ZSTR_VAL(new_value));
335 		return FAILURE;
336 	}
337 	DBA_G(default_hptr) = hptr;
338 	return OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage);
339 }
340 
341 PHP_INI_BEGIN()
342 	STD_PHP_INI_ENTRY("dba.default_handler", DBA_DEFAULT, PHP_INI_ALL, OnUpdateDefaultHandler, default_handler,    zend_dba_globals, dba_globals)
PHP_INI_END()343 PHP_INI_END()
344 /* }}} */
345 
346 /* {{{ PHP_GINIT_FUNCTION */
347 static PHP_GINIT_FUNCTION(dba)
348 {
349 #if defined(COMPILE_DL_DBA) && defined(ZTS)
350 	ZEND_TSRMLS_CACHE_UPDATE();
351 #endif
352 	dba_globals->default_handler = "";
353 	dba_globals->default_hptr    = NULL;
354 }
355 /* }}} */
356 
357 /* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(dba)358 PHP_MINIT_FUNCTION(dba)
359 {
360 	REGISTER_INI_ENTRIES();
361 	le_db = zend_register_list_destructors_ex(dba_close_rsrc, NULL, "dba", module_number);
362 	le_pdb = zend_register_list_destructors_ex(dba_close_pe_rsrc, dba_close_rsrc, "dba persistent", module_number);
363 	register_dba_symbols(module_number);
364 	return SUCCESS;
365 }
366 /* }}} */
367 
368 /* {{{ PHP_MSHUTDOWN_FUNCTION */
PHP_MSHUTDOWN_FUNCTION(dba)369 PHP_MSHUTDOWN_FUNCTION(dba)
370 {
371 	UNREGISTER_INI_ENTRIES();
372 	return SUCCESS;
373 }
374 /* }}} */
375 
376 #include "zend_smart_str.h"
377 
378 /* {{{ PHP_MINFO_FUNCTION */
PHP_MINFO_FUNCTION(dba)379 PHP_MINFO_FUNCTION(dba)
380 {
381 	const dba_handler *hptr;
382 	smart_str handlers = {0};
383 
384 	for(hptr = handler; hptr->name; hptr++) {
385 		smart_str_appends(&handlers, hptr->name);
386 		smart_str_appendc(&handlers, ' ');
387 	}
388 
389 	php_info_print_table_start();
390 	php_info_print_table_row(2, "DBA support", "enabled");
391 	if (handlers.s) {
392 		smart_str_0(&handlers);
393 		php_info_print_table_row(2, "Supported handlers", ZSTR_VAL(handlers.s));
394 		smart_str_free(&handlers);
395 	} else {
396 		php_info_print_table_row(2, "Supported handlers", "none");
397 	}
398 	php_info_print_table_end();
399 	DISPLAY_INI_ENTRIES();
400 }
401 /* }}} */
402 
403 /* {{{ php_dba_update */
php_dba_update(INTERNAL_FUNCTION_PARAMETERS,int mode)404 static void php_dba_update(INTERNAL_FUNCTION_PARAMETERS, int mode)
405 {
406 	zval *id;
407 	dba_info *info = NULL;
408 	HashTable *key_ht = NULL;
409 	zend_string *key_str = NULL;
410 	zend_string *value;
411 
412 	ZEND_PARSE_PARAMETERS_START(3, 3)
413 		Z_PARAM_ARRAY_HT_OR_STR(key_ht, key_str)
414 		Z_PARAM_STR(value)
415 		Z_PARAM_RESOURCE(id);
416 	ZEND_PARSE_PARAMETERS_END();
417 
418 	DBA_FETCH_RESOURCE(info, id);
419 	DBA_WRITE_CHECK(info);
420 
421 	if (key_ht) {
422 		key_str = php_dba_make_key(key_ht);
423 		if (!key_str) {
424 			// TODO ValueError?
425 			RETURN_FALSE;
426 		}
427 	}
428 
429 	RETVAL_BOOL(info->hnd->update(info, key_str, value, mode) == SUCCESS);
430 	DBA_RELEASE_HT_KEY_CREATION();
431 }
432 /* }}} */
433 
434 /* {{{ php_find_dbm */
php_dba_find(const zend_string * path)435 static dba_info *php_dba_find(const zend_string *path)
436 {
437 	zend_resource *le;
438 	dba_info *info;
439 	zend_long numitems, i;
440 
441 	numitems = zend_hash_next_free_element(&EG(regular_list));
442 	for (i=1; i<numitems; i++) {
443 		if ((le = zend_hash_index_find_ptr(&EG(regular_list), i)) == NULL) {
444 			continue;
445 		}
446 		if (le->type == le_db || le->type == le_pdb) {
447 			info = (dba_info *)(le->ptr);
448 			if (zend_string_equals(path, info->path)) {
449 				return (dba_info *)(le->ptr);
450 			}
451 		}
452 	}
453 
454 	return NULL;
455 }
456 /* }}} */
457 
php_dba_zend_string_dup_safe(zend_string * s,bool persistent)458 static zend_always_inline zend_string *php_dba_zend_string_dup_safe(zend_string *s, bool persistent)
459 {
460 	if (ZSTR_IS_INTERNED(s) && !persistent) {
461 		return s;
462 	} else {
463 		zend_string *duplicated_str = zend_string_init(ZSTR_VAL(s), ZSTR_LEN(s), persistent);
464 		if (persistent) {
465 			GC_MAKE_PERSISTENT_LOCAL(duplicated_str);
466 		}
467 		return duplicated_str;
468 	}
469 }
470 
471 
472 #define FREE_PERSISTENT_RESOURCE_KEY() if (persistent_resource_key) {zend_string_release_ex(persistent_resource_key, false);}
473 
474 /* {{{ php_dba_open */
php_dba_open(INTERNAL_FUNCTION_PARAMETERS,bool persistent)475 static void php_dba_open(INTERNAL_FUNCTION_PARAMETERS, bool persistent)
476 {
477 	dba_mode_t modenr;
478 	dba_info *info, *other;
479 	const dba_handler *hptr;
480 	const char *error = NULL;
481 	int lock_mode, lock_flag = 0;
482 	const char *file_mode;
483 	const char *lock_file_mode = NULL;
484 	int persistent_flag = persistent ? STREAM_OPEN_PERSISTENT : 0;
485 	char *lock_name;
486 #ifdef PHP_WIN32
487 	bool restarted = 0;
488 	bool need_creation = 0;
489 #endif
490 
491 	zend_string *path;
492 	zend_string *mode;
493 	zend_string *handler_str = NULL;
494 	zend_long permission = 0644;
495 	zend_long map_size = 0;
496 	zend_long driver_flags = DBA_DEFAULT_DRIVER_FLAGS;
497 	bool is_flags_null = true;
498 	zend_string *persistent_resource_key = NULL;
499 
500 	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "PS|S!lll!", &path, &mode, &handler_str,
501 			&permission, &map_size, &driver_flags, &is_flags_null)) {
502 		RETURN_THROWS();
503 	}
504 
505 	if (ZSTR_LEN(path) == 0) {
506 		zend_argument_value_error(1, "cannot be empty");
507 		RETURN_THROWS();
508 	}
509 	if (ZSTR_LEN(mode) == 0) {
510 		zend_argument_value_error(2, "cannot be empty");
511 		RETURN_THROWS();
512 	}
513 	if (handler_str && ZSTR_LEN(handler_str) == 0) {
514 		zend_argument_value_error(3, "cannot be empty");
515 		RETURN_THROWS();
516 	}
517 	// TODO Check Value for permission
518 	if (map_size < 0) {
519 		zend_argument_value_error(5, "must be greater than or equal to 0");
520 		RETURN_THROWS();
521 	}
522 
523 	if (!is_flags_null && driver_flags < 0) {
524 		zend_argument_value_error(6, "must be greater than or equal to 0");
525 		RETURN_THROWS();
526 	}
527 
528 	if (persistent) {
529 		zend_resource *le;
530 
531 		if (handler_str) {
532 			persistent_resource_key = zend_string_concat3(
533 				ZSTR_VAL(path), ZSTR_LEN(path),
534 				ZSTR_VAL(mode), ZSTR_LEN(mode),
535 				ZSTR_VAL(handler_str), ZSTR_LEN(handler_str)
536 			);
537 		} else {
538 			persistent_resource_key = zend_string_concat2(
539 				ZSTR_VAL(path), ZSTR_LEN(path),
540 				ZSTR_VAL(mode), ZSTR_LEN(mode)
541 			);
542 		}
543 
544 		/* try to find if we already have this link in our persistent list */
545 		if ((le = zend_hash_find_ptr(&EG(persistent_list), persistent_resource_key)) != NULL) {
546 			FREE_PERSISTENT_RESOURCE_KEY();
547 			if (le->type != le_pdb) {
548 				// TODO This should never happen
549 				RETURN_FALSE;
550 			}
551 
552 			info = (dba_info *)le->ptr;
553 
554 			GC_ADDREF(le);
555 			RETURN_RES(zend_register_resource(info, le_pdb));
556 		}
557 	}
558 
559 	if (!handler_str) {
560 		hptr = DBA_G(default_hptr);
561 		if (!hptr) {
562 			php_error_docref(NULL, E_WARNING, "No default handler selected");
563 			FREE_PERSISTENT_RESOURCE_KEY();
564 			RETURN_FALSE;
565 		}
566 		ZEND_ASSERT(hptr->name);
567 	} else {
568 		/* Loop through global static var handlers to see if such a handler exists */
569 		for (hptr = handler; hptr->name && strcasecmp(hptr->name, ZSTR_VAL(handler_str)); hptr++);
570 
571 		if (!hptr->name) {
572 			php_error_docref(NULL, E_WARNING, "Handler \"%s\" is not available", ZSTR_VAL(handler_str));
573 			FREE_PERSISTENT_RESOURCE_KEY();
574 			RETURN_FALSE;
575 		}
576 	}
577 
578 	/* Check mode: [rwnc][dl-]?t?
579 	 * r: Read
580 	 * w: Write
581 	 * n: Create/Truncate
582 	 * c: Create
583 	 *
584 	 * d: force lock on database file
585 	 * l: force lock on lck file
586 	 * -: ignore locking
587 	 *
588 	 * t: test open database, warning if locked
589 	 */
590 	bool is_test_lock = false;
591 	bool is_db_lock = false;
592 	bool is_lock_ignored = false;
593 	// bool is_file_lock = false;
594 
595 	if (ZSTR_LEN(mode) == 0) {
596 		zend_argument_value_error(2, "cannot be empty");
597 		FREE_PERSISTENT_RESOURCE_KEY();
598 		RETURN_THROWS();
599 	}
600 	if (ZSTR_LEN(mode) > 3) {
601 		zend_argument_value_error(2, "must be at most 3 characters");
602 		FREE_PERSISTENT_RESOURCE_KEY();
603 		RETURN_THROWS();
604 	}
605 	if (ZSTR_LEN(mode) == 3) {
606 		if (ZSTR_VAL(mode)[2] != 't') {
607 			zend_argument_value_error(2, "third character must be \"t\"");
608 			FREE_PERSISTENT_RESOURCE_KEY();
609 			RETURN_THROWS();
610 		}
611 		is_test_lock = true;
612 	}
613 	if (ZSTR_LEN(mode) >= 2) {
614 		switch (ZSTR_VAL(mode)[1]) {
615 			case 't':
616 				is_test_lock = true;
617 				break;
618 			case '-':
619 				if ((hptr->flags & DBA_LOCK_ALL) == 0) {
620 					php_error_docref(NULL, E_WARNING, "Locking cannot be disabled for handler %s", hptr->name);
621 					FREE_PERSISTENT_RESOURCE_KEY();
622 					RETURN_FALSE;
623 				}
624 				is_lock_ignored = true;
625 				lock_flag = 0;
626 			break;
627 			case 'd':
628 				is_db_lock = true;
629 				if ((hptr->flags & DBA_LOCK_ALL) == 0) {
630 					lock_flag = (hptr->flags & DBA_LOCK_ALL);
631 					break;
632 				}
633 				ZEND_FALLTHROUGH;
634 			case 'l':
635 				// is_file_lock = true;
636 				lock_flag = DBA_LOCK_ALL;
637 				if ((hptr->flags & DBA_LOCK_ALL) == 0) {
638 					php_error_docref(NULL, E_NOTICE, "Handler %s does locking internally", hptr->name);
639 				}
640 				break;
641 			default:
642 				zend_argument_value_error(2, "second character must be one of \"d\", \"l\", \"-\", or \"t\"");
643 				FREE_PERSISTENT_RESOURCE_KEY();
644 				RETURN_THROWS();
645 		}
646 	} else {
647 		lock_flag = (hptr->flags&DBA_LOCK_ALL);
648 		is_db_lock = true;
649 	}
650 
651 	switch (ZSTR_VAL(mode)[0]) {
652 		case 'r':
653 			modenr = DBA_READER;
654 			lock_mode = (lock_flag & DBA_LOCK_READER) ? LOCK_SH : 0;
655 			file_mode = "r";
656 			break;
657 		case 'w':
658 			modenr = DBA_WRITER;
659 			lock_mode = (lock_flag & DBA_LOCK_WRITER) ? LOCK_EX : 0;
660 			file_mode = "r+b";
661 			break;
662 		case 'c': {
663 #ifdef PHP_WIN32
664 			if (hptr->flags & (DBA_NO_APPEND|DBA_CAST_AS_FD)) {
665 				php_stream_statbuf ssb;
666 				need_creation = (SUCCESS != php_stream_stat_path(ZSTR_VAL(path), &ssb));
667 			}
668 #endif
669 			modenr = DBA_CREAT;
670 			lock_mode = (lock_flag & DBA_LOCK_CREAT) ? LOCK_EX : 0;
671 			if (lock_mode) {
672 				if (is_db_lock) {
673 					/* the create/append check will be done on the lock
674 					 * when the lib opens the file it is already created
675 					 */
676 					file_mode = "r+b";       /* read & write, seek 0 */
677 #ifdef PHP_WIN32
678 					if (!need_creation) {
679 						lock_file_mode = "r+b";
680 					} else
681 #endif
682 					lock_file_mode = "a+b";  /* append */
683 				} else {
684 #ifdef PHP_WIN32
685 					if (!need_creation) {
686 						file_mode = "r+b";
687 					} else
688 #endif
689 					file_mode = "a+b";       /* append */
690 					lock_file_mode = "w+b";  /* create/truncate */
691 				}
692 			} else {
693 #ifdef PHP_WIN32
694 				if (!need_creation) {
695 					file_mode = "r+b";
696 				} else
697 #endif
698 				file_mode = "a+b";
699 			}
700 			/* In case of the 'a+b' append mode, the handler is responsible
701 			 * to handle any rewind problems (see flatfile handler).
702 			 */
703 			break;
704 		}
705 		case 'n':
706 			modenr = DBA_TRUNC;
707 			lock_mode = (lock_flag & DBA_LOCK_TRUNC) ? LOCK_EX : 0;
708 			file_mode = "w+b";
709 			break;
710 		default:
711 			zend_argument_value_error(2, "first character must be one of \"r\", \"w\", \"c\", or \"n\"");
712 			FREE_PERSISTENT_RESOURCE_KEY();
713 			RETURN_THROWS();
714 	}
715 	if (!lock_file_mode) {
716 		lock_file_mode = file_mode;
717 	}
718 	if (is_test_lock) {
719 		if (is_lock_ignored) {
720 			zend_argument_value_error(2, "cannot combine mode \"-\" (no lock) and \"t\" (test lock)");
721 			FREE_PERSISTENT_RESOURCE_KEY();
722 			RETURN_THROWS();
723 		}
724 		if (!lock_mode) {
725 			if ((hptr->flags & DBA_LOCK_ALL) == 0) {
726 				php_error_docref(NULL, E_WARNING, "Handler %s uses its own locking which doesn't support mode modifier t (test lock)", hptr->name);
727 				FREE_PERSISTENT_RESOURCE_KEY();
728 				RETURN_FALSE;
729 			} else {
730 				php_error_docref(NULL, E_WARNING, "Handler %s doesn't uses locking for this mode which makes modifier t (test lock) obsolete", hptr->name);
731 				FREE_PERSISTENT_RESOURCE_KEY();
732 				RETURN_FALSE;
733 			}
734 		} else {
735 			lock_mode |= LOCK_NB; /* test =: non blocking */
736 		}
737 	}
738 
739 	info = pemalloc(sizeof(dba_info), persistent);
740 	memset(info, 0, sizeof(dba_info));
741 	info->path = php_dba_zend_string_dup_safe(path, persistent);
742 	info->mode = modenr;
743 	info->file_permission = permission;
744 	info->map_size = map_size;
745 	info->driver_flags = driver_flags;
746 	info->flags = (hptr->flags & ~DBA_LOCK_ALL) | (lock_flag & DBA_LOCK_ALL) | (persistent ? DBA_PERSISTENT : 0);
747 	info->lock.mode = lock_mode;
748 
749 	/* if any open call is a locking call:
750 	 * check if we already have a locking call open that should block this call
751 	 * the problem is some systems would allow read during write
752 	 */
753 	if (hptr->flags & DBA_LOCK_ALL) {
754 		if ((other = php_dba_find(info->path)) != NULL) {
755 			if (   ( (lock_mode&LOCK_EX)        && (other->lock.mode&(LOCK_EX|LOCK_SH)) )
756 			    || ( (other->lock.mode&LOCK_EX) && (lock_mode&(LOCK_EX|LOCK_SH))        )
757 			   ) {
758 				error = "Unable to establish lock (database file already open)"; /* force failure exit */
759 			}
760 		}
761 	}
762 
763 #ifdef PHP_WIN32
764 restart:
765 #endif
766 	if (!error && lock_mode) {
767 		if (is_db_lock) {
768 			lock_name = ZSTR_VAL(path);
769 		} else {
770 			spprintf(&lock_name, 0, "%s.lck", ZSTR_VAL(info->path));
771 			if (!strcmp(file_mode, "r")) {
772 				zend_string *opened_path = NULL;
773 				/* when in read only mode try to use existing .lck file first */
774 				/* do not log errors for .lck file while in read only mode on .lck file */
775 				lock_file_mode = "rb";
776 				info->lock.fp = php_stream_open_wrapper(lock_name, lock_file_mode, STREAM_MUST_SEEK|IGNORE_PATH|persistent_flag, &opened_path);
777 				if (opened_path) {
778 					zend_string_release_ex(opened_path, 0);
779 				}
780 			}
781 			if (!info->lock.fp) {
782 				/* when not in read mode or failed to open .lck file read only. now try again in create(write) mode and log errors */
783 				lock_file_mode = "a+b";
784 			}
785 		}
786 		if (!info->lock.fp) {
787 			zend_string *opened_path = NULL;
788 			info->lock.fp = php_stream_open_wrapper(lock_name, lock_file_mode, STREAM_MUST_SEEK|REPORT_ERRORS|IGNORE_PATH|persistent_flag, &opened_path);
789 			if (info->lock.fp) {
790 				if (is_db_lock) {
791 					if (opened_path) {
792 						/* replace the path info with the real path of the opened file */
793 						zend_string_release_ex(info->path, persistent);
794 						info->path = php_dba_zend_string_dup_safe(opened_path, persistent);
795 					} else {
796 						error = "Unable to determine path for locking";
797 					}
798 				}
799 			}
800 			if (opened_path) {
801 				zend_string_release_ex(opened_path, 0);
802 			}
803 		}
804 		if (!is_db_lock) {
805 			efree(lock_name);
806 		}
807 		if (!info->lock.fp) {
808 			dba_close(info);
809 			/* stream operation already wrote an error message */
810 			FREE_PERSISTENT_RESOURCE_KEY();
811 			RETURN_FALSE;
812 		}
813 		if (!error && !php_stream_supports_lock(info->lock.fp)) {
814 			error = "Stream does not support locking";
815 		}
816 		if (!error && php_stream_lock(info->lock.fp, lock_mode)) {
817 			error = "Unable to establish lock"; /* force failure exit */
818 		}
819 	}
820 
821 	/* centralised open stream for builtin */
822 	if (!error && (hptr->flags&DBA_STREAM_OPEN)==DBA_STREAM_OPEN) {
823 		if (info->lock.fp && is_db_lock) {
824 			info->fp = info->lock.fp; /* use the same stream for locking and database access */
825 		} else {
826 			info->fp = php_stream_open_wrapper(ZSTR_VAL(info->path), file_mode, STREAM_MUST_SEEK|REPORT_ERRORS|IGNORE_PATH|persistent_flag, NULL);
827 		}
828 		if (!info->fp) {
829 			dba_close(info);
830 			/* stream operation already wrote an error message */
831 			FREE_PERSISTENT_RESOURCE_KEY();
832 			RETURN_FALSE;
833 		}
834 		if (hptr->flags & (DBA_NO_APPEND|DBA_CAST_AS_FD)) {
835 			/* Needed because some systems do not allow to write to the original
836 			 * file contents with O_APPEND being set.
837 			 */
838 			if (SUCCESS != php_stream_cast(info->fp, PHP_STREAM_AS_FD, (void*)&info->fd, 1)) {
839 				php_error_docref(NULL, E_WARNING, "Could not cast stream");
840 				dba_close(info);
841 				FREE_PERSISTENT_RESOURCE_KEY();
842 				RETURN_FALSE;
843 #ifdef F_SETFL
844 			} else if (modenr == DBA_CREAT) {
845 				int flags = fcntl(info->fd, F_GETFL);
846 				fcntl(info->fd, F_SETFL, flags & ~O_APPEND);
847 #elif defined(PHP_WIN32)
848 			} else if (modenr == DBA_CREAT && need_creation && !restarted) {
849 				if (info->lock.fp != NULL) {
850 					php_stream_free(info->lock.fp, persistent ? PHP_STREAM_FREE_CLOSE_PERSISTENT : PHP_STREAM_FREE_CLOSE);
851 				}
852 				if (info->fp != info->lock.fp) {
853 					php_stream_free(info->fp, persistent ? PHP_STREAM_FREE_CLOSE_PERSISTENT : PHP_STREAM_FREE_CLOSE);
854 				}
855 				info->fp = NULL;
856 				info->lock.fp = NULL;
857 				info->fd = -1;
858 
859 				lock_file_mode = "r+b";
860 
861 				restarted = 1;
862 				goto restart;
863 #endif
864 			}
865 		}
866 	}
867 
868 	if (error || hptr->open(info, &error) == FAILURE) {
869 		dba_close(info);
870 		if (EXPECTED(!EG(exception))) {
871 			if (error) {
872 				php_error_docref(NULL, E_WARNING, "Driver initialization failed for handler: %s: %s", hptr->name, error);
873 			} else {
874 				php_error_docref(NULL, E_WARNING, "Driver initialization failed for handler: %s", hptr->name);
875 			}
876 		}
877 		FREE_PERSISTENT_RESOURCE_KEY();
878 		RETURN_FALSE;
879 	}
880 
881 	info->hnd = hptr;
882 
883 	if (persistent) {
884 		ZEND_ASSERT(persistent_resource_key);
885 		if (zend_register_persistent_resource_ex(persistent_resource_key, info, le_pdb) == NULL) {
886 			dba_close(info);
887 			php_error_docref(NULL, E_WARNING, "Could not register persistent resource");
888 			FREE_PERSISTENT_RESOURCE_KEY();
889 			RETURN_FALSE;
890 		}
891 		FREE_PERSISTENT_RESOURCE_KEY();
892 	}
893 
894 	RETURN_RES(zend_register_resource(info, (persistent ? le_pdb : le_db)));
895 }
896 /* }}} */
897 
898 /* {{{ Opens path using the specified handler in mode persistently */
PHP_FUNCTION(dba_popen)899 PHP_FUNCTION(dba_popen)
900 {
901 	php_dba_open(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
902 }
903 /* }}} */
904 
905 /* {{{ Opens path using the specified handler in mode*/
PHP_FUNCTION(dba_open)906 PHP_FUNCTION(dba_open)
907 {
908 	php_dba_open(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
909 }
910 /* }}} */
911 
912 /* {{{ Closes database */
PHP_FUNCTION(dba_close)913 PHP_FUNCTION(dba_close)
914 {
915 	zval *id;
916 	dba_info *info = NULL;
917 
918 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &id) == FAILURE) {
919 		RETURN_THROWS();
920 	}
921 
922 	DBA_FETCH_RESOURCE(info, id);
923 
924 	zend_list_close(Z_RES_P(id));
925 }
926 /* }}} */
927 
928 /* {{{ Checks, if the specified key exists */
PHP_FUNCTION(dba_exists)929 PHP_FUNCTION(dba_exists)
930 {
931 	zval *id;
932 	dba_info *info = NULL;
933 	HashTable *key_ht = NULL;
934 	zend_string *key_str = NULL;
935 
936 	ZEND_PARSE_PARAMETERS_START(2, 2)
937 		Z_PARAM_ARRAY_HT_OR_STR(key_ht, key_str)
938 		Z_PARAM_RESOURCE(id);
939 	ZEND_PARSE_PARAMETERS_END();
940 
941 	DBA_FETCH_RESOURCE(info, id);
942 
943 	if (key_ht) {
944 		key_str = php_dba_make_key(key_ht);
945 		if (!key_str) {
946 			// TODO ValueError?
947 			RETURN_FALSE;
948 		}
949 	}
950 
951 	RETVAL_BOOL(info->hnd->exists(info, key_str) == SUCCESS);
952 	DBA_RELEASE_HT_KEY_CREATION();
953 }
954 /* }}} */
955 
956 /* {{{ Fetches the data associated with key */
PHP_FUNCTION(dba_fetch)957 PHP_FUNCTION(dba_fetch)
958 {
959 	zval *id;
960 	dba_info *info = NULL;
961 	HashTable *key_ht = NULL;
962 	zend_string *key_str = NULL;
963 	zend_long skip = 0;
964 
965 	/* Check for legacy signature */
966 	if (ZEND_NUM_ARGS() == 3) {
967 		ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_QUIET, 3, 3)
968 			Z_PARAM_ARRAY_HT_OR_STR(key_ht, key_str)
969 			Z_PARAM_LONG(skip)
970 			Z_PARAM_RESOURCE(id);
971 		ZEND_PARSE_PARAMETERS_END_EX(goto standard;);
972 
973 		zend_error(E_DEPRECATED, "Calling dba_fetch() with $dba at the 3rd parameter is deprecated");
974 		if (UNEXPECTED(EG(exception))) {
975 			RETURN_THROWS();
976 		}
977 	} else {
978 		standard:
979 		ZEND_PARSE_PARAMETERS_START(2, 3)
980 			Z_PARAM_ARRAY_HT_OR_STR(key_ht, key_str)
981 			Z_PARAM_RESOURCE(id);
982 			Z_PARAM_OPTIONAL
983 			Z_PARAM_LONG(skip)
984 		ZEND_PARSE_PARAMETERS_END();
985 	}
986 
987 	DBA_FETCH_RESOURCE(info, id);
988 
989 	if (key_ht) {
990 		key_str = php_dba_make_key(key_ht);
991 		if (!key_str) {
992 			// TODO ValueError?
993 			RETURN_FALSE;
994 		}
995 	}
996 
997 	if (skip != 0) {
998 		if (!strcmp(info->hnd->name, "cdb")) {
999 			// TODO ValueError?
1000 			if (skip < 0) {
1001 				php_error_docref(NULL, E_NOTICE, "Handler %s accepts only skip values greater than or equal to zero, using skip=0", info->hnd->name);
1002 				skip = 0;
1003 			}
1004 		} else if (!strcmp(info->hnd->name, "inifile")) {
1005 			/* "-1" is comparable to 0 but allows a non restrictive
1006 			 * access which is faster. For example 'inifile' uses this
1007 			 * to allow faster access when the key was already found
1008 			 * using firstkey/nextkey. However explicitly setting the
1009 			 * value to 0 ensures the first value.
1010 			 */
1011 			if (skip < -1) {
1012 				// TODO ValueError?
1013 				php_error_docref(NULL, E_NOTICE, "Handler %s accepts only skip value -1 and greater, using skip=0", info->hnd->name);
1014 				skip = 0;
1015 			}
1016 		} else {
1017 			php_error_docref(NULL, E_NOTICE, "Handler %s does not support optional skip parameter, the value will be ignored", info->hnd->name);
1018 			skip = 0;
1019 		}
1020 	}
1021 
1022 	zend_string *val;
1023 	if ((val = info->hnd->fetch(info, key_str, skip)) == NULL) {
1024 		DBA_RELEASE_HT_KEY_CREATION();
1025 		RETURN_FALSE;
1026 	}
1027 	DBA_RELEASE_HT_KEY_CREATION();
1028 	RETURN_STR(val);
1029 }
1030 /* }}} */
1031 
1032 /* {{{ Splits an inifile key into an array of the form array(0=>group,1=>value_name) but returns false if input is false or null */
PHP_FUNCTION(dba_key_split)1033 PHP_FUNCTION(dba_key_split)
1034 {
1035 	zval *zkey;
1036 	char *key, *name;
1037 	size_t key_len;
1038 
1039 	if (ZEND_NUM_ARGS() != 1) {
1040 		WRONG_PARAM_COUNT;
1041 	}
1042 	if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "z", &zkey) == SUCCESS) {
1043 		if (Z_TYPE_P(zkey) == IS_NULL || (Z_TYPE_P(zkey) == IS_FALSE)) {
1044 			RETURN_FALSE;
1045 		}
1046 	}
1047 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &key, &key_len) == FAILURE) {
1048 		RETURN_THROWS();
1049 	}
1050 	array_init(return_value);
1051 	if (key[0] == '[' && (name = strchr(key, ']')) != NULL) {
1052 		add_next_index_stringl(return_value, key+1, name - (key + 1));
1053 		add_next_index_stringl(return_value, name+1, key_len - (name - key + 1));
1054 	} else {
1055 		add_next_index_stringl(return_value, "", 0);
1056 		add_next_index_stringl(return_value, key, key_len);
1057 	}
1058 }
1059 /* }}} */
1060 
1061 /* {{{ Resets the internal key pointer and returns the first key */
PHP_FUNCTION(dba_firstkey)1062 PHP_FUNCTION(dba_firstkey)
1063 {
1064 	zval *id;
1065 	dba_info *info = NULL;
1066 
1067 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &id) == FAILURE) {
1068 		RETURN_THROWS();
1069 	}
1070 
1071 	DBA_FETCH_RESOURCE(info, id);
1072 
1073 	zend_string *fkey = info->hnd->firstkey(info);
1074 
1075 	if (fkey) {
1076 		RETURN_STR(fkey);
1077 	}
1078 
1079 	RETURN_FALSE;
1080 }
1081 /* }}} */
1082 
1083 /* {{{ Returns the next key */
PHP_FUNCTION(dba_nextkey)1084 PHP_FUNCTION(dba_nextkey)
1085 {
1086 	zval *id;
1087 	dba_info *info = NULL;
1088 
1089 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &id) == FAILURE) {
1090 		RETURN_THROWS();
1091 	}
1092 
1093 	DBA_FETCH_RESOURCE(info, id);
1094 
1095 	zend_string *nkey = info->hnd->nextkey(info);
1096 
1097 	if (nkey) {
1098 		RETURN_STR(nkey);
1099 	}
1100 
1101 	RETURN_FALSE;
1102 }
1103 /* }}} */
1104 
1105 /* {{{ Deletes the entry associated with key
1106    If inifile: remove all other key lines */
PHP_FUNCTION(dba_delete)1107 PHP_FUNCTION(dba_delete)
1108 {
1109 	zval *id;
1110 	dba_info *info = NULL;
1111 	HashTable *key_ht = NULL;
1112 	zend_string *key_str = NULL;
1113 
1114 	ZEND_PARSE_PARAMETERS_START(2, 2)
1115 		Z_PARAM_ARRAY_HT_OR_STR(key_ht, key_str)
1116 		Z_PARAM_RESOURCE(id);
1117 	ZEND_PARSE_PARAMETERS_END();
1118 
1119 	DBA_FETCH_RESOURCE(info, id);
1120 	DBA_WRITE_CHECK(info);
1121 
1122 	if (key_ht) {
1123 		key_str = php_dba_make_key(key_ht);
1124 		if (!key_str) {
1125 			// TODO ValueError?
1126 			RETURN_FALSE;
1127 		}
1128 	}
1129 
1130 	RETVAL_BOOL(info->hnd->delete(info, key_str) == SUCCESS);
1131 	DBA_RELEASE_HT_KEY_CREATION();
1132 }
1133 /* }}} */
1134 
1135 /* {{{ If not inifile: Insert value as key, return false, if key exists already
1136    If inifile: Add vakue as key (next instance of key) */
PHP_FUNCTION(dba_insert)1137 PHP_FUNCTION(dba_insert)
1138 {
1139 	php_dba_update(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
1140 }
1141 /* }}} */
1142 
1143 /* {{{ Inserts value as key, replaces key, if key exists already
1144    If inifile: remove all other key lines */
PHP_FUNCTION(dba_replace)1145 PHP_FUNCTION(dba_replace)
1146 {
1147 	php_dba_update(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
1148 }
1149 /* }}} */
1150 
1151 /* {{{ Optimizes (e.g. clean up, vacuum) database */
PHP_FUNCTION(dba_optimize)1152 PHP_FUNCTION(dba_optimize)
1153 {
1154 	zval *id;
1155 	dba_info *info = NULL;
1156 
1157 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &id) == FAILURE) {
1158 		RETURN_THROWS();
1159 	}
1160 
1161 	DBA_FETCH_RESOURCE(info, id);
1162 	DBA_WRITE_CHECK(info);
1163 
1164 	if (info->hnd->optimize(info) == SUCCESS) {
1165 		RETURN_TRUE;
1166 	}
1167 
1168 	RETURN_FALSE;
1169 }
1170 /* }}} */
1171 
1172 /* {{{ Synchronizes database */
PHP_FUNCTION(dba_sync)1173 PHP_FUNCTION(dba_sync)
1174 {
1175 	zval *id;
1176 	dba_info *info = NULL;
1177 
1178 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &id) == FAILURE) {
1179 		RETURN_THROWS();
1180 	}
1181 
1182 	DBA_FETCH_RESOURCE(info, id);
1183 
1184 	if (info->hnd->sync(info) == SUCCESS) {
1185 		RETURN_TRUE;
1186 	}
1187 
1188 	RETURN_FALSE;
1189 }
1190 /* }}} */
1191 
1192 /* {{{ List configured database handlers */
PHP_FUNCTION(dba_handlers)1193 PHP_FUNCTION(dba_handlers)
1194 {
1195 	const dba_handler *hptr;
1196 	bool full_info = 0;
1197 
1198 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &full_info) == FAILURE) {
1199 		RETURN_THROWS();
1200 	}
1201 
1202 	array_init(return_value);
1203 
1204 	for(hptr = handler; hptr->name; hptr++) {
1205 		if (full_info) {
1206 			// TODO: avoid reallocation ???
1207 			char *str = hptr->info(hptr, NULL);
1208 			add_assoc_string(return_value, hptr->name, str);
1209 			efree(str);
1210 		} else {
1211 			add_next_index_string(return_value, hptr->name);
1212 		}
1213 	}
1214 }
1215 /* }}} */
1216 
1217 /* {{{ List opened databases */
PHP_FUNCTION(dba_list)1218 PHP_FUNCTION(dba_list)
1219 {
1220 	zend_ulong numitems, i;
1221 	zend_resource *le;
1222 	dba_info *info;
1223 
1224 	if (zend_parse_parameters_none() == FAILURE) {
1225 		RETURN_THROWS();
1226 	}
1227 
1228 	array_init(return_value);
1229 
1230 	numitems = zend_hash_next_free_element(&EG(regular_list));
1231 	for (i=1; i<numitems; i++) {
1232 		if ((le = zend_hash_index_find_ptr(&EG(regular_list), i)) == NULL) {
1233 			continue;
1234 		}
1235 		if (le->type == le_db || le->type == le_pdb) {
1236 			info = (dba_info *)(le->ptr);
1237 			add_index_str(return_value, i, zend_string_copy(info->path));
1238 		}
1239 	}
1240 }
1241 /* }}} */
1242 
1243 #endif /* HAVE_DBA */
1244