xref: /php-src/ext/dba/dba.c (revision 134441ef)
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 					ZEND_ASSERT(opened_path);
792 					/* replace the path info with the real path of the opened file */
793 					zend_string_release(info->path);
794 					info->path = php_dba_zend_string_dup_safe(opened_path, persistent);
795 				}
796 			}
797 			if (opened_path) {
798 				zend_string_release_ex(opened_path, 0);
799 			}
800 		}
801 		if (!is_db_lock) {
802 			efree(lock_name);
803 		}
804 		if (!info->lock.fp) {
805 			dba_close(info);
806 			/* stream operation already wrote an error message */
807 			FREE_PERSISTENT_RESOURCE_KEY();
808 			RETURN_FALSE;
809 		}
810 		if (!php_stream_supports_lock(info->lock.fp)) {
811 			error = "Stream does not support locking";
812 		}
813 		if (php_stream_lock(info->lock.fp, lock_mode)) {
814 			error = "Unable to establish lock"; /* force failure exit */
815 		}
816 	}
817 
818 	/* centralised open stream for builtin */
819 	if (!error && (hptr->flags&DBA_STREAM_OPEN)==DBA_STREAM_OPEN) {
820 		if (info->lock.fp && is_db_lock) {
821 			info->fp = info->lock.fp; /* use the same stream for locking and database access */
822 		} else {
823 			info->fp = php_stream_open_wrapper(ZSTR_VAL(info->path), file_mode, STREAM_MUST_SEEK|REPORT_ERRORS|IGNORE_PATH|persistent_flag, NULL);
824 		}
825 		if (!info->fp) {
826 			dba_close(info);
827 			/* stream operation already wrote an error message */
828 			FREE_PERSISTENT_RESOURCE_KEY();
829 			RETURN_FALSE;
830 		}
831 		if (hptr->flags & (DBA_NO_APPEND|DBA_CAST_AS_FD)) {
832 			/* Needed because some systems do not allow to write to the original
833 			 * file contents with O_APPEND being set.
834 			 */
835 			if (SUCCESS != php_stream_cast(info->fp, PHP_STREAM_AS_FD, (void*)&info->fd, 1)) {
836 				php_error_docref(NULL, E_WARNING, "Could not cast stream");
837 				dba_close(info);
838 				FREE_PERSISTENT_RESOURCE_KEY();
839 				RETURN_FALSE;
840 #ifdef F_SETFL
841 			} else if (modenr == DBA_CREAT) {
842 				int flags = fcntl(info->fd, F_GETFL);
843 				fcntl(info->fd, F_SETFL, flags & ~O_APPEND);
844 #elif defined(PHP_WIN32)
845 			} else if (modenr == DBA_CREAT && need_creation && !restarted) {
846 				if (info->lock.fp != NULL) {
847 					php_stream_free(info->lock.fp, persistent ? PHP_STREAM_FREE_CLOSE_PERSISTENT : PHP_STREAM_FREE_CLOSE);
848 				}
849 				if (info->fp != info->lock.fp) {
850 					php_stream_free(info->fp, persistent ? PHP_STREAM_FREE_CLOSE_PERSISTENT : PHP_STREAM_FREE_CLOSE);
851 				}
852 				info->fp = NULL;
853 				info->lock.fp = NULL;
854 				info->fd = -1;
855 
856 				lock_file_mode = "r+b";
857 
858 				restarted = 1;
859 				goto restart;
860 #endif
861 			}
862 		}
863 	}
864 
865 	if (error || hptr->open(info, &error) == FAILURE) {
866 		dba_close(info);
867 		if (EXPECTED(!EG(exception))) {
868 			if (error) {
869 				php_error_docref(NULL, E_WARNING, "Driver initialization failed for handler: %s: %s", hptr->name, error);
870 			} else {
871 				php_error_docref(NULL, E_WARNING, "Driver initialization failed for handler: %s", hptr->name);
872 			}
873 		}
874 		FREE_PERSISTENT_RESOURCE_KEY();
875 		RETURN_FALSE;
876 	}
877 
878 	info->hnd = hptr;
879 
880 	if (persistent) {
881 		ZEND_ASSERT(persistent_resource_key);
882 		if (zend_register_persistent_resource_ex(persistent_resource_key, info, le_pdb) == NULL) {
883 			dba_close(info);
884 			php_error_docref(NULL, E_WARNING, "Could not register persistent resource");
885 			FREE_PERSISTENT_RESOURCE_KEY();
886 			RETURN_FALSE;
887 		}
888 		FREE_PERSISTENT_RESOURCE_KEY();
889 	}
890 
891 	RETURN_RES(zend_register_resource(info, (persistent ? le_pdb : le_db)));
892 }
893 /* }}} */
894 
895 /* {{{ Opens path using the specified handler in mode persistently */
PHP_FUNCTION(dba_popen)896 PHP_FUNCTION(dba_popen)
897 {
898 	php_dba_open(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
899 }
900 /* }}} */
901 
902 /* {{{ Opens path using the specified handler in mode*/
PHP_FUNCTION(dba_open)903 PHP_FUNCTION(dba_open)
904 {
905 	php_dba_open(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
906 }
907 /* }}} */
908 
909 /* {{{ Closes database */
PHP_FUNCTION(dba_close)910 PHP_FUNCTION(dba_close)
911 {
912 	zval *id;
913 	dba_info *info = NULL;
914 
915 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &id) == FAILURE) {
916 		RETURN_THROWS();
917 	}
918 
919 	DBA_FETCH_RESOURCE(info, id);
920 
921 	zend_list_close(Z_RES_P(id));
922 }
923 /* }}} */
924 
925 /* {{{ Checks, if the specified key exists */
PHP_FUNCTION(dba_exists)926 PHP_FUNCTION(dba_exists)
927 {
928 	zval *id;
929 	dba_info *info = NULL;
930 	HashTable *key_ht = NULL;
931 	zend_string *key_str = NULL;
932 
933 	ZEND_PARSE_PARAMETERS_START(2, 2)
934 		Z_PARAM_ARRAY_HT_OR_STR(key_ht, key_str)
935 		Z_PARAM_RESOURCE(id);
936 	ZEND_PARSE_PARAMETERS_END();
937 
938 	DBA_FETCH_RESOURCE(info, id);
939 
940 	if (key_ht) {
941 		key_str = php_dba_make_key(key_ht);
942 		if (!key_str) {
943 			// TODO ValueError?
944 			RETURN_FALSE;
945 		}
946 	}
947 
948 	RETVAL_BOOL(info->hnd->exists(info, key_str) == SUCCESS);
949 	DBA_RELEASE_HT_KEY_CREATION();
950 }
951 /* }}} */
952 
953 /* {{{ Fetches the data associated with key */
PHP_FUNCTION(dba_fetch)954 PHP_FUNCTION(dba_fetch)
955 {
956 	zval *id;
957 	dba_info *info = NULL;
958 	HashTable *key_ht = NULL;
959 	zend_string *key_str = NULL;
960 	zend_long skip = 0;
961 
962 	/* Check for legacy signature */
963 	if (ZEND_NUM_ARGS() == 3) {
964 		ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_QUIET, 3, 3)
965 			Z_PARAM_ARRAY_HT_OR_STR(key_ht, key_str)
966 			Z_PARAM_LONG(skip)
967 			Z_PARAM_RESOURCE(id);
968 		ZEND_PARSE_PARAMETERS_END_EX(goto standard;);
969 
970 		zend_error(E_DEPRECATED, "Calling dba_fetch() with $dba at the 3rd parameter is deprecated");
971 		if (UNEXPECTED(EG(exception))) {
972 			RETURN_THROWS();
973 		}
974 	} else {
975 		standard:
976 		ZEND_PARSE_PARAMETERS_START(2, 3)
977 			Z_PARAM_ARRAY_HT_OR_STR(key_ht, key_str)
978 			Z_PARAM_RESOURCE(id);
979 			Z_PARAM_OPTIONAL
980 			Z_PARAM_LONG(skip)
981 		ZEND_PARSE_PARAMETERS_END();
982 	}
983 
984 	DBA_FETCH_RESOURCE(info, id);
985 
986 	if (key_ht) {
987 		key_str = php_dba_make_key(key_ht);
988 		if (!key_str) {
989 			// TODO ValueError?
990 			RETURN_FALSE;
991 		}
992 	}
993 
994 	if (skip != 0) {
995 		if (!strcmp(info->hnd->name, "cdb")) {
996 			// TODO ValueError?
997 			if (skip < 0) {
998 				php_error_docref(NULL, E_NOTICE, "Handler %s accepts only skip values greater than or equal to zero, using skip=0", info->hnd->name);
999 				skip = 0;
1000 			}
1001 		} else if (!strcmp(info->hnd->name, "inifile")) {
1002 			/* "-1" is comparable to 0 but allows a non restrictive
1003 			 * access which is faster. For example 'inifile' uses this
1004 			 * to allow faster access when the key was already found
1005 			 * using firstkey/nextkey. However explicitly setting the
1006 			 * value to 0 ensures the first value.
1007 			 */
1008 			if (skip < -1) {
1009 				// TODO ValueError?
1010 				php_error_docref(NULL, E_NOTICE, "Handler %s accepts only skip value -1 and greater, using skip=0", info->hnd->name);
1011 				skip = 0;
1012 			}
1013 		} else {
1014 			php_error_docref(NULL, E_NOTICE, "Handler %s does not support optional skip parameter, the value will be ignored", info->hnd->name);
1015 			skip = 0;
1016 		}
1017 	}
1018 
1019 	zend_string *val;
1020 	if ((val = info->hnd->fetch(info, key_str, skip)) == NULL) {
1021 		DBA_RELEASE_HT_KEY_CREATION();
1022 		RETURN_FALSE;
1023 	}
1024 	DBA_RELEASE_HT_KEY_CREATION();
1025 	RETURN_STR(val);
1026 }
1027 /* }}} */
1028 
1029 /* {{{ 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)1030 PHP_FUNCTION(dba_key_split)
1031 {
1032 	zval *zkey;
1033 	char *key, *name;
1034 	size_t key_len;
1035 
1036 	if (ZEND_NUM_ARGS() != 1) {
1037 		WRONG_PARAM_COUNT;
1038 	}
1039 	if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "z", &zkey) == SUCCESS) {
1040 		if (Z_TYPE_P(zkey) == IS_NULL || (Z_TYPE_P(zkey) == IS_FALSE)) {
1041 			RETURN_FALSE;
1042 		}
1043 	}
1044 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &key, &key_len) == FAILURE) {
1045 		RETURN_THROWS();
1046 	}
1047 	array_init(return_value);
1048 	if (key[0] == '[' && (name = strchr(key, ']')) != NULL) {
1049 		add_next_index_stringl(return_value, key+1, name - (key + 1));
1050 		add_next_index_stringl(return_value, name+1, key_len - (name - key + 1));
1051 	} else {
1052 		add_next_index_stringl(return_value, "", 0);
1053 		add_next_index_stringl(return_value, key, key_len);
1054 	}
1055 }
1056 /* }}} */
1057 
1058 /* {{{ Resets the internal key pointer and returns the first key */
PHP_FUNCTION(dba_firstkey)1059 PHP_FUNCTION(dba_firstkey)
1060 {
1061 	zval *id;
1062 	dba_info *info = NULL;
1063 
1064 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &id) == FAILURE) {
1065 		RETURN_THROWS();
1066 	}
1067 
1068 	DBA_FETCH_RESOURCE(info, id);
1069 
1070 	zend_string *fkey = info->hnd->firstkey(info);
1071 
1072 	if (fkey) {
1073 		RETURN_STR(fkey);
1074 	}
1075 
1076 	RETURN_FALSE;
1077 }
1078 /* }}} */
1079 
1080 /* {{{ Returns the next key */
PHP_FUNCTION(dba_nextkey)1081 PHP_FUNCTION(dba_nextkey)
1082 {
1083 	zval *id;
1084 	dba_info *info = NULL;
1085 
1086 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &id) == FAILURE) {
1087 		RETURN_THROWS();
1088 	}
1089 
1090 	DBA_FETCH_RESOURCE(info, id);
1091 
1092 	zend_string *nkey = info->hnd->nextkey(info);
1093 
1094 	if (nkey) {
1095 		RETURN_STR(nkey);
1096 	}
1097 
1098 	RETURN_FALSE;
1099 }
1100 /* }}} */
1101 
1102 /* {{{ Deletes the entry associated with key
1103    If inifile: remove all other key lines */
PHP_FUNCTION(dba_delete)1104 PHP_FUNCTION(dba_delete)
1105 {
1106 	zval *id;
1107 	dba_info *info = NULL;
1108 	HashTable *key_ht = NULL;
1109 	zend_string *key_str = NULL;
1110 
1111 	ZEND_PARSE_PARAMETERS_START(2, 2)
1112 		Z_PARAM_ARRAY_HT_OR_STR(key_ht, key_str)
1113 		Z_PARAM_RESOURCE(id);
1114 	ZEND_PARSE_PARAMETERS_END();
1115 
1116 	DBA_FETCH_RESOURCE(info, id);
1117 	DBA_WRITE_CHECK(info);
1118 
1119 	if (key_ht) {
1120 		key_str = php_dba_make_key(key_ht);
1121 		if (!key_str) {
1122 			// TODO ValueError?
1123 			RETURN_FALSE;
1124 		}
1125 	}
1126 
1127 	RETVAL_BOOL(info->hnd->delete(info, key_str) == SUCCESS);
1128 	DBA_RELEASE_HT_KEY_CREATION();
1129 }
1130 /* }}} */
1131 
1132 /* {{{ If not inifile: Insert value as key, return false, if key exists already
1133    If inifile: Add vakue as key (next instance of key) */
PHP_FUNCTION(dba_insert)1134 PHP_FUNCTION(dba_insert)
1135 {
1136 	php_dba_update(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
1137 }
1138 /* }}} */
1139 
1140 /* {{{ Inserts value as key, replaces key, if key exists already
1141    If inifile: remove all other key lines */
PHP_FUNCTION(dba_replace)1142 PHP_FUNCTION(dba_replace)
1143 {
1144 	php_dba_update(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
1145 }
1146 /* }}} */
1147 
1148 /* {{{ Optimizes (e.g. clean up, vacuum) database */
PHP_FUNCTION(dba_optimize)1149 PHP_FUNCTION(dba_optimize)
1150 {
1151 	zval *id;
1152 	dba_info *info = NULL;
1153 
1154 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &id) == FAILURE) {
1155 		RETURN_THROWS();
1156 	}
1157 
1158 	DBA_FETCH_RESOURCE(info, id);
1159 	DBA_WRITE_CHECK(info);
1160 
1161 	if (info->hnd->optimize(info) == SUCCESS) {
1162 		RETURN_TRUE;
1163 	}
1164 
1165 	RETURN_FALSE;
1166 }
1167 /* }}} */
1168 
1169 /* {{{ Synchronizes database */
PHP_FUNCTION(dba_sync)1170 PHP_FUNCTION(dba_sync)
1171 {
1172 	zval *id;
1173 	dba_info *info = NULL;
1174 
1175 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &id) == FAILURE) {
1176 		RETURN_THROWS();
1177 	}
1178 
1179 	DBA_FETCH_RESOURCE(info, id);
1180 
1181 	if (info->hnd->sync(info) == SUCCESS) {
1182 		RETURN_TRUE;
1183 	}
1184 
1185 	RETURN_FALSE;
1186 }
1187 /* }}} */
1188 
1189 /* {{{ List configured database handlers */
PHP_FUNCTION(dba_handlers)1190 PHP_FUNCTION(dba_handlers)
1191 {
1192 	const dba_handler *hptr;
1193 	bool full_info = 0;
1194 
1195 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &full_info) == FAILURE) {
1196 		RETURN_THROWS();
1197 	}
1198 
1199 	array_init(return_value);
1200 
1201 	for(hptr = handler; hptr->name; hptr++) {
1202 		if (full_info) {
1203 			// TODO: avoid reallocation ???
1204 			char *str = hptr->info(hptr, NULL);
1205 			add_assoc_string(return_value, hptr->name, str);
1206 			efree(str);
1207 		} else {
1208 			add_next_index_string(return_value, hptr->name);
1209 		}
1210 	}
1211 }
1212 /* }}} */
1213 
1214 /* {{{ List opened databases */
PHP_FUNCTION(dba_list)1215 PHP_FUNCTION(dba_list)
1216 {
1217 	zend_ulong numitems, i;
1218 	zend_resource *le;
1219 	dba_info *info;
1220 
1221 	if (zend_parse_parameters_none() == FAILURE) {
1222 		RETURN_THROWS();
1223 	}
1224 
1225 	array_init(return_value);
1226 
1227 	numitems = zend_hash_next_free_element(&EG(regular_list));
1228 	for (i=1; i<numitems; i++) {
1229 		if ((le = zend_hash_index_find_ptr(&EG(regular_list), i)) == NULL) {
1230 			continue;
1231 		}
1232 		if (le->type == le_db || le->type == le_pdb) {
1233 			info = (dba_info *)(le->ptr);
1234 			add_index_str(return_value, i, zend_string_copy(info->path));
1235 		}
1236 	}
1237 }
1238 /* }}} */
1239 
1240 #endif /* HAVE_DBA */
1241