New PHP5 APC - version 3.0.18, using PHP5 5.2.0-8+etch10,
[php5-apc.git] / apc_cache.c
1 /*
2   +----------------------------------------------------------------------+
3   | APC                                                                  |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 2006 The PHP Group                                     |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Authors: Daniel Cowgill <dcowgill@communityconnect.com>              |
16   |          Rasmus Lerdorf <rasmus@php.net>                             |
17   |          Arun C. Murthy <arunc@yahoo-inc.com>                        |
18   |          Gopal Vijayaraghavan <gopalv@yahoo-inc.com>                 |
19   +----------------------------------------------------------------------+
20
21    This software was contributed to PHP by Community Connect Inc. in 2002
22    and revised in 2005 by Yahoo! Inc. to add support for PHP 5.1.
23    Future revisions and derivatives of this source code must acknowledge
24    Community Connect Inc. as the original contributor of this module by
25    leaving this note intact in the source code.
26
27    All other licensing and usage conditions are those of the PHP Group.
28
29  */
30
31 /* $Id: apc_cache.c,v 3.145.2.2 2008/03/25 18:24:57 gopalv Exp $ */
32
33 #include "apc_cache.h"
34 #include "apc_lock.h"
35 #include "apc_sma.h"
36 #include "apc_globals.h"
37 #include "SAPI.h"
38 #include "ext/standard/php_var.h"
39 #include "ext/standard/php_smart_str.h"
40
41 /* TODO: rehash when load factor exceeds threshold */
42
43 #define CHECK(p) { if ((p) == NULL) return NULL; }
44
45 /* {{{ locking macros */
46 #define CREATE_LOCK(lock)     apc_lck_create(NULL, 0, 1, lock)
47 #define DESTROY_LOCK(c) apc_lck_destroy(c->header->lock)
48 #define LOCK(c)         { HANDLE_BLOCK_INTERRUPTIONS(); apc_lck_lock(c->header->lock); }
49 #define RDLOCK(c)       { HANDLE_BLOCK_INTERRUPTIONS(); apc_lck_rdlock(c->header->lock); }
50 #define UNLOCK(c)       { apc_lck_unlock(c->header->lock); HANDLE_UNBLOCK_INTERRUPTIONS(); }
51 /* }}} */
52
53 /* {{{ key_equals */
54 #define key_equals(a, b) (a.inode==b.inode && a.device==b.device)
55 /* }}} */
56
57 static void apc_cache_expunge(apc_cache_t* cache, size_t size);
58
59 /* {{{ hash */
60 static unsigned int hash(apc_cache_key_t key)
61 {
62     return key.data.file.device + key.data.file.inode;
63 }
64 /* }}} */
65
66 /* {{{ string_nhash_8 */
67 static unsigned int string_nhash_8(const char *s, size_t len)
68 {
69     register const unsigned int *iv = (const unsigned int *)s;
70     register unsigned int h = 0;
71     register const unsigned int *e  = (const unsigned int *)(s + len - (len % sizeof(unsigned int)));
72
73     for(;iv<e;iv++) {
74         h += *iv;
75         h = (h << 7) | (h >> ((8*sizeof(unsigned int)) - 7));
76     }
77     s = (const char *)iv;
78     for(len %= sizeof(unsigned int);len;len--) {
79         h += *(s++);
80     }
81     h ^= (h >> 13);
82     h ^= (h >> 7);
83     return h;
84 }
85 /* }}} */
86
87 /* {{{ make_slot */
88 slot_t* make_slot(apc_cache_key_t key, apc_cache_entry_t* value, slot_t* next, time_t t)
89 {
90     slot_t* p = apc_sma_malloc(sizeof(slot_t));
91     if (!p) return NULL;
92
93     if(value->type == APC_CACHE_ENTRY_USER) {
94         char *identifier = (char*) apc_xstrdup(key.data.user.identifier, apc_sma_malloc);
95         if (!identifier) {
96             apc_sma_free(p);
97             return NULL;
98         }
99         key.data.user.identifier = identifier;
100     } else if(key.type == APC_CACHE_KEY_FPFILE) {
101         char *fullpath = (char*) apc_xstrdup(key.data.fpfile.fullpath, apc_sma_malloc);
102         if (!fullpath) {
103             apc_sma_free(p);
104             return NULL;
105         }
106         key.data.fpfile.fullpath = fullpath;
107     }
108     p->key = key;
109     p->value = value;
110     p->next = next;
111     p->num_hits = 0;
112     p->creation_time = t;
113     p->access_time = t;
114     p->deletion_time = 0;
115     return p;
116 }
117 /* }}} */
118
119 /* {{{ free_slot */
120 static void free_slot(slot_t* slot)
121 {
122     if(slot->value->type == APC_CACHE_ENTRY_USER) {
123         apc_sma_free((char *)slot->key.data.user.identifier);
124     } else if(slot->key.type == APC_CACHE_KEY_FPFILE) {
125         apc_sma_free((char *)slot->key.data.fpfile.fullpath);
126     }
127     apc_cache_free_entry(slot->value);
128     apc_sma_free(slot);
129 }
130 /* }}} */
131
132 /* {{{ remove_slot */
133 static void remove_slot(apc_cache_t* cache, slot_t** slot)
134 {
135     slot_t* dead = *slot;
136     *slot = (*slot)->next;
137
138     cache->header->mem_size -= dead->value->mem_size;
139     cache->header->num_entries--;
140     if (dead->value->ref_count <= 0) {
141         free_slot(dead);
142     }
143     else {
144         dead->next = cache->header->deleted_list;
145         dead->deletion_time = time(0);
146         cache->header->deleted_list = dead;
147     }
148 }
149 /* }}} */
150
151 /* {{{ process_pending_removals */
152 static void process_pending_removals(apc_cache_t* cache)
153 {
154     slot_t** slot;
155     time_t now;
156
157     /* This function scans the list of removed cache entries and deletes any
158      * entry whose reference count is zero (indicating that it is no longer
159      * being executed) or that has been on the pending list for more than
160      * cache->gc_ttl seconds (we issue a warning in the latter case).
161      */
162
163     if (!cache->header->deleted_list)
164         return;
165
166     slot = &cache->header->deleted_list;
167     now = time(0);
168
169     while (*slot != NULL) {
170         int gc_sec = cache->gc_ttl ? (now - (*slot)->deletion_time) : 0;
171
172         if ((*slot)->value->ref_count <= 0 || gc_sec > cache->gc_ttl) {
173             slot_t* dead = *slot;
174
175             if (dead->value->ref_count > 0) {
176                 switch(dead->value->type) {
177                     case APC_CACHE_ENTRY_FILE:
178                         apc_log(APC_WARNING, "GC cache entry '%s' (dev=%d ino=%d) "
179                             "was on gc-list for %d seconds", dead->value->data.file.filename,
180                             dead->key.data.file.device, dead->key.data.file.inode, gc_sec);
181                         break;
182                     case APC_CACHE_ENTRY_USER:
183                         apc_log(APC_WARNING, "GC cache entry '%s' "
184                             "was on gc-list for %d seconds", dead->value->data.user.info, gc_sec);
185                         break;
186                 }
187             }
188             *slot = dead->next;
189             free_slot(dead);
190         }
191         else {
192             slot = &(*slot)->next;
193         }
194     }
195 }
196 /* }}} */
197
198 /* {{{ prevent_garbage_collection */
199 static void prevent_garbage_collection(apc_cache_entry_t* entry)
200 {
201     /* set reference counts on zend objects to an arbitrarily high value to
202      * prevent garbage collection after execution */
203
204     enum { BIG_VALUE = 1000 };
205
206     if(entry->data.file.op_array) {
207         entry->data.file.op_array->refcount[0] = BIG_VALUE;
208     }
209     if (entry->data.file.functions) {
210         int i;
211         apc_function_t* fns = entry->data.file.functions;
212         for (i=0; fns[i].function != NULL; i++) {
213             *(fns[i].function->op_array.refcount) = BIG_VALUE;
214         }
215     }
216     if (entry->data.file.classes) {
217         int i;
218         apc_class_t* classes = entry->data.file.classes;
219         for (i=0; classes[i].class_entry != NULL; i++) {
220 #ifdef ZEND_ENGINE_2            
221             classes[i].class_entry->refcount = BIG_VALUE;
222 #else            
223             classes[i].class_entry->refcount[0] = BIG_VALUE;
224 #endif
225         }
226     }
227 }
228 /* }}} */
229
230 /* {{{ apc_cache_create */
231 apc_cache_t* apc_cache_create(int size_hint, int gc_ttl, int ttl)
232 {
233     apc_cache_t* cache;
234     int cache_size;
235     int num_slots;
236     int i;
237
238     num_slots = size_hint > 0 ? size_hint*2 : 2000;
239
240     cache = (apc_cache_t*) apc_emalloc(sizeof(apc_cache_t));
241     cache_size = sizeof(cache_header_t) + num_slots*sizeof(slot_t*);
242
243     cache->shmaddr = apc_sma_malloc(cache_size);
244     if(!cache->shmaddr) {
245         apc_eprint("Unable to allocate shared memory for cache structures.  (Perhaps your shared memory size isn't large enough?). ");
246     }
247     memset(cache->shmaddr, 0, cache_size);
248
249     cache->header = (cache_header_t*) cache->shmaddr;
250     cache->header->num_hits = 0;
251     cache->header->num_misses = 0;
252     cache->header->deleted_list = NULL;
253     cache->header->start_time = time(NULL);
254     cache->header->expunges = 0;
255     cache->header->busy = 0;
256
257     cache->slots = (slot_t**) (((char*) cache->shmaddr) + sizeof(cache_header_t));
258     cache->num_slots = num_slots;
259     cache->gc_ttl = gc_ttl;
260     cache->ttl = ttl;
261     CREATE_LOCK(cache->header->lock);
262 #if NONBLOCKING_LOCK_AVAILABLE
263     CREATE_LOCK(cache->header->wrlock);
264 #endif
265     for (i = 0; i < num_slots; i++) {
266         cache->slots[i] = NULL;
267     }
268     cache->expunge_cb = apc_cache_expunge;
269
270     return cache;
271 }
272 /* }}} */
273
274 /* {{{ apc_cache_destroy */
275 void apc_cache_destroy(apc_cache_t* cache)
276 {
277     DESTROY_LOCK(cache);
278     apc_efree(cache);
279 }
280 /* }}} */
281
282 /* {{{ apc_cache_clear */
283 void apc_cache_clear(apc_cache_t* cache)
284 {
285     int i;
286
287     if(!cache) return;
288
289     LOCK(cache);
290     cache->header->busy = 1;
291     cache->header->num_hits = 0;
292     cache->header->num_misses = 0;
293     cache->header->start_time = time(NULL);
294     cache->header->expunges = 0;
295
296     for (i = 0; i < cache->num_slots; i++) {
297         slot_t* p = cache->slots[i];
298         while (p) {
299             remove_slot(cache, &p);
300         }
301         cache->slots[i] = NULL;
302     }
303     
304     cache->header->busy = 0;
305     UNLOCK(cache);
306 }
307 /* }}} */
308
309 /* {{{ apc_cache_expunge */
310 static void apc_cache_expunge(apc_cache_t* cache, size_t size)
311 {
312     int i;
313     time_t t;
314     TSRMLS_FETCH();
315
316 #if PHP_API_VERSION < 20041225
317 #if HAVE_APACHE && defined(APC_PHP4_STAT)
318     t = ((request_rec *)SG(server_context))->request_time;
319 #else
320     t = time(0);
321 #endif
322 #else
323     t = sapi_get_request_time(TSRMLS_C);
324 #endif
325
326     if(!cache) return;
327
328     if(!cache->ttl) {
329         /* 
330          * If cache->ttl is not set, we wipe out the entire cache when
331          * we run out of space. 
332          */
333         LOCK(cache);
334         cache->header->busy = 1;
335         cache->header->expunges++;
336         for (i = 0; i < cache->num_slots; i++) {
337             slot_t* p = cache->slots[i];
338             while (p) {
339                 remove_slot(cache, &p);
340             }
341             cache->slots[i] = NULL;
342         }
343         cache->header->busy = 0;
344         UNLOCK(cache);
345     } else {
346         slot_t **p;
347
348         /*
349          * If the ttl for the cache is set we walk through and delete stale 
350          * entries.  For the user cache that is slightly confusing since
351          * we have the individual entry ttl's we can look at, but that would be
352          * too much work.  So if you want the user cache expunged, set a high
353          * default apc.user_ttl and still provide a specific ttl for each entry
354          * on insert
355          */
356
357         LOCK(cache);
358         cache->header->busy = 1;
359         cache->header->expunges++;
360         for (i = 0; i < cache->num_slots; i++) {
361             p = &cache->slots[i];
362             while(*p) {
363                 /* 
364                  * For the user cache we look at the individual entry ttl values
365                  * and if not set fall back to the default ttl for the user cache
366                  */
367                 if((*p)->value->type == APC_CACHE_ENTRY_USER) {
368                     if((*p)->value->data.user.ttl) {
369                         if((*p)->creation_time + (*p)->value->data.user.ttl < t) {
370                             remove_slot(cache, p);
371                             continue;
372                         }
373                     } else if(cache->ttl) {
374                         if((*p)->creation_time + cache->ttl < t) {
375                             remove_slot(cache, p);
376                             continue;
377                         }
378                     }
379                 } else if((*p)->access_time < (t - cache->ttl)) {
380                     remove_slot(cache, p);
381                     continue;
382                 }
383                 p = &(*p)->next;
384             }
385         }
386         cache->header->busy = 0;
387         UNLOCK(cache);
388     }
389 }
390 /* }}} */
391
392 /* {{{ apc_cache_insert */
393 int apc_cache_insert(apc_cache_t* cache,
394                      apc_cache_key_t key,
395                      apc_cache_entry_t* value,
396                      time_t t)
397 {
398     slot_t** slot;
399
400     if (!value) {
401         return 0;
402     }
403
404 #ifdef __DEBUG_APC__
405     fprintf(stderr,"Inserting [%s]\n", value->data.file.filename);
406 #endif
407
408     LOCK(cache);
409     process_pending_removals(cache);
410
411     if(key.type == APC_CACHE_KEY_FILE) slot = &cache->slots[hash(key) % cache->num_slots];
412     else slot = &cache->slots[string_nhash_8(key.data.fpfile.fullpath, key.data.fpfile.fullpath_len) % cache->num_slots];
413
414     while(*slot) {
415       if(key.type == (*slot)->key.type) {
416         if(key.type == APC_CACHE_KEY_FILE) {
417             if(key_equals((*slot)->key.data.file, key.data.file)) {
418                 /* If existing slot for the same device+inode is different, remove it and insert the new version */
419                 if ((*slot)->key.mtime != key.mtime) {
420                     remove_slot(cache, slot);
421                     break;
422                 }
423                 UNLOCK(cache);
424                 return 0;
425             } else if(cache->ttl && (*slot)->access_time < (t - cache->ttl)) {
426                 remove_slot(cache, slot);
427                 continue;
428             }
429         } else {   /* APC_CACHE_KEY_FPFILE */
430                 if(!memcmp((*slot)->key.data.fpfile.fullpath, key.data.fpfile.fullpath, key.data.fpfile.fullpath_len+1)) {
431                 /* Hrm.. it's already here, remove it and insert new one */
432                 remove_slot(cache, slot);
433                 break;
434             } else if(cache->ttl && (*slot)->access_time < (t - cache->ttl)) {
435                 remove_slot(cache, slot);
436                 continue;
437             }
438         }
439       }
440       slot = &(*slot)->next;
441     }
442
443     if ((*slot = make_slot(key, value, *slot, t)) == NULL) {
444         UNLOCK(cache);
445         return -1;
446     }
447    
448     cache->header->mem_size += value->mem_size;
449     cache->header->num_entries++;
450     cache->header->num_inserts++;
451     
452     UNLOCK(cache);
453     return 1;
454 }
455 /* }}} */
456
457 /* {{{ apc_cache_user_insert */
458 int apc_cache_user_insert(apc_cache_t* cache, apc_cache_key_t key, apc_cache_entry_t* value, time_t t, int exclusive TSRMLS_DC)
459 {
460     slot_t** slot;
461     size_t* mem_size_ptr = NULL;
462
463     if (!value) {
464         return 0;
465     }
466
467     LOCK(cache);
468     process_pending_removals(cache);
469
470     slot = &cache->slots[string_nhash_8(key.data.user.identifier, key.data.user.identifier_len) % cache->num_slots];
471
472     if (APCG(mem_size_ptr) != NULL) {
473         mem_size_ptr = APCG(mem_size_ptr);
474         APCG(mem_size_ptr) = NULL;
475     }
476
477     while (*slot) {
478         if (!memcmp((*slot)->key.data.user.identifier, key.data.user.identifier, key.data.user.identifier_len)) {
479             /* 
480              * At this point we have found the user cache entry.  If we are doing 
481              * an exclusive insert (apc_add) we are going to bail right away if
482              * the user entry already exists and it has no ttl, or
483              * there is a ttl and the entry has not timed out yet.
484              */
485             if(exclusive && (  !(*slot)->value->data.user.ttl ||
486                               ( (*slot)->value->data.user.ttl && ((*slot)->creation_time + (*slot)->value->data.user.ttl) >= t ) 
487                             ) ) {
488                 UNLOCK(cache);
489                 return 0;
490             }
491             remove_slot(cache, slot);
492             break;
493         } else 
494         /* 
495          * This is a bit nasty.  The idea here is to do runtime cleanup of the linked list of
496          * slot entries so we don't always have to skip past a bunch of stale entries.  We check
497          * for staleness here and get rid of them by first checking to see if the cache has a global
498          * access ttl on it and removing entries that haven't been accessed for ttl seconds and secondly
499          * we see if the entry has a hard ttl on it and remove it if it has been around longer than its ttl
500          */
501         if((cache->ttl && (*slot)->access_time < (t - cache->ttl)) || 
502            ((*slot)->value->data.user.ttl && ((*slot)->creation_time + (*slot)->value->data.user.ttl) < t)) {
503             remove_slot(cache, slot);
504             continue;
505         }
506         slot = &(*slot)->next;
507     }
508
509     if (mem_size_ptr != NULL) {
510         APCG(mem_size_ptr) = mem_size_ptr;
511     }
512
513     if ((*slot = make_slot(key, value, *slot, t)) == NULL) {
514         UNLOCK(cache);
515         return 0;
516     }
517     if (APCG(mem_size_ptr) != NULL) {
518         value->mem_size = *APCG(mem_size_ptr);
519         cache->header->mem_size += *APCG(mem_size_ptr);
520     }
521     cache->header->num_entries++;
522     cache->header->num_inserts++;
523
524     UNLOCK(cache);
525     return 1;
526 }
527 /* }}} */
528
529 /* {{{ apc_cache_find_slot */
530 slot_t* apc_cache_find_slot(apc_cache_t* cache, apc_cache_key_t key, time_t t)
531 {
532     slot_t** slot;
533     volatile slot_t* retval = NULL;
534
535     LOCK(cache);
536     if(key.type == APC_CACHE_KEY_FILE) slot = &cache->slots[hash(key) % cache->num_slots];
537     else slot = &cache->slots[string_nhash_8(key.data.fpfile.fullpath, key.data.fpfile.fullpath_len) % cache->num_slots];
538
539     while (*slot) {
540       if(key.type == (*slot)->key.type) {
541         if(key.type == APC_CACHE_KEY_FILE) {
542             if(key_equals((*slot)->key.data.file, key.data.file)) {
543                 if((*slot)->key.mtime != key.mtime) {
544                     remove_slot(cache, slot);
545                     cache->header->num_misses++;
546                     UNLOCK(cache);
547                     return NULL;
548                 }
549                 (*slot)->num_hits++;
550                 (*slot)->value->ref_count++;
551                 (*slot)->access_time = t;
552                 prevent_garbage_collection((*slot)->value);
553                 cache->header->num_hits++;
554                 retval = *slot;
555                 UNLOCK(cache);
556                 return (slot_t*)retval;
557             }
558         } else {  /* APC_CACHE_KEY_FPFILE */
559             if(!memcmp((*slot)->key.data.fpfile.fullpath, key.data.fpfile.fullpath, key.data.fpfile.fullpath_len+1)) {
560                 /* TTL Check ? */
561                 (*slot)->num_hits++;
562                 (*slot)->value->ref_count++;
563                 (*slot)->access_time = t;
564                 prevent_garbage_collection((*slot)->value);
565                 cache->header->num_hits++;
566                 retval = *slot;
567                 UNLOCK(cache);
568                 return (slot_t*)retval;
569             }
570         }
571       }
572       slot = &(*slot)->next;
573     }
574     cache->header->num_misses++;
575     UNLOCK(cache);
576     return NULL;
577 }
578 /* }}} */
579
580 /* {{{ apc_cache_find */
581 apc_cache_entry_t* apc_cache_find(apc_cache_t* cache, apc_cache_key_t key, time_t t)
582 {
583     slot_t * slot = apc_cache_find_slot(cache, key, t);
584     return (slot) ? slot->value : NULL;
585 }
586 /* }}} */
587
588 /* {{{ apc_cache_user_find */
589 apc_cache_entry_t* apc_cache_user_find(apc_cache_t* cache, char *strkey, int keylen, time_t t)
590 {
591     slot_t** slot;
592     volatile apc_cache_entry_t* value = NULL;
593
594     LOCK(cache);
595
596     slot = &cache->slots[string_nhash_8(strkey, keylen) % cache->num_slots];
597
598     while (*slot) {
599         if (!memcmp((*slot)->key.data.user.identifier, strkey, keylen)) {
600             /* Check to make sure this entry isn't expired by a hard TTL */
601             if((*slot)->value->data.user.ttl && ((*slot)->creation_time + (*slot)->value->data.user.ttl) < t) {
602                 remove_slot(cache, slot);
603                 UNLOCK(cache);
604                 return NULL;
605             }
606             /* Otherwise we are fine, increase counters and return the cache entry */
607             (*slot)->num_hits++;
608             (*slot)->value->ref_count++;
609             (*slot)->access_time = t;
610
611             cache->header->num_hits++;
612             value = (*slot)->value;
613             UNLOCK(cache);
614             return (apc_cache_entry_t*)value;
615         }
616         slot = &(*slot)->next;
617     }
618  
619     UNLOCK(cache);
620     return NULL;
621 }
622 /* }}} */
623
624 /* {{{ apc_cache_user_delete */
625 int apc_cache_user_delete(apc_cache_t* cache, char *strkey, int keylen)
626 {
627     slot_t** slot;
628
629     LOCK(cache);
630
631     slot = &cache->slots[string_nhash_8(strkey, keylen) % cache->num_slots];
632
633     while (*slot) {
634         if (!memcmp((*slot)->key.data.user.identifier, strkey, keylen)) {
635             remove_slot(cache, slot);
636             UNLOCK(cache);
637             return 1;
638         }
639         slot = &(*slot)->next;
640     }
641
642     UNLOCK(cache);
643     return 0;
644 }
645 /* }}} */
646
647 /* {{{ apc_cache_release */
648 void apc_cache_release(apc_cache_t* cache, apc_cache_entry_t* entry)
649 {
650     LOCK(cache);
651     entry->ref_count--;
652     UNLOCK(cache);
653 }
654 /* }}} */
655
656 /* {{{ apc_cache_make_file_key */
657 int apc_cache_make_file_key(apc_cache_key_t* key,
658                        const char* filename,
659                        const char* include_path,
660                        time_t t
661                                            TSRMLS_DC)
662 {
663     struct stat *tmp_buf=NULL;
664     struct apc_fileinfo_t fileinfo = { {0}, };
665     int len;
666
667     assert(key != NULL);
668
669     if (!filename || !SG(request_info).path_translated) {
670 #ifdef __DEBUG_APC__
671         fprintf(stderr,"No filename and no path_translated - bailing\n");
672 #endif
673         return 0;
674         }
675
676     len = strlen(filename);
677     if(APCG(fpstat)==0) {
678         if(IS_ABSOLUTE_PATH(filename,len)) {
679             key->data.fpfile.fullpath = filename;
680             key->data.fpfile.fullpath_len = len;
681             key->mtime = t;
682             key->type = APC_CACHE_KEY_FPFILE;
683         } else {
684             if (apc_search_paths(filename, include_path, &fileinfo) != 0) {
685                 apc_wprint("apc failed to locate %s - bailing", filename);
686                 return 0;
687             }
688
689             if(!realpath(fileinfo.fullpath, APCG(canon_path))) {
690                 apc_wprint("realpath failed to canonicalize %s - bailing", filename);
691                 return 0;
692             }
693
694             key->data.fpfile.fullpath = APCG(canon_path);
695             key->data.fpfile.fullpath_len = strlen(APCG(canon_path));
696             key->mtime = t;
697             key->type = APC_CACHE_KEY_FPFILE;
698         }
699         return 1;
700     } 
701
702     if(!strcmp(SG(request_info).path_translated, filename)) {
703         tmp_buf = sapi_get_stat(TSRMLS_C);  /* Apache has already done this stat() for us */
704     }
705     if(tmp_buf) { 
706                 fileinfo.st_buf.sb = *tmp_buf;
707     } else {
708         if (apc_search_paths(filename, include_path, &fileinfo) != 0) {
709 #ifdef __DEBUG_APC__
710             fprintf(stderr,"Stat failed %s - bailing (%s) (%d)\n",filename,SG(request_info).path_translated);
711 #endif
712             return 0;
713         }
714     }
715
716     if(APCG(max_file_size) < fileinfo.st_buf.sb.st_size) {
717 #ifdef __DEBUG_APC__
718         fprintf(stderr,"File is too big %s (%d - %ld) - bailing\n",filename,t,fileinfo.st_buf.sb.st_size);
719 #endif
720         return 0;
721     }
722
723     /*
724      * This is a bit of a hack.
725      *
726      * Here I am checking to see if the file is at least 2 seconds old.  
727      * The idea is that if the file is currently being written to then its
728      * mtime is going to match or at most be 1 second off of the current
729      * request time and we want to avoid caching files that have not been
730      * completely written.  Of course, people should be using atomic 
731      * mechanisms to push files onto live web servers, but adding this
732      * tiny safety is easier than educating the world.  This is now
733      * configurable, but the default is still 2 seconds.
734      */
735     if(APCG(file_update_protection) && (t - fileinfo.st_buf.sb.st_mtime < APCG(file_update_protection))) { 
736 #ifdef __DEBUG_APC__
737         fprintf(stderr,"File is too new %s (%d - %d) - bailing\n",filename,t,fileinfo.st_buf.sb.st_mtime);
738 #endif
739         return 0;
740     }
741
742     key->data.file.device = fileinfo.st_buf.sb.st_dev;
743     key->data.file.inode  = fileinfo.st_buf.sb.st_ino;
744     /* 
745      * If working with content management systems that like to munge the mtime, 
746      * it might be appropriate to key off of the ctime to be immune to systems
747      * that try to backdate a template.  If the mtime is set to something older
748      * than the previous mtime of a template we will obviously never see this
749      * "older" template.  At some point the Smarty templating system did this.
750      * I generally disagree with using the ctime here because you lose the 
751      * ability to warm up new content by saving it to a temporary file, hitting
752      * it once to cache it and then renaming it into its permanent location so
753      * set the apc.stat_ctime=true to enable this check.
754      */
755     if(APCG(stat_ctime)) {
756         key->mtime  = (fileinfo.st_buf.sb.st_ctime > fileinfo.st_buf.sb.st_mtime) ? fileinfo.st_buf.sb.st_ctime : fileinfo.st_buf.sb.st_mtime; 
757     } else {
758         key->mtime = fileinfo.st_buf.sb.st_mtime;
759     }
760     key->type = APC_CACHE_KEY_FILE;
761     return 1;
762 }
763 /* }}} */
764
765 /* {{{ apc_cache_make_user_key */
766 int apc_cache_make_user_key(apc_cache_key_t* key, char* identifier, int identifier_len, const time_t t)
767 {
768     assert(key != NULL);
769
770     if (!identifier)
771         return 0;
772
773     key->data.user.identifier = identifier;
774     key->data.user.identifier_len = identifier_len;
775     key->mtime = t;
776     key->type = APC_CACHE_KEY_USER;
777     return 1;
778 }
779 /* }}} */
780
781 /* {{{ apc_cache_make_file_entry */
782 apc_cache_entry_t* apc_cache_make_file_entry(const char* filename,
783                                         zend_op_array* op_array,
784                                         apc_function_t* functions,
785                                         apc_class_t* classes)
786 {
787     apc_cache_entry_t* entry;
788
789     entry = (apc_cache_entry_t*) apc_sma_malloc(sizeof(apc_cache_entry_t));
790     if (!entry) return NULL;
791
792     entry->data.file.filename  = apc_xstrdup(filename, apc_sma_malloc);
793     if(!entry->data.file.filename) {
794 #ifdef __DEBUG_APC__
795         fprintf(stderr,"apc_cache_make_file_entry: entry->data.file.filename is NULL - bailing\n");
796 #endif
797         apc_sma_free(entry);
798         return NULL;
799     }
800 #ifdef __DEBUG_APC__
801     fprintf(stderr,"apc_cache_make_file_entry: entry->data.file.filename is [%s]\n",entry->data.file.filename);
802 #endif
803     entry->data.file.op_array  = op_array;
804     entry->data.file.functions = functions;
805     entry->data.file.classes   = classes;
806     entry->type = APC_CACHE_ENTRY_FILE;
807     entry->ref_count = 0;
808     entry->mem_size = 0;
809     return entry;
810 }
811 /* }}} */
812
813 /* {{{ apc_cache_store_zval */
814 zval* apc_cache_store_zval(zval* dst, const zval* src, apc_malloc_t allocate, apc_free_t deallocate)
815 {
816     smart_str buf = {0};
817     php_serialize_data_t var_hash;
818     TSRMLS_FETCH();
819
820     if((src->type & ~IS_CONSTANT_INDEX) == IS_OBJECT) {
821         if(!dst) {
822             CHECK(dst = (zval*) allocate(sizeof(zval)));
823         }
824                 
825         PHP_VAR_SERIALIZE_INIT(var_hash);
826         php_var_serialize(&buf, (zval**)&src, &var_hash TSRMLS_CC);
827         PHP_VAR_SERIALIZE_DESTROY(var_hash);
828                 
829         dst->type = IS_NULL; /* in case we fail */
830         if(buf.c) {
831             dst->type = src->type & ~IS_CONSTANT_INDEX;
832             dst->value.str.len = buf.len;
833             CHECK(dst->value.str.val = apc_xmemcpy(buf.c, buf.len+1, allocate));
834             dst->type = src->type;
835             smart_str_free(&buf);
836         }
837         return dst; 
838     } else {
839         
840         /* Maintain a list of zvals we've copied to properly handle recursive structures */
841         HashTable *old = APCG(copied_zvals);
842         APCG(copied_zvals) = emalloc(sizeof(HashTable));
843         zend_hash_init(APCG(copied_zvals), 0, NULL, NULL, 0);
844         
845         dst = apc_copy_zval(dst, src, allocate, deallocate);
846
847         if(APCG(copied_zvals)) {
848             zend_hash_destroy(APCG(copied_zvals));
849             efree(APCG(copied_zvals));
850         }
851
852         APCG(copied_zvals) = old;
853
854         return dst;
855     }
856 }
857 /* }}} */
858
859 /* {{{ apc_cache_fetch_zval */
860 zval* apc_cache_fetch_zval(zval* dst, const zval* src, apc_malloc_t allocate, apc_free_t deallocate)
861 {
862     TSRMLS_FETCH();
863     if((src->type & ~IS_CONSTANT_INDEX) == IS_OBJECT) {
864         php_unserialize_data_t var_hash;
865         const unsigned char *p = (unsigned char*)Z_STRVAL_P(src);
866
867         PHP_VAR_UNSERIALIZE_INIT(var_hash);
868         if(!php_var_unserialize(&dst, &p, p + Z_STRLEN_P(src), &var_hash TSRMLS_CC)) {
869             PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
870             zval_dtor(dst);
871             php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Error at offset %ld of %d bytes", (long)((char*)p - Z_STRVAL_P(src)), Z_STRLEN_P(src));
872             dst->type = IS_NULL;
873         }
874         PHP_VAR_UNSERIALIZE_DESTROY(var_hash);          
875         return dst; 
876     } else {
877     
878         /* Maintain a list of zvals we've copied to properly handle recursive structures */
879         HashTable *old = APCG(copied_zvals);
880         APCG(copied_zvals) = emalloc(sizeof(HashTable));
881         zend_hash_init(APCG(copied_zvals), 0, NULL, NULL, 0);
882         
883         dst = apc_copy_zval(dst, src, allocate, deallocate);
884
885         if(APCG(copied_zvals)) {
886             zend_hash_destroy(APCG(copied_zvals));
887             efree(APCG(copied_zvals));
888         }
889
890         APCG(copied_zvals) = old;
891
892         return dst;
893     }
894 }
895 /* }}} */
896
897 /* {{{ apc_cache_free_zval */
898 void apc_cache_free_zval(zval* src, apc_free_t deallocate)
899 {
900     TSRMLS_FETCH();
901     if ((src->type & ~IS_CONSTANT_INDEX) == IS_OBJECT) {
902         if (src->value.str.val) {
903                 deallocate(src->value.str.val);
904         }
905         deallocate(src);
906     } else {
907         /* Maintain a list of zvals we've copied to properly handle recursive structures */
908         HashTable *old = APCG(copied_zvals);
909         APCG(copied_zvals) = emalloc(sizeof(HashTable));
910         zend_hash_init(APCG(copied_zvals), 0, NULL, NULL, 0);
911         
912         apc_free_zval(src, deallocate);
913
914         if(APCG(copied_zvals)) {
915             zend_hash_destroy(APCG(copied_zvals));
916             efree(APCG(copied_zvals));
917         }
918
919         APCG(copied_zvals) = old;
920     }
921 }
922 /* }}} */
923
924 /* {{{ apc_cache_make_user_entry */
925 apc_cache_entry_t* apc_cache_make_user_entry(const char* info, int info_len, const zval* val, const unsigned int ttl)
926 {
927     apc_cache_entry_t* entry;
928
929     entry = (apc_cache_entry_t*) apc_sma_malloc(sizeof(apc_cache_entry_t));
930     if (!entry) return NULL;
931
932     entry->data.user.info = apc_xmemcpy(info, info_len, apc_sma_malloc);
933     entry->data.user.info_len = info_len;
934     if(!entry->data.user.info) {
935         apc_sma_free(entry);
936         return NULL;
937     }
938     entry->data.user.val = apc_cache_store_zval(NULL, val, apc_sma_malloc, apc_sma_free);
939     if(!entry->data.user.val) {
940         apc_sma_free(entry->data.user.info);
941         apc_sma_free(entry);
942         return NULL;
943     }
944     INIT_PZVAL(entry->data.user.val);
945     entry->data.user.ttl = ttl;
946     entry->type = APC_CACHE_ENTRY_USER;
947     entry->ref_count = 0;
948     entry->mem_size = 0;
949     return entry;
950 }
951 /* }}} */
952
953 /* {{{ apc_cache_free_entry */
954 void apc_cache_free_entry(apc_cache_entry_t* entry)
955 {
956     if (entry != NULL) {
957         assert(entry->ref_count == 0);
958         switch(entry->type) {
959             case APC_CACHE_ENTRY_FILE:
960                 apc_sma_free(entry->data.file.filename);
961                 apc_free_op_array(entry->data.file.op_array, apc_sma_free);
962                 apc_free_functions(entry->data.file.functions, apc_sma_free);
963                 apc_free_classes(entry->data.file.classes, apc_sma_free);
964                 break;
965             case APC_CACHE_ENTRY_USER:
966                 apc_sma_free(entry->data.user.info);
967                 apc_cache_free_zval(entry->data.user.val, apc_sma_free);
968                 break;
969         }
970         apc_sma_free(entry);
971     }
972 }
973 /* }}} */
974
975 /* {{{ apc_cache_info */
976 apc_cache_info_t* apc_cache_info(apc_cache_t* cache, zend_bool limited)
977 {
978     apc_cache_info_t* info;
979     slot_t* p;
980     int i;
981
982     if(!cache) return NULL;
983
984     LOCK(cache);
985
986     info = (apc_cache_info_t*) apc_emalloc(sizeof(apc_cache_info_t));
987     if(!info) {
988         UNLOCK(cache);
989         return NULL;
990     }
991     info->num_slots = cache->num_slots;
992     info->ttl = cache->ttl;
993     info->num_hits = cache->header->num_hits;
994     info->num_misses = cache->header->num_misses;
995     info->list = NULL;
996     info->deleted_list = NULL;
997     info->start_time = cache->header->start_time;
998     info->expunges = cache->header->expunges;
999     info->mem_size = cache->header->mem_size;
1000     info->num_entries = cache->header->num_entries;
1001     info->num_inserts = cache->header->num_inserts;
1002
1003     if(!limited) {
1004         /* For each hashtable slot */
1005         for (i = 0; i < info->num_slots; i++) {
1006             p = cache->slots[i];
1007             for (; p != NULL; p = p->next) {
1008                 apc_cache_link_t* link = (apc_cache_link_t*) apc_emalloc(sizeof(apc_cache_link_t));
1009
1010                 if(p->value->type == APC_CACHE_ENTRY_FILE) {
1011                     link->data.file.filename = apc_xstrdup(p->value->data.file.filename, apc_emalloc);
1012                     link->data.file.device = p->key.data.file.device;
1013                     link->data.file.inode = p->key.data.file.inode;
1014                     link->type = APC_CACHE_ENTRY_FILE;
1015                 } else if(p->value->type == APC_CACHE_ENTRY_USER) {
1016                     link->data.user.info = apc_xmemcpy(p->value->data.user.info, p->value->data.user.info_len, apc_emalloc);
1017                     link->data.user.ttl = p->value->data.user.ttl;
1018                     link->type = APC_CACHE_ENTRY_USER;
1019                 }
1020                 link->num_hits = p->num_hits;
1021                 link->mtime = p->key.mtime;
1022                 link->creation_time = p->creation_time;
1023                 link->deletion_time = p->deletion_time;
1024                 link->access_time = p->access_time;
1025                 link->ref_count = p->value->ref_count;
1026                 link->mem_size = p->value->mem_size;
1027                 link->next = info->list;
1028                 info->list = link;
1029             }
1030         }
1031
1032         /* For each slot pending deletion */
1033         for (p = cache->header->deleted_list; p != NULL; p = p->next) {
1034             apc_cache_link_t* link = (apc_cache_link_t*) apc_emalloc(sizeof(apc_cache_link_t));
1035
1036             if(p->value->type == APC_CACHE_ENTRY_FILE) {
1037                 link->data.file.filename = apc_xstrdup(p->value->data.file.filename, apc_emalloc);
1038                 if(p->key.type == APC_CACHE_KEY_FILE) {
1039                     link->data.file.device = p->key.data.file.device;
1040                     link->data.file.inode = p->key.data.file.inode;
1041                 } else { /* This is a no-stat fullpath file entry */
1042                     link->data.file.device = 0;
1043                     link->data.file.inode = 0;
1044                 }
1045                 link->type = APC_CACHE_ENTRY_FILE;
1046             } else if(p->value->type == APC_CACHE_ENTRY_USER) {
1047                 link->data.user.info = apc_xmemcpy(p->value->data.user.info, p->value->data.user.info_len, apc_emalloc);
1048                 link->data.user.ttl = p->value->data.user.ttl;
1049                 link->type = APC_CACHE_ENTRY_USER;
1050             }
1051             link->num_hits = p->num_hits;
1052             link->mtime = p->key.mtime;
1053             link->creation_time = p->creation_time;
1054             link->deletion_time = p->deletion_time;
1055             link->access_time = p->access_time;
1056             link->ref_count = p->value->ref_count;
1057             link->mem_size = p->value->mem_size;
1058             link->next = info->deleted_list;
1059             info->deleted_list = link;
1060         }
1061     }
1062
1063     UNLOCK(cache);
1064     return info;
1065 }
1066 /* }}} */
1067
1068 /* {{{ apc_cache_free_info */
1069 void apc_cache_free_info(apc_cache_info_t* info)
1070 {
1071     apc_cache_link_t* p = info->list;
1072     apc_cache_link_t* q = NULL;
1073     while (p != NULL) {
1074         q = p;
1075         p = p->next;
1076         if(q->type == APC_CACHE_ENTRY_FILE) apc_efree(q->data.file.filename);
1077         else if(q->type == APC_CACHE_ENTRY_USER) apc_efree(q->data.user.info);
1078         apc_efree(q);
1079     }
1080     p = info->deleted_list;
1081     while (p != NULL) {
1082         q = p;
1083         p = p->next;
1084         if(q->type == APC_CACHE_ENTRY_FILE) apc_efree(q->data.file.filename);
1085         else if(q->type == APC_CACHE_ENTRY_USER) apc_efree(q->data.user.info);
1086         apc_efree(q);
1087     }
1088     apc_efree(info);
1089 }
1090 /* }}} */
1091
1092 /* {{{ apc_cache_unlock */
1093 void apc_cache_unlock(apc_cache_t* cache)
1094 {
1095     UNLOCK(cache);
1096 }
1097 /* }}} */
1098
1099 /* {{{ apc_cache_busy */
1100 zend_bool apc_cache_busy(apc_cache_t* cache)
1101 {
1102     return cache->header->busy;
1103 }
1104 /* }}} */
1105
1106 #if NONBLOCKING_LOCK_AVAILABLE
1107 /* {{{ apc_cache_write_lock */
1108 zend_bool apc_cache_write_lock(apc_cache_t* cache)
1109 {
1110     return apc_lck_nb_lock(cache->header->wrlock);
1111 }
1112 /* }}} */
1113
1114 /* {{{ apc_cache_write_unlock */
1115 void apc_cache_write_unlock(apc_cache_t* cache)
1116 {
1117     apc_lck_unlock(cache->header->wrlock);
1118 }
1119 /* }}} */
1120 #endif
1121
1122 /*
1123  * Local variables:
1124  * tab-width: 4
1125  * c-basic-offset: 4
1126  * End:
1127  * vim600: expandtab sw=4 ts=4 sts=4 fdm=marker
1128  * vim<600: expandtab sw=4 ts=4 sts=4
1129  */