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