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