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