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