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