fix bad access with interned strings
[m6w6/ext-raphf] / php_raphf.c
1 /*
2 +--------------------------------------------------------------------+
3 | PECL :: raphf |
4 +--------------------------------------------------------------------+
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted provided that the conditions mentioned |
7 | in the accompanying LICENSE file are met. |
8 +--------------------------------------------------------------------+
9 | Copyright (c) 2013, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
11 */
12
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif
16
17 #include "php.h"
18 #include "php_ini.h"
19 #include "ext/standard/info.h"
20 #include "php_raphf.h"
21
22 #ifndef PHP_RAPHF_TEST
23 # define PHP_RAPHF_TEST 0
24 #endif
25
26 struct php_persistent_handle_globals {
27 ulong limit;
28 HashTable hash;
29 };
30
31 ZEND_BEGIN_MODULE_GLOBALS(raphf)
32 struct php_persistent_handle_globals persistent_handle;
33 ZEND_END_MODULE_GLOBALS(raphf)
34
35 #ifdef ZTS
36 # define PHP_RAPHF_G ((zend_raphf_globals *) \
37 (*((void ***) tsrm_get_ls_cache()))[TSRM_UNSHUFFLE_RSRC_ID(raphf_globals_id)])
38 #else
39 # define PHP_RAPHF_G (&raphf_globals)
40 #endif
41
42 ZEND_DECLARE_MODULE_GLOBALS(raphf)
43
44 #ifndef PHP_RAPHF_DEBUG_PHANDLES
45 # define PHP_RAPHF_DEBUG_PHANDLES 0
46 #endif
47 #if PHP_RAPHF_DEBUG_PHANDLES
48 # undef inline
49 # define inline
50 #endif
51
52 php_resource_factory_t *php_resource_factory_init(php_resource_factory_t *f,
53 php_resource_factory_ops_t *fops, void *data, void (*dtor)(void *data))
54 {
55 if (!f) {
56 f = emalloc(sizeof(*f));
57 }
58 memset(f, 0, sizeof(*f));
59
60 memcpy(&f->fops, fops, sizeof(*fops));
61
62 f->data = data;
63 f->dtor = dtor;
64
65 f->refcount = 1;
66
67 return f;
68 }
69
70 unsigned php_resource_factory_addref(php_resource_factory_t *rf)
71 {
72 return ++rf->refcount;
73 }
74
75 void php_resource_factory_dtor(php_resource_factory_t *f)
76 {
77 if (!--f->refcount) {
78 if (f->dtor) {
79 f->dtor(f->data);
80 }
81 }
82 }
83
84 void php_resource_factory_free(php_resource_factory_t **f)
85 {
86 if (*f) {
87 php_resource_factory_dtor(*f);
88 if (!(*f)->refcount) {
89 efree(*f);
90 *f = NULL;
91 }
92 }
93 }
94
95 void *php_resource_factory_handle_ctor(php_resource_factory_t *f, void *init_arg)
96 {
97 if (f->fops.ctor) {
98 return f->fops.ctor(f->data, init_arg);
99 }
100 return NULL;
101 }
102
103 void *php_resource_factory_handle_copy(php_resource_factory_t *f, void *handle)
104 {
105 if (f->fops.copy) {
106 return f->fops.copy(f->data, handle);
107 }
108 return NULL;
109 }
110
111 void php_resource_factory_handle_dtor(php_resource_factory_t *f, void *handle)
112 {
113 if (f->fops.dtor) {
114 f->fops.dtor(f->data, handle);
115 }
116 }
117
118 static inline php_persistent_handle_list_t *php_persistent_handle_list_init(
119 php_persistent_handle_list_t *list)
120 {
121 if (!list) {
122 list = pemalloc(sizeof(*list), 1);
123 }
124 list->used = 0;
125 zend_hash_init(&list->free, 0, NULL, NULL, 1);
126
127 return list;
128 }
129
130 static int php_persistent_handle_apply_stat(zval *p, int argc, va_list argv,
131 zend_hash_key *key)
132 {
133 php_persistent_handle_list_t *list = Z_PTR_P(p);
134 zval zsubentry, *zentry = va_arg(argv, zval *);
135
136 array_init(&zsubentry);
137 add_assoc_long_ex(&zsubentry, ZEND_STRL("used"), list->used);
138 add_assoc_long_ex(&zsubentry, ZEND_STRL("free"),
139 zend_hash_num_elements(&list->free));
140 if (key->key) {
141 add_assoc_zval_ex(zentry, key->key->val, key->key->len, &zsubentry);
142 } else {
143 add_index_zval(zentry, key->h, &zsubentry);
144 }
145 return ZEND_HASH_APPLY_KEEP;
146 }
147
148 static int php_persistent_handle_apply_statall(zval *p, int argc, va_list argv,
149 zend_hash_key *key)
150 {
151 php_persistent_handle_provider_t *provider = Z_PTR_P(p);
152 HashTable *ht = va_arg(argv, HashTable *);
153 zval zentry;
154
155 array_init(&zentry);
156
157 zend_hash_apply_with_arguments(&provider->list.free,
158 php_persistent_handle_apply_stat, 1, &zentry);
159
160 if (key->key) {
161 zend_hash_update(ht, key->key, &zentry);
162 } else {
163 zend_hash_index_update(ht, key->h, &zentry);
164 }
165
166 return ZEND_HASH_APPLY_KEEP;
167 }
168
169 static int php_persistent_handle_apply_cleanup_ex(zval *p, void *arg)
170 {
171 php_resource_factory_t *rf = arg;
172 void *handle = Z_PTR_P(p);
173
174 #if PHP_RAPHF_DEBUG_PHANDLES
175 fprintf(stderr, "DESTROY: %p\n", handle);
176 #endif
177 php_resource_factory_handle_dtor(rf, handle);
178 return ZEND_HASH_APPLY_REMOVE;
179 }
180
181 static int php_persistent_handle_apply_cleanup(zval *p, void *arg)
182 {
183 php_resource_factory_t *rf = arg;
184 php_persistent_handle_list_t *list = Z_PTR_P(p);
185
186 zend_hash_apply_with_argument(&list->free,
187 php_persistent_handle_apply_cleanup_ex, rf);
188 if (list->used) {
189 return ZEND_HASH_APPLY_KEEP;
190 }
191 zend_hash_destroy(&list->free);
192 #if PHP_RAPHF_DEBUG_PHANDLES
193 fprintf(stderr, "LSTFREE: %p\n", list);
194 #endif
195 pefree(list, 1);
196 return ZEND_HASH_APPLY_REMOVE;
197 }
198
199 static inline void php_persistent_handle_list_dtor(
200 php_persistent_handle_list_t *list,
201 php_persistent_handle_provider_t *provider)
202 {
203 #if PHP_RAPHF_DEBUG_PHANDLES
204 fprintf(stderr, "LSTDTOR: %p\n", list);
205 #endif
206 zend_hash_apply_with_argument(&list->free,
207 php_persistent_handle_apply_cleanup_ex, &provider->rf);
208 zend_hash_destroy(&list->free);
209 }
210
211 static inline void php_persistent_handle_list_free(
212 php_persistent_handle_list_t **list,
213 php_persistent_handle_provider_t *provider)
214 {
215 php_persistent_handle_list_dtor(*list, provider);
216 #if PHP_RAPHF_DEBUG_PHANDLES
217 fprintf(stderr, "LSTFREE: %p\n", *list);
218 #endif
219 pefree(*list, 1);
220 *list = NULL;
221 }
222
223 static int php_persistent_handle_list_apply_dtor(zval *p, void *provider)
224 {
225 php_persistent_handle_list_t *list = Z_PTR_P(p);
226
227 php_persistent_handle_list_free(&list, provider );
228 ZVAL_PTR(p, NULL);
229 return ZEND_HASH_APPLY_REMOVE;
230 }
231
232 static inline php_persistent_handle_list_t *php_persistent_handle_list_find(
233 php_persistent_handle_provider_t *provider, zend_string *ident)
234 {
235 php_persistent_handle_list_t *list;
236 zval *zlist = zend_symtable_find(&provider->list.free, ident);
237
238 if (zlist && (list = Z_PTR_P(zlist))) {
239 #if PHP_RAPHF_DEBUG_PHANDLES
240 fprintf(stderr, "LSTFIND: %p\n", list);
241 #endif
242 return list;
243 }
244
245 if ((list = php_persistent_handle_list_init(NULL))) {
246 zval p, *rv;
247 zend_string *id;
248
249 ZVAL_PTR(&p, list);
250 id = zend_string_init(ident->val, ident->len, 1);
251 rv = zend_symtable_update(&provider->list.free, id, &p);
252 zend_string_release(id);
253
254 if (rv) {
255 #if PHP_RAPHF_DEBUG_PHANDLES
256 fprintf(stderr, "LSTFIND: %p (new)\n", list);
257 #endif
258 return list;
259 }
260 php_persistent_handle_list_free(&list, provider);
261 }
262
263 return NULL;
264 }
265
266 static int php_persistent_handle_apply_cleanup_all(zval *p, int argc,
267 va_list argv, zend_hash_key *key)
268 {
269 php_persistent_handle_provider_t *provider = Z_PTR_P(p);
270 zend_string *ident = va_arg(argv, zend_string *);
271 php_persistent_handle_list_t *list;
272
273 if (ident && ident->len) {
274 if ((list = php_persistent_handle_list_find(provider, ident))) {
275 zend_hash_apply_with_argument(&list->free,
276 php_persistent_handle_apply_cleanup_ex,
277 &provider->rf);
278 }
279 } else {
280 zend_hash_apply_with_argument(&provider->list.free,
281 php_persistent_handle_apply_cleanup, &provider->rf);
282 }
283
284 return ZEND_HASH_APPLY_KEEP;
285 }
286
287 static void php_persistent_handle_hash_dtor(zval *p)
288 {
289 php_persistent_handle_provider_t *provider = Z_PTR_P(p);
290
291 zend_hash_apply_with_argument(&provider->list.free,
292 php_persistent_handle_list_apply_dtor, provider);
293 zend_hash_destroy(&provider->list.free);
294 php_resource_factory_dtor(&provider->rf);
295 pefree(provider, 1);
296 }
297
298 ZEND_RESULT_CODE php_persistent_handle_provide(zend_string *name,
299 php_resource_factory_ops_t *fops, void *data, void (*dtor)(void *))
300 {
301 php_persistent_handle_provider_t *provider = pemalloc(sizeof(*provider), 1);
302
303 if (php_persistent_handle_list_init(&provider->list)) {
304 if (php_resource_factory_init(&provider->rf, fops, data, dtor)) {
305 zval p, *rv;
306 zend_string *ns;
307
308 #if PHP_RAPHF_DEBUG_PHANDLES
309 fprintf(stderr, "PROVIDE: %p %s\n", PHP_RAPHF_G, name_str);
310 #endif
311
312 ZVAL_PTR(&p, provider);
313 ns = zend_string_init(name->val, name->len, 1);
314 rv = zend_symtable_update(&PHP_RAPHF_G->persistent_handle.hash, ns, &p);
315 zend_string_release(ns);
316
317 if (rv) {
318 return SUCCESS;
319 }
320 php_resource_factory_dtor(&provider->rf);
321 }
322 }
323
324 return FAILURE;
325 }
326
327
328 php_persistent_handle_factory_t *php_persistent_handle_concede(
329 php_persistent_handle_factory_t *a,
330 zend_string *name, zend_string *ident,
331 php_persistent_handle_wakeup_t wakeup,
332 php_persistent_handle_retire_t retire)
333 {
334 zval *zprovider = zend_symtable_find(&PHP_RAPHF_G->persistent_handle.hash, name);
335
336 if (zprovider) {
337 zend_bool free_a = 0;
338
339 if ((free_a = !a)) {
340 a = emalloc(sizeof(*a));
341 }
342 memset(a, 0, sizeof(*a));
343
344 a->provider = Z_PTR_P(zprovider);
345 a->ident = zend_string_copy(ident);
346 a->wakeup = wakeup;
347 a->retire = retire;
348 a->free_on_abandon = free_a;
349 } else {
350 a = NULL;
351 }
352
353 #if PHP_RAPHF_DEBUG_PHANDLES
354 fprintf(stderr, "CONCEDE: %p %p (%s) (%s)\n", PHP_RAPHF_G,
355 a ? a->provider : NULL, name->val, ident->val);
356 #endif
357
358 return a;
359 }
360
361 void php_persistent_handle_abandon(php_persistent_handle_factory_t *a)
362 {
363 zend_bool f = a->free_on_abandon;
364
365 #if PHP_RAPHF_DEBUG_PHANDLES
366 fprintf(stderr, "ABANDON: %p\n", a->provider);
367 #endif
368
369 zend_string_release(a->ident);
370 memset(a, 0, sizeof(*a));
371 if (f) {
372 efree(a);
373 }
374 }
375
376 void *php_persistent_handle_acquire(php_persistent_handle_factory_t *a, void *init_arg)
377 {
378 int key;
379 zval *p;
380 zend_ulong index;
381 void *handle = NULL;
382 php_persistent_handle_list_t *list;
383
384 list = php_persistent_handle_list_find(a->provider, a->ident);
385 if (list) {
386 zend_hash_internal_pointer_end(&list->free);
387 key = zend_hash_get_current_key(&list->free, NULL, &index);
388 p = zend_hash_get_current_data(&list->free);
389 if (p && HASH_KEY_NON_EXISTENT != key) {
390 handle = Z_PTR_P(p);
391 if (a->wakeup) {
392 a->wakeup(a, &handle);
393 }
394 zend_hash_index_del(&list->free, index);
395 } else {
396 handle = php_resource_factory_handle_ctor(&a->provider->rf, init_arg);
397 }
398 #if PHP_RAPHF_DEBUG_PHANDLES
399 fprintf(stderr, "CREATED: %p\n", handle);
400 #endif
401 if (handle) {
402 ++a->provider->list.used;
403 ++list->used;
404 }
405 }
406
407 return handle;
408 }
409
410 void *php_persistent_handle_accrete(php_persistent_handle_factory_t *a, void *handle)
411 {
412 void *new_handle = NULL;
413 php_persistent_handle_list_t *list;
414
415 new_handle = php_resource_factory_handle_copy(&a->provider->rf, handle);
416 if (handle) {
417 list = php_persistent_handle_list_find(a->provider, a->ident);
418 if (list) {
419 ++list->used;
420 }
421 ++a->provider->list.used;
422 }
423
424 return new_handle;
425 }
426
427 void php_persistent_handle_release(php_persistent_handle_factory_t *a, void *handle)
428 {
429 php_persistent_handle_list_t *list;
430
431 list = php_persistent_handle_list_find(a->provider, a->ident);
432 if (list) {
433 if (a->provider->list.used >= PHP_RAPHF_G->persistent_handle.limit) {
434 #if PHP_RAPHF_DEBUG_PHANDLES
435 fprintf(stderr, "DESTROY: %p\n", handle);
436 #endif
437 php_resource_factory_handle_dtor(&a->provider->rf, handle);
438 } else {
439 if (a->retire) {
440 a->retire(a, &handle);
441 }
442 zend_hash_next_index_insert_ptr(&list->free, handle);
443 }
444
445 --a->provider->list.used;
446 --list->used;
447 }
448 }
449
450 void php_persistent_handle_cleanup(zend_string *name, zend_string *ident)
451 {
452 php_persistent_handle_provider_t *provider;
453 php_persistent_handle_list_t *list;
454
455 if (name) {
456 zval *zprovider = zend_symtable_find(&PHP_RAPHF_G->persistent_handle.hash,
457 name);
458
459 if (zprovider && (provider = Z_PTR_P(zprovider))) {
460 if (ident) {
461 list = php_persistent_handle_list_find(provider, ident);
462 if (list) {
463 zend_hash_apply_with_argument(&list->free,
464 php_persistent_handle_apply_cleanup_ex,
465 &provider->rf);
466 }
467 } else {
468 zend_hash_apply_with_argument(&provider->list.free,
469 php_persistent_handle_apply_cleanup,
470 &provider->rf);
471 }
472 }
473 } else {
474 zend_hash_apply_with_arguments(
475 &PHP_RAPHF_G->persistent_handle.hash,
476 php_persistent_handle_apply_cleanup_all, 1, ident);
477 }
478 }
479
480 HashTable *php_persistent_handle_statall(HashTable *ht)
481 {
482 if (zend_hash_num_elements(&PHP_RAPHF_G->persistent_handle.hash)) {
483 if (!ht) {
484 ALLOC_HASHTABLE(ht);
485 zend_hash_init(ht, 0, NULL, ZVAL_PTR_DTOR, 0);
486 }
487 zend_hash_apply_with_arguments(
488 &PHP_RAPHF_G->persistent_handle.hash,
489 php_persistent_handle_apply_statall, 1, ht);
490 } else if (ht) {
491 ht = NULL;
492 }
493
494 return ht;
495 }
496
497 static php_resource_factory_ops_t php_persistent_handle_resource_factory_ops = {
498 (php_resource_factory_handle_ctor_t) php_persistent_handle_acquire,
499 (php_resource_factory_handle_copy_t) php_persistent_handle_accrete,
500 (php_resource_factory_handle_dtor_t) php_persistent_handle_release
501 };
502
503 php_resource_factory_ops_t *php_persistent_handle_get_resource_factory_ops(void)
504 {
505 return &php_persistent_handle_resource_factory_ops;
506 }
507
508 ZEND_BEGIN_ARG_INFO_EX(ai_raphf_stat_persistent_handles, 0, 0, 0)
509 ZEND_END_ARG_INFO();
510 static PHP_FUNCTION(raphf_stat_persistent_handles)
511 {
512 if (SUCCESS == zend_parse_parameters_none()) {
513 object_init(return_value);
514 if (php_persistent_handle_statall(HASH_OF(return_value))) {
515 return;
516 }
517 zval_dtor(return_value);
518 }
519 RETURN_FALSE;
520 }
521
522 ZEND_BEGIN_ARG_INFO_EX(ai_raphf_clean_persistent_handles, 0, 0, 0)
523 ZEND_ARG_INFO(0, name)
524 ZEND_ARG_INFO(0, ident)
525 ZEND_END_ARG_INFO();
526 static PHP_FUNCTION(raphf_clean_persistent_handles)
527 {
528 zend_string *name = NULL, *ident = NULL;
529
530 if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "|S!S!", &name, &ident)) {
531 php_persistent_handle_cleanup(name, ident);
532 }
533 }
534
535 #if PHP_RAPHF_TEST
536 # include "php_raphf_test.c"
537 #endif
538
539 static const zend_function_entry raphf_functions[] = {
540 ZEND_NS_FENTRY("raphf", stat_persistent_handles,
541 ZEND_FN(raphf_stat_persistent_handles),
542 ai_raphf_stat_persistent_handles, 0)
543 ZEND_NS_FENTRY("raphf", clean_persistent_handles,
544 ZEND_FN(raphf_clean_persistent_handles),
545 ai_raphf_clean_persistent_handles, 0)
546 #if PHP_RAPHF_TEST
547 ZEND_NS_FENTRY("raphf", provide, ZEND_FN(raphf_provide), NULL, 0)
548 ZEND_NS_FENTRY("raphf", conceal, ZEND_FN(raphf_conceal), NULL, 0)
549 ZEND_NS_FENTRY("raphf", concede, ZEND_FN(raphf_concede), NULL, 0)
550 ZEND_NS_FENTRY("raphf", dispute, ZEND_FN(raphf_dispute), NULL, 0)
551 ZEND_NS_FENTRY("raphf", handle_ctor, ZEND_FN(raphf_handle_ctor), NULL, 0)
552 ZEND_NS_FENTRY("raphf", handle_copy, ZEND_FN(raphf_handle_copy), NULL, 0)
553 ZEND_NS_FENTRY("raphf", handle_dtor, ZEND_FN(raphf_handle_dtor), NULL, 0)
554 #endif
555 {0}
556 };
557
558 PHP_INI_BEGIN()
559 STD_PHP_INI_ENTRY("raphf.persistent_handle.limit", "-1", PHP_INI_SYSTEM,
560 OnUpdateLong, persistent_handle.limit, zend_raphf_globals,
561 raphf_globals)
562 PHP_INI_END()
563
564 static HashTable *php_persistent_handles_global_hash;
565
566 static PHP_GINIT_FUNCTION(raphf)
567 {
568 raphf_globals->persistent_handle.limit = -1;
569
570 zend_hash_init(&raphf_globals->persistent_handle.hash, 0, NULL,
571 php_persistent_handle_hash_dtor, 1);
572 if (php_persistent_handles_global_hash) {
573 zend_hash_copy(&raphf_globals->persistent_handle.hash,
574 php_persistent_handles_global_hash, NULL);
575 }
576 }
577
578 static PHP_GSHUTDOWN_FUNCTION(raphf)
579 {
580 zend_hash_destroy(&raphf_globals->persistent_handle.hash);
581 }
582
583 PHP_MINIT_FUNCTION(raphf)
584 {
585 php_persistent_handles_global_hash = &PHP_RAPHF_G->persistent_handle.hash;
586
587 #if PHP_RAPHF_TEST
588 PHP_MINIT(raphf_test)(INIT_FUNC_ARGS_PASSTHRU);
589 #endif
590
591 REGISTER_INI_ENTRIES();
592 return SUCCESS;
593 }
594
595 PHP_MSHUTDOWN_FUNCTION(raphf)
596 {
597 #if PHP_RAPHF_TEST
598 PHP_MSHUTDOWN(raphf_test)(SHUTDOWN_FUNC_ARGS_PASSTHRU);
599 #endif
600
601 UNREGISTER_INI_ENTRIES();
602 php_persistent_handles_global_hash = NULL;
603 return SUCCESS;
604 }
605
606 static int php_persistent_handle_apply_info_ex(zval *p, int argc,
607 va_list argv, zend_hash_key *key)
608 {
609 php_persistent_handle_list_t *list = Z_PTR_P(p);
610 zend_hash_key *super_key = va_arg(argv, zend_hash_key *);
611 char used[21], free[21];
612
613 slprintf(used, sizeof(used), "%u", list->used);
614 slprintf(free, sizeof(free), "%d", zend_hash_num_elements(&list->free));
615
616 php_info_print_table_row(4, super_key->key->val, key->key->val, used, free);
617
618 return ZEND_HASH_APPLY_KEEP;
619 }
620
621 static int php_persistent_handle_apply_info(zval *p, int argc,
622 va_list argv, zend_hash_key *key)
623 {
624 php_persistent_handle_provider_t *provider = Z_PTR_P(p);
625
626 zend_hash_apply_with_arguments(&provider->list.free,
627 php_persistent_handle_apply_info_ex, 1, key);
628
629 return ZEND_HASH_APPLY_KEEP;
630 }
631
632 PHP_MINFO_FUNCTION(raphf)
633 {
634 php_info_print_table_start();
635 php_info_print_table_header(2,
636 "Resource and persistent handle factory support", "enabled");
637 php_info_print_table_row(2, "Extension version", PHP_RAPHF_VERSION);
638 php_info_print_table_end();
639
640 php_info_print_table_start();
641 php_info_print_table_colspan_header(4, "Persistent handles in this "
642 #ifdef ZTS
643 "thread"
644 #else
645 "process"
646 #endif
647 );
648 php_info_print_table_header(4, "Provider", "Ident", "Used", "Free");
649 zend_hash_apply_with_arguments(
650 &PHP_RAPHF_G->persistent_handle.hash,
651 php_persistent_handle_apply_info, 0);
652 php_info_print_table_end();
653
654 DISPLAY_INI_ENTRIES();
655 }
656
657 zend_module_entry raphf_module_entry = {
658 STANDARD_MODULE_HEADER,
659 "raphf",
660 raphf_functions,
661 PHP_MINIT(raphf),
662 PHP_MSHUTDOWN(raphf),
663 NULL,
664 NULL,
665 PHP_MINFO(raphf),
666 PHP_RAPHF_VERSION,
667 ZEND_MODULE_GLOBALS(raphf),
668 PHP_GINIT(raphf),
669 PHP_GSHUTDOWN(raphf),
670 NULL,
671 STANDARD_MODULE_PROPERTIES_EX
672 };
673 /* }}} */
674
675 #ifdef COMPILE_DL_RAPHF
676 ZEND_GET_MODULE(raphf)
677 #endif
678
679 /*
680 * Local variables:
681 * tab-width: 4
682 * c-basic-offset: 4
683 * End:
684 * vim600: noet sw=4 ts=4 fdm=marker
685 * vim<600: noet sw=4 ts=4
686 */