xref: /PHP-8.2/ext/dba/dba.c (revision 89216b23)
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 	char *default_handler;
59 	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 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 	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 	if (info->path) {
266 		pefree(info->path, info->flags&DBA_PERSISTENT);
267 	}
268 	if (info->fp && info->fp != info->lock.fp) {
269 		if (info->flags & DBA_PERSISTENT) {
270 			php_stream_pclose(info->fp);
271 		} else {
272 			php_stream_close(info->fp);
273 		}
274 	}
275 	if (info->lock.fp) {
276 		if (info->flags & DBA_PERSISTENT) {
277 			php_stream_pclose(info->lock.fp);
278 		} else {
279 			php_stream_close(info->lock.fp);
280 		}
281 	}
282 	pefree(info, info->flags&DBA_PERSISTENT);
283 }
284 /* }}} */
285 
286 /* {{{ dba_close_rsrc */
dba_close_rsrc(zend_resource * rsrc)287 static void dba_close_rsrc(zend_resource *rsrc)
288 {
289 	dba_info *info = (dba_info *)rsrc->ptr;
290 
291 	dba_close(info);
292 }
293 /* }}} */
294 
295 /* {{{ dba_close_pe_rsrc_deleter */
dba_close_pe_rsrc_deleter(zval * el,void * pDba)296 int dba_close_pe_rsrc_deleter(zval *el, void *pDba)
297 {
298 	if (Z_RES_P(el)->ptr == pDba) {
299 		if (Z_DELREF_P(el) == 0) {
300 			return ZEND_HASH_APPLY_REMOVE;
301 		} else {
302 			return ZEND_HASH_APPLY_KEEP | ZEND_HASH_APPLY_STOP;
303 		}
304 	} else {
305 		return ZEND_HASH_APPLY_KEEP;
306 	}
307 }
308 /* }}} */
309 
310 /* {{{ dba_close_pe_rsrc */
dba_close_pe_rsrc(zend_resource * rsrc)311 static void dba_close_pe_rsrc(zend_resource *rsrc)
312 {
313 	dba_info *info = (dba_info *)rsrc->ptr;
314 
315 	/* closes the resource by calling dba_close_rsrc() */
316 	zend_hash_apply_with_argument(&EG(persistent_list), dba_close_pe_rsrc_deleter, info);
317 }
318 /* }}} */
319 
320 /* {{{ PHP_INI */
ZEND_INI_MH(OnUpdateDefaultHandler)321 ZEND_INI_MH(OnUpdateDefaultHandler)
322 {
323 	dba_handler *hptr;
324 
325 	if (!ZSTR_LEN(new_value)) {
326 		DBA_G(default_hptr) = NULL;
327 		return OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage);
328 	}
329 
330 	for (hptr = handler; hptr->name && strcasecmp(hptr->name, ZSTR_VAL(new_value)); hptr++);
331 
332 	if (!hptr->name) {
333 		php_error_docref(NULL, E_WARNING, "No such handler: %s", ZSTR_VAL(new_value));
334 		return FAILURE;
335 	}
336 	DBA_G(default_hptr) = hptr;
337 	return OnUpdateString(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage);
338 }
339 
340 PHP_INI_BEGIN()
341 	STD_PHP_INI_ENTRY("dba.default_handler", DBA_DEFAULT, PHP_INI_ALL, OnUpdateDefaultHandler, default_handler,    zend_dba_globals, dba_globals)
PHP_INI_END()342 PHP_INI_END()
343 /* }}} */
344 
345 /* {{{ PHP_GINIT_FUNCTION */
346 static PHP_GINIT_FUNCTION(dba)
347 {
348 #if defined(COMPILE_DL_DBA) && defined(ZTS)
349 	ZEND_TSRMLS_CACHE_UPDATE();
350 #endif
351 	dba_globals->default_handler = "";
352 	dba_globals->default_hptr    = NULL;
353 }
354 /* }}} */
355 
356 /* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(dba)357 PHP_MINIT_FUNCTION(dba)
358 {
359 	REGISTER_INI_ENTRIES();
360 	le_db = zend_register_list_destructors_ex(dba_close_rsrc, NULL, "dba", module_number);
361 	le_pdb = zend_register_list_destructors_ex(dba_close_pe_rsrc, dba_close_rsrc, "dba persistent", module_number);
362 	register_dba_symbols(module_number);
363 	return SUCCESS;
364 }
365 /* }}} */
366 
367 /* {{{ PHP_MSHUTDOWN_FUNCTION */
PHP_MSHUTDOWN_FUNCTION(dba)368 PHP_MSHUTDOWN_FUNCTION(dba)
369 {
370 	UNREGISTER_INI_ENTRIES();
371 	return SUCCESS;
372 }
373 /* }}} */
374 
375 #include "zend_smart_str.h"
376 
377 /* {{{ PHP_MINFO_FUNCTION */
PHP_MINFO_FUNCTION(dba)378 PHP_MINFO_FUNCTION(dba)
379 {
380 	dba_handler *hptr;
381 	smart_str handlers = {0};
382 
383 	for(hptr = handler; hptr->name; hptr++) {
384 		smart_str_appends(&handlers, hptr->name);
385 		smart_str_appendc(&handlers, ' ');
386 	}
387 
388 	php_info_print_table_start();
389 	php_info_print_table_row(2, "DBA support", "enabled");
390 	if (handlers.s) {
391 		smart_str_0(&handlers);
392 		php_info_print_table_row(2, "Supported handlers", ZSTR_VAL(handlers.s));
393 		smart_str_free(&handlers);
394 	} else {
395 		php_info_print_table_row(2, "Supported handlers", "none");
396 	}
397 	php_info_print_table_end();
398 	DISPLAY_INI_ENTRIES();
399 }
400 /* }}} */
401 
402 /* {{{ php_dba_update */
php_dba_update(INTERNAL_FUNCTION_PARAMETERS,int mode)403 static void php_dba_update(INTERNAL_FUNCTION_PARAMETERS, int mode)
404 {
405 	zval *id;
406 	dba_info *info = NULL;
407 	HashTable *key_ht = NULL;
408 	zend_string *key_str = NULL;
409 	zend_string *value;
410 
411 	ZEND_PARSE_PARAMETERS_START(3, 3)
412 		Z_PARAM_ARRAY_HT_OR_STR(key_ht, key_str)
413 		Z_PARAM_STR(value)
414 		Z_PARAM_RESOURCE(id);
415 	ZEND_PARSE_PARAMETERS_END();
416 
417 	DBA_FETCH_RESOURCE(info, id);
418 	DBA_WRITE_CHECK(info);
419 
420 	if (key_ht) {
421 		key_str = php_dba_make_key(key_ht);
422 		if (!key_str) {
423 			// TODO ValueError?
424 			RETURN_FALSE;
425 		}
426 	}
427 
428 	RETVAL_BOOL(info->hnd->update(info, key_str, value, mode) == SUCCESS);
429 	DBA_RELEASE_HT_KEY_CREATION();
430 }
431 /* }}} */
432 
433 /* {{{ php_find_dbm */
php_dba_find(const char * path)434 dba_info *php_dba_find(const char* path)
435 {
436 	zend_resource *le;
437 	dba_info *info;
438 	zend_long numitems, i;
439 
440 	numitems = zend_hash_next_free_element(&EG(regular_list));
441 	for (i=1; i<numitems; i++) {
442 		if ((le = zend_hash_index_find_ptr(&EG(regular_list), i)) == NULL) {
443 			continue;
444 		}
445 		if (le->type == le_db || le->type == le_pdb) {
446 			info = (dba_info *)(le->ptr);
447 			if (!strcmp(info->path, path)) {
448 				return (dba_info *)(le->ptr);
449 			}
450 		}
451 	}
452 
453 	return NULL;
454 }
455 /* }}} */
456 
457 #define FREE_PERSISTENT_RESOURCE_KEY() if (persistent_resource_key) {zend_string_release_ex(persistent_resource_key, false);}
458 
459 /* {{{ php_dba_open */
php_dba_open(INTERNAL_FUNCTION_PARAMETERS,bool persistent)460 static void php_dba_open(INTERNAL_FUNCTION_PARAMETERS, bool persistent)
461 {
462 	dba_mode_t modenr;
463 	dba_info *info, *other;
464 	dba_handler *hptr;
465 	char *error = NULL;
466 	int lock_mode, lock_flag = 0;
467 	char *file_mode;
468 	char *lock_file_mode = NULL;
469 	int persistent_flag = persistent ? STREAM_OPEN_PERSISTENT : 0;
470 	zend_string *opened_path = NULL;
471 	char *lock_name;
472 #ifdef PHP_WIN32
473 	bool restarted = 0;
474 	bool need_creation = 0;
475 #endif
476 
477 	zend_string *path;
478 	zend_string *mode;
479 	zend_string *handler_str = NULL;
480 	zend_long permission = 0644;
481 	zend_long map_size = 0;
482 	zend_long driver_flags = DBA_DEFAULT_DRIVER_FLAGS;
483 	bool is_flags_null = true;
484 	zend_string *persistent_resource_key = NULL;
485 
486 	if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "PS|S!lll!", &path, &mode, &handler_str,
487 			&permission, &map_size, &driver_flags, &is_flags_null)) {
488 		RETURN_THROWS();
489 	}
490 
491 	if (ZSTR_LEN(path) == 0) {
492 		zend_argument_value_error(1, "cannot be empty");
493 		RETURN_THROWS();
494 	}
495 	if (ZSTR_LEN(mode) == 0) {
496 		zend_argument_value_error(2, "cannot be empty");
497 		RETURN_THROWS();
498 	}
499 	if (handler_str && ZSTR_LEN(handler_str) == 0) {
500 		zend_argument_value_error(3, "cannot be empty");
501 		RETURN_THROWS();
502 	}
503 	// TODO Check Value for permission
504 	if (map_size < 0) {
505 		zend_argument_value_error(5, "must be greater than or equal to 0");
506 		RETURN_THROWS();
507 	}
508 
509 	if (!is_flags_null && driver_flags < 0) {
510 		zend_argument_value_error(6, "must be greater than or equal to 0");
511 		RETURN_THROWS();
512 	}
513 
514 	if (persistent) {
515 		zend_resource *le;
516 
517 		if (handler_str) {
518 			persistent_resource_key = zend_string_concat3(
519 				ZSTR_VAL(path), ZSTR_LEN(path),
520 				ZSTR_VAL(mode), ZSTR_LEN(mode),
521 				ZSTR_VAL(handler_str), ZSTR_LEN(handler_str)
522 			);
523 		} else {
524 			persistent_resource_key = zend_string_concat2(
525 				ZSTR_VAL(path), ZSTR_LEN(path),
526 				ZSTR_VAL(mode), ZSTR_LEN(mode)
527 			);
528 		}
529 
530 		/* try to find if we already have this link in our persistent list */
531 		if ((le = zend_hash_find_ptr(&EG(persistent_list), persistent_resource_key)) != NULL) {
532 			FREE_PERSISTENT_RESOURCE_KEY();
533 			if (le->type != le_pdb) {
534 				// TODO This should never happen
535 				RETURN_FALSE;
536 			}
537 
538 			info = (dba_info *)le->ptr;
539 
540 			GC_ADDREF(le);
541 			RETURN_RES(zend_register_resource(info, le_pdb));
542 		}
543 	}
544 
545 	if (!handler_str) {
546 		hptr = DBA_G(default_hptr);
547 		if (!hptr) {
548 			php_error_docref(NULL, E_WARNING, "No default handler selected");
549 			FREE_PERSISTENT_RESOURCE_KEY();
550 			RETURN_FALSE;
551 		}
552 		ZEND_ASSERT(hptr->name);
553 	} else {
554 		/* Loop through global static var handlers to see if such a handler exists */
555 		for (hptr = handler; hptr->name && strcasecmp(hptr->name, ZSTR_VAL(handler_str)); hptr++);
556 
557 		if (!hptr->name) {
558 			php_error_docref(NULL, E_WARNING, "Handler \"%s\" is not available", ZSTR_VAL(handler_str));
559 			FREE_PERSISTENT_RESOURCE_KEY();
560 			RETURN_FALSE;
561 		}
562 	}
563 
564 	/* Check mode: [rwnc][dl-]?t?
565 	 * r: Read
566 	 * w: Write
567 	 * n: Create/Truncate
568 	 * c: Create
569 	 *
570 	 * d: force lock on database file
571 	 * l: force lock on lck file
572 	 * -: ignore locking
573 	 *
574 	 * t: test open database, warning if locked
575 	 */
576 	bool is_test_lock = false;
577 	bool is_db_lock = false;
578 	bool is_lock_ignored = false;
579 	// bool is_file_lock = false;
580 
581 	if (ZSTR_LEN(mode) == 0) {
582 		zend_argument_value_error(2, "cannot be empty");
583 		FREE_PERSISTENT_RESOURCE_KEY();
584 		RETURN_THROWS();
585 	}
586 	if (ZSTR_LEN(mode) > 3) {
587 		zend_argument_value_error(2, "must be at most 3 characters");
588 		FREE_PERSISTENT_RESOURCE_KEY();
589 		RETURN_THROWS();
590 	}
591 	if (ZSTR_LEN(mode) == 3) {
592 		if (ZSTR_VAL(mode)[2] != 't') {
593 			zend_argument_value_error(2, "third character must be \"t\"");
594 			FREE_PERSISTENT_RESOURCE_KEY();
595 			RETURN_THROWS();
596 		}
597 		is_test_lock = true;
598 	}
599 	if (ZSTR_LEN(mode) >= 2) {
600 		switch (ZSTR_VAL(mode)[1]) {
601 			case 't':
602 				is_test_lock = true;
603 				break;
604 			case '-':
605 				if ((hptr->flags & DBA_LOCK_ALL) == 0) {
606 					php_error_docref(NULL, E_WARNING, "Locking cannot be disabled for handler %s", hptr->name);
607 					FREE_PERSISTENT_RESOURCE_KEY();
608 					RETURN_FALSE;
609 				}
610 				is_lock_ignored = true;
611 				lock_flag = 0;
612 			break;
613 			case 'd':
614 				is_db_lock = true;
615 				if ((hptr->flags & DBA_LOCK_ALL) == 0) {
616 					lock_flag = (hptr->flags & DBA_LOCK_ALL);
617 					break;
618 				}
619 				ZEND_FALLTHROUGH;
620 			case 'l':
621 				// is_file_lock = true;
622 				lock_flag = DBA_LOCK_ALL;
623 				if ((hptr->flags & DBA_LOCK_ALL) == 0) {
624 					php_error_docref(NULL, E_NOTICE, "Handler %s does locking internally", hptr->name);
625 				}
626 				break;
627 			default:
628 				zend_argument_value_error(2, "second character must be one of \"d\", \"l\", \"-\", or \"t\"");
629 				FREE_PERSISTENT_RESOURCE_KEY();
630 				RETURN_THROWS();
631 		}
632 	} else {
633 		lock_flag = (hptr->flags&DBA_LOCK_ALL);
634 		is_db_lock = true;
635 	}
636 
637 	switch (ZSTR_VAL(mode)[0]) {
638 		case 'r':
639 			modenr = DBA_READER;
640 			lock_mode = (lock_flag & DBA_LOCK_READER) ? LOCK_SH : 0;
641 			file_mode = "r";
642 			break;
643 		case 'w':
644 			modenr = DBA_WRITER;
645 			lock_mode = (lock_flag & DBA_LOCK_WRITER) ? LOCK_EX : 0;
646 			file_mode = "r+b";
647 			break;
648 		case 'c': {
649 #ifdef PHP_WIN32
650 			if (hptr->flags & (DBA_NO_APPEND|DBA_CAST_AS_FD)) {
651 				php_stream_statbuf ssb;
652 				need_creation = (SUCCESS != php_stream_stat_path(ZSTR_VAL(path), &ssb));
653 			}
654 #endif
655 			modenr = DBA_CREAT;
656 			lock_mode = (lock_flag & DBA_LOCK_CREAT) ? LOCK_EX : 0;
657 			if (lock_mode) {
658 				if (is_db_lock) {
659 					/* the create/append check will be done on the lock
660 					 * when the lib opens the file it is already created
661 					 */
662 					file_mode = "r+b";       /* read & write, seek 0 */
663 #ifdef PHP_WIN32
664 					if (!need_creation) {
665 						lock_file_mode = "r+b";
666 					} else
667 #endif
668 					lock_file_mode = "a+b";  /* append */
669 				} else {
670 #ifdef PHP_WIN32
671 					if (!need_creation) {
672 						file_mode = "r+b";
673 					} else
674 #endif
675 					file_mode = "a+b";       /* append */
676 					lock_file_mode = "w+b";  /* create/truncate */
677 				}
678 			} else {
679 #ifdef PHP_WIN32
680 				if (!need_creation) {
681 					file_mode = "r+b";
682 				} else
683 #endif
684 				file_mode = "a+b";
685 			}
686 			/* In case of the 'a+b' append mode, the handler is responsible
687 			 * to handle any rewind problems (see flatfile handler).
688 			 */
689 			break;
690 		}
691 		case 'n':
692 			modenr = DBA_TRUNC;
693 			lock_mode = (lock_flag & DBA_LOCK_TRUNC) ? LOCK_EX : 0;
694 			file_mode = "w+b";
695 			break;
696 		default:
697 			zend_argument_value_error(2, "first character must be one of \"r\", \"w\", \"c\", or \"n\"");
698 			FREE_PERSISTENT_RESOURCE_KEY();
699 			RETURN_THROWS();
700 	}
701 	if (!lock_file_mode) {
702 		lock_file_mode = file_mode;
703 	}
704 	if (is_test_lock) {
705 		if (is_lock_ignored) {
706 			zend_argument_value_error(2, "cannot combine mode \"-\" (no lock) and \"t\" (test lock)");
707 			FREE_PERSISTENT_RESOURCE_KEY();
708 			RETURN_THROWS();
709 		}
710 		if (!lock_mode) {
711 			if ((hptr->flags & DBA_LOCK_ALL) == 0) {
712 				php_error_docref(NULL, E_WARNING, "Handler %s uses its own locking which doesn't support mode modifier t (test lock)", hptr->name);
713 				FREE_PERSISTENT_RESOURCE_KEY();
714 				RETURN_FALSE;
715 			} else {
716 				php_error_docref(NULL, E_WARNING, "Handler %s doesn't uses locking for this mode which makes modifier t (test lock) obsolete", hptr->name);
717 				FREE_PERSISTENT_RESOURCE_KEY();
718 				RETURN_FALSE;
719 			}
720 		} else {
721 			lock_mode |= LOCK_NB; /* test =: non blocking */
722 		}
723 	}
724 
725 	info = pemalloc(sizeof(dba_info), persistent);
726 	memset(info, 0, sizeof(dba_info));
727 	info->path = pestrdup(ZSTR_VAL(path), persistent);
728 	info->mode = modenr;
729 	info->file_permission = permission;
730 	info->map_size = map_size;
731 	info->driver_flags = driver_flags;
732 	info->flags = (hptr->flags & ~DBA_LOCK_ALL) | (lock_flag & DBA_LOCK_ALL) | (persistent ? DBA_PERSISTENT : 0);
733 	info->lock.mode = lock_mode;
734 
735 	/* if any open call is a locking call:
736 	 * check if we already have a locking call open that should block this call
737 	 * the problem is some systems would allow read during write
738 	 */
739 	if (hptr->flags & DBA_LOCK_ALL) {
740 		if ((other = php_dba_find(info->path)) != NULL) {
741 			if (   ( (lock_mode&LOCK_EX)        && (other->lock.mode&(LOCK_EX|LOCK_SH)) )
742 			    || ( (other->lock.mode&LOCK_EX) && (lock_mode&(LOCK_EX|LOCK_SH))        )
743 			   ) {
744 				error = "Unable to establish lock (database file already open)"; /* force failure exit */
745 			}
746 		}
747 	}
748 
749 #ifdef PHP_WIN32
750 restart:
751 #endif
752 	if (!error && lock_mode) {
753 		if (is_db_lock) {
754 			lock_name = ZSTR_VAL(path);
755 		} else {
756 			spprintf(&lock_name, 0, "%s.lck", info->path);
757 			if (!strcmp(file_mode, "r")) {
758 				/* when in read only mode try to use existing .lck file first */
759 				/* do not log errors for .lck file while in read only mode on .lck file */
760 				lock_file_mode = "rb";
761 				info->lock.fp = php_stream_open_wrapper(lock_name, lock_file_mode, STREAM_MUST_SEEK|IGNORE_PATH|persistent_flag, &opened_path);
762 				if (opened_path) {
763 					zend_string_release_ex(opened_path, 0);
764 				}
765 			}
766 			if (!info->lock.fp) {
767 				/* when not in read mode or failed to open .lck file read only. now try again in create(write) mode and log errors */
768 				lock_file_mode = "a+b";
769 			}
770 		}
771 		if (!info->lock.fp) {
772 			info->lock.fp = php_stream_open_wrapper(lock_name, lock_file_mode, STREAM_MUST_SEEK|REPORT_ERRORS|IGNORE_PATH|persistent_flag, &opened_path);
773 			if (info->lock.fp) {
774 				if (is_db_lock) {
775 					/* replace the path info with the real path of the opened file */
776 					pefree(info->path, persistent);
777 					info->path = pestrndup(ZSTR_VAL(opened_path), ZSTR_LEN(opened_path), persistent);
778 				}
779 				zend_string_release_ex(opened_path, 0);
780 			}
781 		}
782 		if (!is_db_lock) {
783 			efree(lock_name);
784 		}
785 		if (!info->lock.fp) {
786 			dba_close(info);
787 			/* stream operation already wrote an error message */
788 			FREE_PERSISTENT_RESOURCE_KEY();
789 			RETURN_FALSE;
790 		}
791 		if (!php_stream_supports_lock(info->lock.fp)) {
792 			error = "Stream does not support locking";
793 		}
794 		if (php_stream_lock(info->lock.fp, lock_mode)) {
795 			error = "Unable to establish lock"; /* force failure exit */
796 		}
797 	}
798 
799 	/* centralised open stream for builtin */
800 	if (!error && (hptr->flags&DBA_STREAM_OPEN)==DBA_STREAM_OPEN) {
801 		if (info->lock.fp && is_db_lock) {
802 			info->fp = info->lock.fp; /* use the same stream for locking and database access */
803 		} else {
804 			info->fp = php_stream_open_wrapper(info->path, file_mode, STREAM_MUST_SEEK|REPORT_ERRORS|IGNORE_PATH|persistent_flag, NULL);
805 		}
806 		if (!info->fp) {
807 			dba_close(info);
808 			/* stream operation already wrote an error message */
809 			FREE_PERSISTENT_RESOURCE_KEY();
810 			RETURN_FALSE;
811 		}
812 		if (hptr->flags & (DBA_NO_APPEND|DBA_CAST_AS_FD)) {
813 			/* Needed because some systems do not allow to write to the original
814 			 * file contents with O_APPEND being set.
815 			 */
816 			if (SUCCESS != php_stream_cast(info->fp, PHP_STREAM_AS_FD, (void*)&info->fd, 1)) {
817 				php_error_docref(NULL, E_WARNING, "Could not cast stream");
818 				dba_close(info);
819 				FREE_PERSISTENT_RESOURCE_KEY();
820 				RETURN_FALSE;
821 #ifdef F_SETFL
822 			} else if (modenr == DBA_CREAT) {
823 				int flags = fcntl(info->fd, F_GETFL);
824 				fcntl(info->fd, F_SETFL, flags & ~O_APPEND);
825 #elif defined(PHP_WIN32)
826 			} else if (modenr == DBA_CREAT && need_creation && !restarted) {
827 				if (info->lock.fp != NULL) {
828 					php_stream_free(info->lock.fp, persistent ? PHP_STREAM_FREE_CLOSE_PERSISTENT : PHP_STREAM_FREE_CLOSE);
829 				}
830 				if (info->fp != info->lock.fp) {
831 					php_stream_free(info->fp, persistent ? PHP_STREAM_FREE_CLOSE_PERSISTENT : PHP_STREAM_FREE_CLOSE);
832 				}
833 				info->fp = NULL;
834 				info->lock.fp = NULL;
835 				info->fd = -1;
836 
837 				lock_file_mode = "r+b";
838 
839 				restarted = 1;
840 				goto restart;
841 #endif
842 			}
843 		}
844 	}
845 
846 	if (error || hptr->open(info, &error) == FAILURE) {
847 		dba_close(info);
848 		if (EXPECTED(!EG(exception))) {
849 			if (error) {
850 				php_error_docref(NULL, E_WARNING, "Driver initialization failed for handler: %s: %s", hptr->name, error);
851 			} else {
852 				php_error_docref(NULL, E_WARNING, "Driver initialization failed for handler: %s", hptr->name);
853 			}
854 		}
855 		FREE_PERSISTENT_RESOURCE_KEY();
856 		RETURN_FALSE;
857 	}
858 
859 	info->hnd = hptr;
860 
861 	if (persistent) {
862 		ZEND_ASSERT(persistent_resource_key);
863 		if (zend_register_persistent_resource_ex(persistent_resource_key, info, le_pdb) == NULL) {
864 			dba_close(info);
865 			php_error_docref(NULL, E_WARNING, "Could not register persistent resource");
866 			FREE_PERSISTENT_RESOURCE_KEY();
867 			RETURN_FALSE;
868 		}
869 		FREE_PERSISTENT_RESOURCE_KEY();
870 	}
871 
872 	RETURN_RES(zend_register_resource(info, (persistent ? le_pdb : le_db)));
873 }
874 /* }}} */
875 
876 /* {{{ Opens path using the specified handler in mode persistently */
PHP_FUNCTION(dba_popen)877 PHP_FUNCTION(dba_popen)
878 {
879 	php_dba_open(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
880 }
881 /* }}} */
882 
883 /* {{{ Opens path using the specified handler in mode*/
PHP_FUNCTION(dba_open)884 PHP_FUNCTION(dba_open)
885 {
886 	php_dba_open(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
887 }
888 /* }}} */
889 
890 /* {{{ Closes database */
PHP_FUNCTION(dba_close)891 PHP_FUNCTION(dba_close)
892 {
893 	zval *id;
894 	dba_info *info = NULL;
895 
896 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &id) == FAILURE) {
897 		RETURN_THROWS();
898 	}
899 
900 	DBA_FETCH_RESOURCE(info, id);
901 
902 	zend_list_close(Z_RES_P(id));
903 }
904 /* }}} */
905 
906 /* {{{ Checks, if the specified key exists */
PHP_FUNCTION(dba_exists)907 PHP_FUNCTION(dba_exists)
908 {
909 	zval *id;
910 	dba_info *info = NULL;
911 	HashTable *key_ht = NULL;
912 	zend_string *key_str = NULL;
913 
914 	ZEND_PARSE_PARAMETERS_START(2, 2)
915 		Z_PARAM_ARRAY_HT_OR_STR(key_ht, key_str)
916 		Z_PARAM_RESOURCE(id);
917 	ZEND_PARSE_PARAMETERS_END();
918 
919 	DBA_FETCH_RESOURCE(info, id);
920 
921 	if (key_ht) {
922 		key_str = php_dba_make_key(key_ht);
923 		if (!key_str) {
924 			// TODO ValueError?
925 			RETURN_FALSE;
926 		}
927 	}
928 
929 	RETVAL_BOOL(info->hnd->exists(info, key_str) == SUCCESS);
930 	DBA_RELEASE_HT_KEY_CREATION();
931 }
932 /* }}} */
933 
934 /* {{{ Fetches the data associated with key */
PHP_FUNCTION(dba_fetch)935 PHP_FUNCTION(dba_fetch)
936 {
937 	zval *id;
938 	dba_info *info = NULL;
939 	HashTable *key_ht = NULL;
940 	zend_string *key_str = NULL;
941 	zend_long skip = 0;
942 
943 	/* Check for legacy signature */
944 	if (ZEND_NUM_ARGS() == 3) {
945 		ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_QUIET, 3, 3)
946 			Z_PARAM_ARRAY_HT_OR_STR(key_ht, key_str)
947 			Z_PARAM_LONG(skip)
948 			Z_PARAM_RESOURCE(id);
949 		ZEND_PARSE_PARAMETERS_END_EX(goto standard;);
950 	} else {
951 		standard:
952 		ZEND_PARSE_PARAMETERS_START(2, 3)
953 			Z_PARAM_ARRAY_HT_OR_STR(key_ht, key_str)
954 			Z_PARAM_RESOURCE(id);
955 			Z_PARAM_OPTIONAL
956 			Z_PARAM_LONG(skip)
957 		ZEND_PARSE_PARAMETERS_END();
958 	}
959 
960 	DBA_FETCH_RESOURCE(info, id);
961 
962 	if (key_ht) {
963 		key_str = php_dba_make_key(key_ht);
964 		if (!key_str) {
965 			// TODO ValueError?
966 			RETURN_FALSE;
967 		}
968 	}
969 
970 	if (skip != 0) {
971 		if (!strcmp(info->hnd->name, "cdb")) {
972 			// TODO ValueError?
973 			if (skip < 0) {
974 				php_error_docref(NULL, E_NOTICE, "Handler %s accepts only skip values greater than or equal to zero, using skip=0", info->hnd->name);
975 				skip = 0;
976 			}
977 		} else if (!strcmp(info->hnd->name, "inifile")) {
978 			/* "-1" is comparable to 0 but allows a non restrictive
979 			 * access which is faster. For example 'inifile' uses this
980 			 * to allow faster access when the key was already found
981 			 * using firstkey/nextkey. However explicitly setting the
982 			 * value to 0 ensures the first value.
983 			 */
984 			if (skip < -1) {
985 				// TODO ValueError?
986 				php_error_docref(NULL, E_NOTICE, "Handler %s accepts only skip value -1 and greater, using skip=0", info->hnd->name);
987 				skip = 0;
988 			}
989 		} else {
990 			php_error_docref(NULL, E_NOTICE, "Handler %s does not support optional skip parameter, the value will be ignored", info->hnd->name);
991 			skip = 0;
992 		}
993 	}
994 
995 	zend_string *val;
996 	if ((val = info->hnd->fetch(info, key_str, skip)) == NULL) {
997 		DBA_RELEASE_HT_KEY_CREATION();
998 		RETURN_FALSE;
999 	}
1000 	DBA_RELEASE_HT_KEY_CREATION();
1001 	RETURN_STR(val);
1002 }
1003 /* }}} */
1004 
1005 /* {{{ 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)1006 PHP_FUNCTION(dba_key_split)
1007 {
1008 	zval *zkey;
1009 	char *key, *name;
1010 	size_t key_len;
1011 
1012 	if (ZEND_NUM_ARGS() != 1) {
1013 		WRONG_PARAM_COUNT;
1014 	}
1015 	if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "z", &zkey) == SUCCESS) {
1016 		if (Z_TYPE_P(zkey) == IS_NULL || (Z_TYPE_P(zkey) == IS_FALSE)) {
1017 			RETURN_FALSE;
1018 		}
1019 	}
1020 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &key, &key_len) == FAILURE) {
1021 		RETURN_THROWS();
1022 	}
1023 	array_init(return_value);
1024 	if (key[0] == '[' && (name = strchr(key, ']')) != NULL) {
1025 		add_next_index_stringl(return_value, key+1, name - (key + 1));
1026 		add_next_index_stringl(return_value, name+1, key_len - (name - key + 1));
1027 	} else {
1028 		add_next_index_stringl(return_value, "", 0);
1029 		add_next_index_stringl(return_value, key, key_len);
1030 	}
1031 }
1032 /* }}} */
1033 
1034 /* {{{ Resets the internal key pointer and returns the first key */
PHP_FUNCTION(dba_firstkey)1035 PHP_FUNCTION(dba_firstkey)
1036 {
1037 	zval *id;
1038 	dba_info *info = NULL;
1039 
1040 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &id) == FAILURE) {
1041 		RETURN_THROWS();
1042 	}
1043 
1044 	DBA_FETCH_RESOURCE(info, id);
1045 
1046 	zend_string *fkey = info->hnd->firstkey(info);
1047 
1048 	if (fkey) {
1049 		RETURN_STR(fkey);
1050 	}
1051 
1052 	RETURN_FALSE;
1053 }
1054 /* }}} */
1055 
1056 /* {{{ Returns the next key */
PHP_FUNCTION(dba_nextkey)1057 PHP_FUNCTION(dba_nextkey)
1058 {
1059 	zval *id;
1060 	dba_info *info = NULL;
1061 
1062 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &id) == FAILURE) {
1063 		RETURN_THROWS();
1064 	}
1065 
1066 	DBA_FETCH_RESOURCE(info, id);
1067 
1068 	zend_string *nkey = info->hnd->nextkey(info);
1069 
1070 	if (nkey) {
1071 		RETURN_STR(nkey);
1072 	}
1073 
1074 	RETURN_FALSE;
1075 }
1076 /* }}} */
1077 
1078 /* {{{ Deletes the entry associated with key
1079    If inifile: remove all other key lines */
PHP_FUNCTION(dba_delete)1080 PHP_FUNCTION(dba_delete)
1081 {
1082 	zval *id;
1083 	dba_info *info = NULL;
1084 	HashTable *key_ht = NULL;
1085 	zend_string *key_str = NULL;
1086 
1087 	ZEND_PARSE_PARAMETERS_START(2, 2)
1088 		Z_PARAM_ARRAY_HT_OR_STR(key_ht, key_str)
1089 		Z_PARAM_RESOURCE(id);
1090 	ZEND_PARSE_PARAMETERS_END();
1091 
1092 	DBA_FETCH_RESOURCE(info, id);
1093 	DBA_WRITE_CHECK(info);
1094 
1095 	if (key_ht) {
1096 		key_str = php_dba_make_key(key_ht);
1097 		if (!key_str) {
1098 			// TODO ValueError?
1099 			RETURN_FALSE;
1100 		}
1101 	}
1102 
1103 	RETVAL_BOOL(info->hnd->delete(info, key_str) == SUCCESS);
1104 	DBA_RELEASE_HT_KEY_CREATION();
1105 }
1106 /* }}} */
1107 
1108 /* {{{ If not inifile: Insert value as key, return false, if key exists already
1109    If inifile: Add vakue as key (next instance of key) */
PHP_FUNCTION(dba_insert)1110 PHP_FUNCTION(dba_insert)
1111 {
1112 	php_dba_update(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
1113 }
1114 /* }}} */
1115 
1116 /* {{{ Inserts value as key, replaces key, if key exists already
1117    If inifile: remove all other key lines */
PHP_FUNCTION(dba_replace)1118 PHP_FUNCTION(dba_replace)
1119 {
1120 	php_dba_update(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
1121 }
1122 /* }}} */
1123 
1124 /* {{{ Optimizes (e.g. clean up, vacuum) database */
PHP_FUNCTION(dba_optimize)1125 PHP_FUNCTION(dba_optimize)
1126 {
1127 	zval *id;
1128 	dba_info *info = NULL;
1129 
1130 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &id) == FAILURE) {
1131 		RETURN_THROWS();
1132 	}
1133 
1134 	DBA_FETCH_RESOURCE(info, id);
1135 	DBA_WRITE_CHECK(info);
1136 
1137 	if (info->hnd->optimize(info) == SUCCESS) {
1138 		RETURN_TRUE;
1139 	}
1140 
1141 	RETURN_FALSE;
1142 }
1143 /* }}} */
1144 
1145 /* {{{ Synchronizes database */
PHP_FUNCTION(dba_sync)1146 PHP_FUNCTION(dba_sync)
1147 {
1148 	zval *id;
1149 	dba_info *info = NULL;
1150 
1151 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "r", &id) == FAILURE) {
1152 		RETURN_THROWS();
1153 	}
1154 
1155 	DBA_FETCH_RESOURCE(info, id);
1156 
1157 	if (info->hnd->sync(info) == SUCCESS) {
1158 		RETURN_TRUE;
1159 	}
1160 
1161 	RETURN_FALSE;
1162 }
1163 /* }}} */
1164 
1165 /* {{{ List configured database handlers */
PHP_FUNCTION(dba_handlers)1166 PHP_FUNCTION(dba_handlers)
1167 {
1168 	dba_handler *hptr;
1169 	bool full_info = 0;
1170 
1171 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|b", &full_info) == FAILURE) {
1172 		RETURN_THROWS();
1173 	}
1174 
1175 	array_init(return_value);
1176 
1177 	for(hptr = handler; hptr->name; hptr++) {
1178 		if (full_info) {
1179 			// TODO: avoid reallocation ???
1180 			char *str = hptr->info(hptr, NULL);
1181 			add_assoc_string(return_value, hptr->name, str);
1182 			efree(str);
1183 		} else {
1184 			add_next_index_string(return_value, hptr->name);
1185 		}
1186 	}
1187 }
1188 /* }}} */
1189 
1190 /* {{{ List opened databases */
PHP_FUNCTION(dba_list)1191 PHP_FUNCTION(dba_list)
1192 {
1193 	zend_ulong numitems, i;
1194 	zend_resource *le;
1195 	dba_info *info;
1196 
1197 	if (zend_parse_parameters_none() == FAILURE) {
1198 		RETURN_THROWS();
1199 	}
1200 
1201 	array_init(return_value);
1202 
1203 	numitems = zend_hash_next_free_element(&EG(regular_list));
1204 	for (i=1; i<numitems; i++) {
1205 		if ((le = zend_hash_index_find_ptr(&EG(regular_list), i)) == NULL) {
1206 			continue;
1207 		}
1208 		if (le->type == le_db || le->type == le_pdb) {
1209 			info = (dba_info *)(le->ptr);
1210 			add_index_string(return_value, i, info->path);
1211 		}
1212 	}
1213 }
1214 /* }}} */
1215 
1216 #endif /* HAVE_DBA */
1217