--- /dev/null
+/*
+ +----------------------------------------------------------------------+
+ | APC |
+ +----------------------------------------------------------------------+
+ | Copyright (c) 2006 The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Daniel Cowgill <dcowgill@communityconnect.com> |
+ | Rasmus Lerdorf <rasmus@php.net> |
+ | Arun C. Murthy <arunc@yahoo-inc.com> |
+ | Gopal Vijayaraghavan <gopalv@yahoo-inc.com> |
+ +----------------------------------------------------------------------+
+
+ This software was contributed to PHP by Community Connect Inc. in 2002
+ and revised in 2005 by Yahoo! Inc. to add support for PHP 5.1.
+ Future revisions and derivatives of this source code must acknowledge
+ Community Connect Inc. as the original contributor of this module by
+ leaving this note intact in the source code.
+
+ All other licensing and usage conditions are those of the PHP Group.
+
+ */
+
+/* $Id: apc_cache.c,v 3.145.2.2 2008/03/25 18:24:57 gopalv Exp $ */
+
+#include "apc_cache.h"
+#include "apc_lock.h"
+#include "apc_sma.h"
+#include "apc_globals.h"
+#include "SAPI.h"
+#include "ext/standard/php_var.h"
+#include "ext/standard/php_smart_str.h"
+
+/* TODO: rehash when load factor exceeds threshold */
+
+#define CHECK(p) { if ((p) == NULL) return NULL; }
+
+/* {{{ locking macros */
+#define CREATE_LOCK(lock) apc_lck_create(NULL, 0, 1, lock)
+#define DESTROY_LOCK(c) apc_lck_destroy(c->header->lock)
+#define LOCK(c) { HANDLE_BLOCK_INTERRUPTIONS(); apc_lck_lock(c->header->lock); }
+#define RDLOCK(c) { HANDLE_BLOCK_INTERRUPTIONS(); apc_lck_rdlock(c->header->lock); }
+#define UNLOCK(c) { apc_lck_unlock(c->header->lock); HANDLE_UNBLOCK_INTERRUPTIONS(); }
+/* }}} */
+
+/* {{{ key_equals */
+#define key_equals(a, b) (a.inode==b.inode && a.device==b.device)
+/* }}} */
+
+static void apc_cache_expunge(apc_cache_t* cache, size_t size);
+
+/* {{{ hash */
+static unsigned int hash(apc_cache_key_t key)
+{
+ return key.data.file.device + key.data.file.inode;
+}
+/* }}} */
+
+/* {{{ string_nhash_8 */
+static unsigned int string_nhash_8(const char *s, size_t len)
+{
+ register const unsigned int *iv = (const unsigned int *)s;
+ register unsigned int h = 0;
+ register const unsigned int *e = (const unsigned int *)(s + len - (len % sizeof(unsigned int)));
+
+ for(;iv<e;iv++) {
+ h += *iv;
+ h = (h << 7) | (h >> ((8*sizeof(unsigned int)) - 7));
+ }
+ s = (const char *)iv;
+ for(len %= sizeof(unsigned int);len;len--) {
+ h += *(s++);
+ }
+ h ^= (h >> 13);
+ h ^= (h >> 7);
+ return h;
+}
+/* }}} */
+
+/* {{{ make_slot */
+slot_t* make_slot(apc_cache_key_t key, apc_cache_entry_t* value, slot_t* next, time_t t)
+{
+ slot_t* p = apc_sma_malloc(sizeof(slot_t));
+ if (!p) return NULL;
+
+ if(value->type == APC_CACHE_ENTRY_USER) {
+ char *identifier = (char*) apc_xstrdup(key.data.user.identifier, apc_sma_malloc);
+ if (!identifier) {
+ apc_sma_free(p);
+ return NULL;
+ }
+ key.data.user.identifier = identifier;
+ } else if(key.type == APC_CACHE_KEY_FPFILE) {
+ char *fullpath = (char*) apc_xstrdup(key.data.fpfile.fullpath, apc_sma_malloc);
+ if (!fullpath) {
+ apc_sma_free(p);
+ return NULL;
+ }
+ key.data.fpfile.fullpath = fullpath;
+ }
+ p->key = key;
+ p->value = value;
+ p->next = next;
+ p->num_hits = 0;
+ p->creation_time = t;
+ p->access_time = t;
+ p->deletion_time = 0;
+ return p;
+}
+/* }}} */
+
+/* {{{ free_slot */
+static void free_slot(slot_t* slot)
+{
+ if(slot->value->type == APC_CACHE_ENTRY_USER) {
+ apc_sma_free((char *)slot->key.data.user.identifier);
+ } else if(slot->key.type == APC_CACHE_KEY_FPFILE) {
+ apc_sma_free((char *)slot->key.data.fpfile.fullpath);
+ }
+ apc_cache_free_entry(slot->value);
+ apc_sma_free(slot);
+}
+/* }}} */
+
+/* {{{ remove_slot */
+static void remove_slot(apc_cache_t* cache, slot_t** slot)
+{
+ slot_t* dead = *slot;
+ *slot = (*slot)->next;
+
+ cache->header->mem_size -= dead->value->mem_size;
+ cache->header->num_entries--;
+ if (dead->value->ref_count <= 0) {
+ free_slot(dead);
+ }
+ else {
+ dead->next = cache->header->deleted_list;
+ dead->deletion_time = time(0);
+ cache->header->deleted_list = dead;
+ }
+}
+/* }}} */
+
+/* {{{ process_pending_removals */
+static void process_pending_removals(apc_cache_t* cache)
+{
+ slot_t** slot;
+ time_t now;
+
+ /* This function scans the list of removed cache entries and deletes any
+ * entry whose reference count is zero (indicating that it is no longer
+ * being executed) or that has been on the pending list for more than
+ * cache->gc_ttl seconds (we issue a warning in the latter case).
+ */
+
+ if (!cache->header->deleted_list)
+ return;
+
+ slot = &cache->header->deleted_list;
+ now = time(0);
+
+ while (*slot != NULL) {
+ int gc_sec = cache->gc_ttl ? (now - (*slot)->deletion_time) : 0;
+
+ if ((*slot)->value->ref_count <= 0 || gc_sec > cache->gc_ttl) {
+ slot_t* dead = *slot;
+
+ if (dead->value->ref_count > 0) {
+ switch(dead->value->type) {
+ case APC_CACHE_ENTRY_FILE:
+ apc_log(APC_WARNING, "GC cache entry '%s' (dev=%d ino=%d) "
+ "was on gc-list for %d seconds", dead->value->data.file.filename,
+ dead->key.data.file.device, dead->key.data.file.inode, gc_sec);
+ break;
+ case APC_CACHE_ENTRY_USER:
+ apc_log(APC_WARNING, "GC cache entry '%s' "
+ "was on gc-list for %d seconds", dead->value->data.user.info, gc_sec);
+ break;
+ }
+ }
+ *slot = dead->next;
+ free_slot(dead);
+ }
+ else {
+ slot = &(*slot)->next;
+ }
+ }
+}
+/* }}} */
+
+/* {{{ prevent_garbage_collection */
+static void prevent_garbage_collection(apc_cache_entry_t* entry)
+{
+ /* set reference counts on zend objects to an arbitrarily high value to
+ * prevent garbage collection after execution */
+
+ enum { BIG_VALUE = 1000 };
+
+ if(entry->data.file.op_array) {
+ entry->data.file.op_array->refcount[0] = BIG_VALUE;
+ }
+ if (entry->data.file.functions) {
+ int i;
+ apc_function_t* fns = entry->data.file.functions;
+ for (i=0; fns[i].function != NULL; i++) {
+ *(fns[i].function->op_array.refcount) = BIG_VALUE;
+ }
+ }
+ if (entry->data.file.classes) {
+ int i;
+ apc_class_t* classes = entry->data.file.classes;
+ for (i=0; classes[i].class_entry != NULL; i++) {
+#ifdef ZEND_ENGINE_2
+ classes[i].class_entry->refcount = BIG_VALUE;
+#else
+ classes[i].class_entry->refcount[0] = BIG_VALUE;
+#endif
+ }
+ }
+}
+/* }}} */
+
+/* {{{ apc_cache_create */
+apc_cache_t* apc_cache_create(int size_hint, int gc_ttl, int ttl)
+{
+ apc_cache_t* cache;
+ int cache_size;
+ int num_slots;
+ int i;
+
+ num_slots = size_hint > 0 ? size_hint*2 : 2000;
+
+ cache = (apc_cache_t*) apc_emalloc(sizeof(apc_cache_t));
+ cache_size = sizeof(cache_header_t) + num_slots*sizeof(slot_t*);
+
+ cache->shmaddr = apc_sma_malloc(cache_size);
+ if(!cache->shmaddr) {
+ apc_eprint("Unable to allocate shared memory for cache structures. (Perhaps your shared memory size isn't large enough?). ");
+ }
+ memset(cache->shmaddr, 0, cache_size);
+
+ cache->header = (cache_header_t*) cache->shmaddr;
+ cache->header->num_hits = 0;
+ cache->header->num_misses = 0;
+ cache->header->deleted_list = NULL;
+ cache->header->start_time = time(NULL);
+ cache->header->expunges = 0;
+ cache->header->busy = 0;
+
+ cache->slots = (slot_t**) (((char*) cache->shmaddr) + sizeof(cache_header_t));
+ cache->num_slots = num_slots;
+ cache->gc_ttl = gc_ttl;
+ cache->ttl = ttl;
+ CREATE_LOCK(cache->header->lock);
+#if NONBLOCKING_LOCK_AVAILABLE
+ CREATE_LOCK(cache->header->wrlock);
+#endif
+ for (i = 0; i < num_slots; i++) {
+ cache->slots[i] = NULL;
+ }
+ cache->expunge_cb = apc_cache_expunge;
+
+ return cache;
+}
+/* }}} */
+
+/* {{{ apc_cache_destroy */
+void apc_cache_destroy(apc_cache_t* cache)
+{
+ DESTROY_LOCK(cache);
+ apc_efree(cache);
+}
+/* }}} */
+
+/* {{{ apc_cache_clear */
+void apc_cache_clear(apc_cache_t* cache)
+{
+ int i;
+
+ if(!cache) return;
+
+ LOCK(cache);
+ cache->header->busy = 1;
+ cache->header->num_hits = 0;
+ cache->header->num_misses = 0;
+ cache->header->start_time = time(NULL);
+ cache->header->expunges = 0;
+
+ for (i = 0; i < cache->num_slots; i++) {
+ slot_t* p = cache->slots[i];
+ while (p) {
+ remove_slot(cache, &p);
+ }
+ cache->slots[i] = NULL;
+ }
+
+ cache->header->busy = 0;
+ UNLOCK(cache);
+}
+/* }}} */
+
+/* {{{ apc_cache_expunge */
+static void apc_cache_expunge(apc_cache_t* cache, size_t size)
+{
+ int i;
+ time_t t;
+ TSRMLS_FETCH();
+
+#if PHP_API_VERSION < 20041225
+#if HAVE_APACHE && defined(APC_PHP4_STAT)
+ t = ((request_rec *)SG(server_context))->request_time;
+#else
+ t = time(0);
+#endif
+#else
+ t = sapi_get_request_time(TSRMLS_C);
+#endif
+
+ if(!cache) return;
+
+ if(!cache->ttl) {
+ /*
+ * If cache->ttl is not set, we wipe out the entire cache when
+ * we run out of space.
+ */
+ LOCK(cache);
+ cache->header->busy = 1;
+ cache->header->expunges++;
+ for (i = 0; i < cache->num_slots; i++) {
+ slot_t* p = cache->slots[i];
+ while (p) {
+ remove_slot(cache, &p);
+ }
+ cache->slots[i] = NULL;
+ }
+ cache->header->busy = 0;
+ UNLOCK(cache);
+ } else {
+ slot_t **p;
+
+ /*
+ * If the ttl for the cache is set we walk through and delete stale
+ * entries. For the user cache that is slightly confusing since
+ * we have the individual entry ttl's we can look at, but that would be
+ * too much work. So if you want the user cache expunged, set a high
+ * default apc.user_ttl and still provide a specific ttl for each entry
+ * on insert
+ */
+
+ LOCK(cache);
+ cache->header->busy = 1;
+ cache->header->expunges++;
+ for (i = 0; i < cache->num_slots; i++) {
+ p = &cache->slots[i];
+ while(*p) {
+ /*
+ * For the user cache we look at the individual entry ttl values
+ * and if not set fall back to the default ttl for the user cache
+ */
+ if((*p)->value->type == APC_CACHE_ENTRY_USER) {
+ if((*p)->value->data.user.ttl) {
+ if((*p)->creation_time + (*p)->value->data.user.ttl < t) {
+ remove_slot(cache, p);
+ continue;
+ }
+ } else if(cache->ttl) {
+ if((*p)->creation_time + cache->ttl < t) {
+ remove_slot(cache, p);
+ continue;
+ }
+ }
+ } else if((*p)->access_time < (t - cache->ttl)) {
+ remove_slot(cache, p);
+ continue;
+ }
+ p = &(*p)->next;
+ }
+ }
+ cache->header->busy = 0;
+ UNLOCK(cache);
+ }
+}
+/* }}} */
+
+/* {{{ apc_cache_insert */
+int apc_cache_insert(apc_cache_t* cache,
+ apc_cache_key_t key,
+ apc_cache_entry_t* value,
+ time_t t)
+{
+ slot_t** slot;
+
+ if (!value) {
+ return 0;
+ }
+
+#ifdef __DEBUG_APC__
+ fprintf(stderr,"Inserting [%s]\n", value->data.file.filename);
+#endif
+
+ LOCK(cache);
+ process_pending_removals(cache);
+
+ if(key.type == APC_CACHE_KEY_FILE) slot = &cache->slots[hash(key) % cache->num_slots];
+ else slot = &cache->slots[string_nhash_8(key.data.fpfile.fullpath, key.data.fpfile.fullpath_len) % cache->num_slots];
+
+ while(*slot) {
+ if(key.type == (*slot)->key.type) {
+ if(key.type == APC_CACHE_KEY_FILE) {
+ if(key_equals((*slot)->key.data.file, key.data.file)) {
+ /* If existing slot for the same device+inode is different, remove it and insert the new version */
+ if ((*slot)->key.mtime != key.mtime) {
+ remove_slot(cache, slot);
+ break;
+ }
+ UNLOCK(cache);
+ return 0;
+ } else if(cache->ttl && (*slot)->access_time < (t - cache->ttl)) {
+ remove_slot(cache, slot);
+ continue;
+ }
+ } else { /* APC_CACHE_KEY_FPFILE */
+ if(!memcmp((*slot)->key.data.fpfile.fullpath, key.data.fpfile.fullpath, key.data.fpfile.fullpath_len+1)) {
+ /* Hrm.. it's already here, remove it and insert new one */
+ remove_slot(cache, slot);
+ break;
+ } else if(cache->ttl && (*slot)->access_time < (t - cache->ttl)) {
+ remove_slot(cache, slot);
+ continue;
+ }
+ }
+ }
+ slot = &(*slot)->next;
+ }
+
+ if ((*slot = make_slot(key, value, *slot, t)) == NULL) {
+ UNLOCK(cache);
+ return -1;
+ }
+
+ cache->header->mem_size += value->mem_size;
+ cache->header->num_entries++;
+ cache->header->num_inserts++;
+
+ UNLOCK(cache);
+ return 1;
+}
+/* }}} */
+
+/* {{{ apc_cache_user_insert */
+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)
+{
+ slot_t** slot;
+ size_t* mem_size_ptr = NULL;
+
+ if (!value) {
+ return 0;
+ }
+
+ LOCK(cache);
+ process_pending_removals(cache);
+
+ slot = &cache->slots[string_nhash_8(key.data.user.identifier, key.data.user.identifier_len) % cache->num_slots];
+
+ if (APCG(mem_size_ptr) != NULL) {
+ mem_size_ptr = APCG(mem_size_ptr);
+ APCG(mem_size_ptr) = NULL;
+ }
+
+ while (*slot) {
+ if (!memcmp((*slot)->key.data.user.identifier, key.data.user.identifier, key.data.user.identifier_len)) {
+ /*
+ * At this point we have found the user cache entry. If we are doing
+ * an exclusive insert (apc_add) we are going to bail right away if
+ * the user entry already exists and it has no ttl, or
+ * there is a ttl and the entry has not timed out yet.
+ */
+ if(exclusive && ( !(*slot)->value->data.user.ttl ||
+ ( (*slot)->value->data.user.ttl && ((*slot)->creation_time + (*slot)->value->data.user.ttl) >= t )
+ ) ) {
+ UNLOCK(cache);
+ return 0;
+ }
+ remove_slot(cache, slot);
+ break;
+ } else
+ /*
+ * This is a bit nasty. The idea here is to do runtime cleanup of the linked list of
+ * slot entries so we don't always have to skip past a bunch of stale entries. We check
+ * for staleness here and get rid of them by first checking to see if the cache has a global
+ * access ttl on it and removing entries that haven't been accessed for ttl seconds and secondly
+ * we see if the entry has a hard ttl on it and remove it if it has been around longer than its ttl
+ */
+ if((cache->ttl && (*slot)->access_time < (t - cache->ttl)) ||
+ ((*slot)->value->data.user.ttl && ((*slot)->creation_time + (*slot)->value->data.user.ttl) < t)) {
+ remove_slot(cache, slot);
+ continue;
+ }
+ slot = &(*slot)->next;
+ }
+
+ if (mem_size_ptr != NULL) {
+ APCG(mem_size_ptr) = mem_size_ptr;
+ }
+
+ if ((*slot = make_slot(key, value, *slot, t)) == NULL) {
+ UNLOCK(cache);
+ return 0;
+ }
+ if (APCG(mem_size_ptr) != NULL) {
+ value->mem_size = *APCG(mem_size_ptr);
+ cache->header->mem_size += *APCG(mem_size_ptr);
+ }
+ cache->header->num_entries++;
+ cache->header->num_inserts++;
+
+ UNLOCK(cache);
+ return 1;
+}
+/* }}} */
+
+/* {{{ apc_cache_find_slot */
+slot_t* apc_cache_find_slot(apc_cache_t* cache, apc_cache_key_t key, time_t t)
+{
+ slot_t** slot;
+ volatile slot_t* retval = NULL;
+
+ LOCK(cache);
+ if(key.type == APC_CACHE_KEY_FILE) slot = &cache->slots[hash(key) % cache->num_slots];
+ else slot = &cache->slots[string_nhash_8(key.data.fpfile.fullpath, key.data.fpfile.fullpath_len) % cache->num_slots];
+
+ while (*slot) {
+ if(key.type == (*slot)->key.type) {
+ if(key.type == APC_CACHE_KEY_FILE) {
+ if(key_equals((*slot)->key.data.file, key.data.file)) {
+ if((*slot)->key.mtime != key.mtime) {
+ remove_slot(cache, slot);
+ cache->header->num_misses++;
+ UNLOCK(cache);
+ return NULL;
+ }
+ (*slot)->num_hits++;
+ (*slot)->value->ref_count++;
+ (*slot)->access_time = t;
+ prevent_garbage_collection((*slot)->value);
+ cache->header->num_hits++;
+ retval = *slot;
+ UNLOCK(cache);
+ return (slot_t*)retval;
+ }
+ } else { /* APC_CACHE_KEY_FPFILE */
+ if(!memcmp((*slot)->key.data.fpfile.fullpath, key.data.fpfile.fullpath, key.data.fpfile.fullpath_len+1)) {
+ /* TTL Check ? */
+ (*slot)->num_hits++;
+ (*slot)->value->ref_count++;
+ (*slot)->access_time = t;
+ prevent_garbage_collection((*slot)->value);
+ cache->header->num_hits++;
+ retval = *slot;
+ UNLOCK(cache);
+ return (slot_t*)retval;
+ }
+ }
+ }
+ slot = &(*slot)->next;
+ }
+ cache->header->num_misses++;
+ UNLOCK(cache);
+ return NULL;
+}
+/* }}} */
+
+/* {{{ apc_cache_find */
+apc_cache_entry_t* apc_cache_find(apc_cache_t* cache, apc_cache_key_t key, time_t t)
+{
+ slot_t * slot = apc_cache_find_slot(cache, key, t);
+ return (slot) ? slot->value : NULL;
+}
+/* }}} */
+
+/* {{{ apc_cache_user_find */
+apc_cache_entry_t* apc_cache_user_find(apc_cache_t* cache, char *strkey, int keylen, time_t t)
+{
+ slot_t** slot;
+ volatile apc_cache_entry_t* value = NULL;
+
+ LOCK(cache);
+
+ slot = &cache->slots[string_nhash_8(strkey, keylen) % cache->num_slots];
+
+ while (*slot) {
+ if (!memcmp((*slot)->key.data.user.identifier, strkey, keylen)) {
+ /* Check to make sure this entry isn't expired by a hard TTL */
+ if((*slot)->value->data.user.ttl && ((*slot)->creation_time + (*slot)->value->data.user.ttl) < t) {
+ remove_slot(cache, slot);
+ UNLOCK(cache);
+ return NULL;
+ }
+ /* Otherwise we are fine, increase counters and return the cache entry */
+ (*slot)->num_hits++;
+ (*slot)->value->ref_count++;
+ (*slot)->access_time = t;
+
+ cache->header->num_hits++;
+ value = (*slot)->value;
+ UNLOCK(cache);
+ return (apc_cache_entry_t*)value;
+ }
+ slot = &(*slot)->next;
+ }
+
+ UNLOCK(cache);
+ return NULL;
+}
+/* }}} */
+
+/* {{{ apc_cache_user_delete */
+int apc_cache_user_delete(apc_cache_t* cache, char *strkey, int keylen)
+{
+ slot_t** slot;
+
+ LOCK(cache);
+
+ slot = &cache->slots[string_nhash_8(strkey, keylen) % cache->num_slots];
+
+ while (*slot) {
+ if (!memcmp((*slot)->key.data.user.identifier, strkey, keylen)) {
+ remove_slot(cache, slot);
+ UNLOCK(cache);
+ return 1;
+ }
+ slot = &(*slot)->next;
+ }
+
+ UNLOCK(cache);
+ return 0;
+}
+/* }}} */
+
+/* {{{ apc_cache_release */
+void apc_cache_release(apc_cache_t* cache, apc_cache_entry_t* entry)
+{
+ LOCK(cache);
+ entry->ref_count--;
+ UNLOCK(cache);
+}
+/* }}} */
+
+/* {{{ apc_cache_make_file_key */
+int apc_cache_make_file_key(apc_cache_key_t* key,
+ const char* filename,
+ const char* include_path,
+ time_t t
+ TSRMLS_DC)
+{
+ struct stat *tmp_buf=NULL;
+ struct apc_fileinfo_t fileinfo = { {0}, };
+ int len;
+
+ assert(key != NULL);
+
+ if (!filename || !SG(request_info).path_translated) {
+#ifdef __DEBUG_APC__
+ fprintf(stderr,"No filename and no path_translated - bailing\n");
+#endif
+ return 0;
+ }
+
+ len = strlen(filename);
+ if(APCG(fpstat)==0) {
+ if(IS_ABSOLUTE_PATH(filename,len)) {
+ key->data.fpfile.fullpath = filename;
+ key->data.fpfile.fullpath_len = len;
+ key->mtime = t;
+ key->type = APC_CACHE_KEY_FPFILE;
+ } else {
+ if (apc_search_paths(filename, include_path, &fileinfo) != 0) {
+ apc_wprint("apc failed to locate %s - bailing", filename);
+ return 0;
+ }
+
+ if(!realpath(fileinfo.fullpath, APCG(canon_path))) {
+ apc_wprint("realpath failed to canonicalize %s - bailing", filename);
+ return 0;
+ }
+
+ key->data.fpfile.fullpath = APCG(canon_path);
+ key->data.fpfile.fullpath_len = strlen(APCG(canon_path));
+ key->mtime = t;
+ key->type = APC_CACHE_KEY_FPFILE;
+ }
+ return 1;
+ }
+
+ if(!strcmp(SG(request_info).path_translated, filename)) {
+ tmp_buf = sapi_get_stat(TSRMLS_C); /* Apache has already done this stat() for us */
+ }
+ if(tmp_buf) {
+ fileinfo.st_buf.sb = *tmp_buf;
+ } else {
+ if (apc_search_paths(filename, include_path, &fileinfo) != 0) {
+#ifdef __DEBUG_APC__
+ fprintf(stderr,"Stat failed %s - bailing (%s) (%d)\n",filename,SG(request_info).path_translated);
+#endif
+ return 0;
+ }
+ }
+
+ if(APCG(max_file_size) < fileinfo.st_buf.sb.st_size) {
+#ifdef __DEBUG_APC__
+ fprintf(stderr,"File is too big %s (%d - %ld) - bailing\n",filename,t,fileinfo.st_buf.sb.st_size);
+#endif
+ return 0;
+ }
+
+ /*
+ * This is a bit of a hack.
+ *
+ * Here I am checking to see if the file is at least 2 seconds old.
+ * The idea is that if the file is currently being written to then its
+ * mtime is going to match or at most be 1 second off of the current
+ * request time and we want to avoid caching files that have not been
+ * completely written. Of course, people should be using atomic
+ * mechanisms to push files onto live web servers, but adding this
+ * tiny safety is easier than educating the world. This is now
+ * configurable, but the default is still 2 seconds.
+ */
+ if(APCG(file_update_protection) && (t - fileinfo.st_buf.sb.st_mtime < APCG(file_update_protection))) {
+#ifdef __DEBUG_APC__
+ fprintf(stderr,"File is too new %s (%d - %d) - bailing\n",filename,t,fileinfo.st_buf.sb.st_mtime);
+#endif
+ return 0;
+ }
+
+ key->data.file.device = fileinfo.st_buf.sb.st_dev;
+ key->data.file.inode = fileinfo.st_buf.sb.st_ino;
+ /*
+ * If working with content management systems that like to munge the mtime,
+ * it might be appropriate to key off of the ctime to be immune to systems
+ * that try to backdate a template. If the mtime is set to something older
+ * than the previous mtime of a template we will obviously never see this
+ * "older" template. At some point the Smarty templating system did this.
+ * I generally disagree with using the ctime here because you lose the
+ * ability to warm up new content by saving it to a temporary file, hitting
+ * it once to cache it and then renaming it into its permanent location so
+ * set the apc.stat_ctime=true to enable this check.
+ */
+ if(APCG(stat_ctime)) {
+ 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;
+ } else {
+ key->mtime = fileinfo.st_buf.sb.st_mtime;
+ }
+ key->type = APC_CACHE_KEY_FILE;
+ return 1;
+}
+/* }}} */
+
+/* {{{ apc_cache_make_user_key */
+int apc_cache_make_user_key(apc_cache_key_t* key, char* identifier, int identifier_len, const time_t t)
+{
+ assert(key != NULL);
+
+ if (!identifier)
+ return 0;
+
+ key->data.user.identifier = identifier;
+ key->data.user.identifier_len = identifier_len;
+ key->mtime = t;
+ key->type = APC_CACHE_KEY_USER;
+ return 1;
+}
+/* }}} */
+
+/* {{{ apc_cache_make_file_entry */
+apc_cache_entry_t* apc_cache_make_file_entry(const char* filename,
+ zend_op_array* op_array,
+ apc_function_t* functions,
+ apc_class_t* classes)
+{
+ apc_cache_entry_t* entry;
+
+ entry = (apc_cache_entry_t*) apc_sma_malloc(sizeof(apc_cache_entry_t));
+ if (!entry) return NULL;
+
+ entry->data.file.filename = apc_xstrdup(filename, apc_sma_malloc);
+ if(!entry->data.file.filename) {
+#ifdef __DEBUG_APC__
+ fprintf(stderr,"apc_cache_make_file_entry: entry->data.file.filename is NULL - bailing\n");
+#endif
+ apc_sma_free(entry);
+ return NULL;
+ }
+#ifdef __DEBUG_APC__
+ fprintf(stderr,"apc_cache_make_file_entry: entry->data.file.filename is [%s]\n",entry->data.file.filename);
+#endif
+ entry->data.file.op_array = op_array;
+ entry->data.file.functions = functions;
+ entry->data.file.classes = classes;
+ entry->type = APC_CACHE_ENTRY_FILE;
+ entry->ref_count = 0;
+ entry->mem_size = 0;
+ return entry;
+}
+/* }}} */
+
+/* {{{ apc_cache_store_zval */
+zval* apc_cache_store_zval(zval* dst, const zval* src, apc_malloc_t allocate, apc_free_t deallocate)
+{
+ smart_str buf = {0};
+ php_serialize_data_t var_hash;
+ TSRMLS_FETCH();
+
+ if((src->type & ~IS_CONSTANT_INDEX) == IS_OBJECT) {
+ if(!dst) {
+ CHECK(dst = (zval*) allocate(sizeof(zval)));
+ }
+
+ PHP_VAR_SERIALIZE_INIT(var_hash);
+ php_var_serialize(&buf, (zval**)&src, &var_hash TSRMLS_CC);
+ PHP_VAR_SERIALIZE_DESTROY(var_hash);
+
+ dst->type = IS_NULL; /* in case we fail */
+ if(buf.c) {
+ dst->type = src->type & ~IS_CONSTANT_INDEX;
+ dst->value.str.len = buf.len;
+ CHECK(dst->value.str.val = apc_xmemcpy(buf.c, buf.len+1, allocate));
+ dst->type = src->type;
+ smart_str_free(&buf);
+ }
+ return dst;
+ } else {
+
+ /* Maintain a list of zvals we've copied to properly handle recursive structures */
+ HashTable *old = APCG(copied_zvals);
+ APCG(copied_zvals) = emalloc(sizeof(HashTable));
+ zend_hash_init(APCG(copied_zvals), 0, NULL, NULL, 0);
+
+ dst = apc_copy_zval(dst, src, allocate, deallocate);
+
+ if(APCG(copied_zvals)) {
+ zend_hash_destroy(APCG(copied_zvals));
+ efree(APCG(copied_zvals));
+ }
+
+ APCG(copied_zvals) = old;
+
+ return dst;
+ }
+}
+/* }}} */
+
+/* {{{ apc_cache_fetch_zval */
+zval* apc_cache_fetch_zval(zval* dst, const zval* src, apc_malloc_t allocate, apc_free_t deallocate)
+{
+ TSRMLS_FETCH();
+ if((src->type & ~IS_CONSTANT_INDEX) == IS_OBJECT) {
+ php_unserialize_data_t var_hash;
+ const unsigned char *p = (unsigned char*)Z_STRVAL_P(src);
+
+ PHP_VAR_UNSERIALIZE_INIT(var_hash);
+ if(!php_var_unserialize(&dst, &p, p + Z_STRLEN_P(src), &var_hash TSRMLS_CC)) {
+ PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
+ zval_dtor(dst);
+ 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));
+ dst->type = IS_NULL;
+ }
+ PHP_VAR_UNSERIALIZE_DESTROY(var_hash);
+ return dst;
+ } else {
+
+ /* Maintain a list of zvals we've copied to properly handle recursive structures */
+ HashTable *old = APCG(copied_zvals);
+ APCG(copied_zvals) = emalloc(sizeof(HashTable));
+ zend_hash_init(APCG(copied_zvals), 0, NULL, NULL, 0);
+
+ dst = apc_copy_zval(dst, src, allocate, deallocate);
+
+ if(APCG(copied_zvals)) {
+ zend_hash_destroy(APCG(copied_zvals));
+ efree(APCG(copied_zvals));
+ }
+
+ APCG(copied_zvals) = old;
+
+ return dst;
+ }
+}
+/* }}} */
+
+/* {{{ apc_cache_free_zval */
+void apc_cache_free_zval(zval* src, apc_free_t deallocate)
+{
+ TSRMLS_FETCH();
+ if ((src->type & ~IS_CONSTANT_INDEX) == IS_OBJECT) {
+ if (src->value.str.val) {
+ deallocate(src->value.str.val);
+ }
+ deallocate(src);
+ } else {
+ /* Maintain a list of zvals we've copied to properly handle recursive structures */
+ HashTable *old = APCG(copied_zvals);
+ APCG(copied_zvals) = emalloc(sizeof(HashTable));
+ zend_hash_init(APCG(copied_zvals), 0, NULL, NULL, 0);
+
+ apc_free_zval(src, deallocate);
+
+ if(APCG(copied_zvals)) {
+ zend_hash_destroy(APCG(copied_zvals));
+ efree(APCG(copied_zvals));
+ }
+
+ APCG(copied_zvals) = old;
+ }
+}
+/* }}} */
+
+/* {{{ apc_cache_make_user_entry */
+apc_cache_entry_t* apc_cache_make_user_entry(const char* info, int info_len, const zval* val, const unsigned int ttl)
+{
+ apc_cache_entry_t* entry;
+
+ entry = (apc_cache_entry_t*) apc_sma_malloc(sizeof(apc_cache_entry_t));
+ if (!entry) return NULL;
+
+ entry->data.user.info = apc_xmemcpy(info, info_len, apc_sma_malloc);
+ entry->data.user.info_len = info_len;
+ if(!entry->data.user.info) {
+ apc_sma_free(entry);
+ return NULL;
+ }
+ entry->data.user.val = apc_cache_store_zval(NULL, val, apc_sma_malloc, apc_sma_free);
+ if(!entry->data.user.val) {
+ apc_sma_free(entry->data.user.info);
+ apc_sma_free(entry);
+ return NULL;
+ }
+ INIT_PZVAL(entry->data.user.val);
+ entry->data.user.ttl = ttl;
+ entry->type = APC_CACHE_ENTRY_USER;
+ entry->ref_count = 0;
+ entry->mem_size = 0;
+ return entry;
+}
+/* }}} */
+
+/* {{{ apc_cache_free_entry */
+void apc_cache_free_entry(apc_cache_entry_t* entry)
+{
+ if (entry != NULL) {
+ assert(entry->ref_count == 0);
+ switch(entry->type) {
+ case APC_CACHE_ENTRY_FILE:
+ apc_sma_free(entry->data.file.filename);
+ apc_free_op_array(entry->data.file.op_array, apc_sma_free);
+ apc_free_functions(entry->data.file.functions, apc_sma_free);
+ apc_free_classes(entry->data.file.classes, apc_sma_free);
+ break;
+ case APC_CACHE_ENTRY_USER:
+ apc_sma_free(entry->data.user.info);
+ apc_cache_free_zval(entry->data.user.val, apc_sma_free);
+ break;
+ }
+ apc_sma_free(entry);
+ }
+}
+/* }}} */
+
+/* {{{ apc_cache_info */
+apc_cache_info_t* apc_cache_info(apc_cache_t* cache, zend_bool limited)
+{
+ apc_cache_info_t* info;
+ slot_t* p;
+ int i;
+
+ if(!cache) return NULL;
+
+ LOCK(cache);
+
+ info = (apc_cache_info_t*) apc_emalloc(sizeof(apc_cache_info_t));
+ if(!info) {
+ UNLOCK(cache);
+ return NULL;
+ }
+ info->num_slots = cache->num_slots;
+ info->ttl = cache->ttl;
+ info->num_hits = cache->header->num_hits;
+ info->num_misses = cache->header->num_misses;
+ info->list = NULL;
+ info->deleted_list = NULL;
+ info->start_time = cache->header->start_time;
+ info->expunges = cache->header->expunges;
+ info->mem_size = cache->header->mem_size;
+ info->num_entries = cache->header->num_entries;
+ info->num_inserts = cache->header->num_inserts;
+
+ if(!limited) {
+ /* For each hashtable slot */
+ for (i = 0; i < info->num_slots; i++) {
+ p = cache->slots[i];
+ for (; p != NULL; p = p->next) {
+ apc_cache_link_t* link = (apc_cache_link_t*) apc_emalloc(sizeof(apc_cache_link_t));
+
+ if(p->value->type == APC_CACHE_ENTRY_FILE) {
+ link->data.file.filename = apc_xstrdup(p->value->data.file.filename, apc_emalloc);
+ link->data.file.device = p->key.data.file.device;
+ link->data.file.inode = p->key.data.file.inode;
+ link->type = APC_CACHE_ENTRY_FILE;
+ } else if(p->value->type == APC_CACHE_ENTRY_USER) {
+ link->data.user.info = apc_xmemcpy(p->value->data.user.info, p->value->data.user.info_len, apc_emalloc);
+ link->data.user.ttl = p->value->data.user.ttl;
+ link->type = APC_CACHE_ENTRY_USER;
+ }
+ link->num_hits = p->num_hits;
+ link->mtime = p->key.mtime;
+ link->creation_time = p->creation_time;
+ link->deletion_time = p->deletion_time;
+ link->access_time = p->access_time;
+ link->ref_count = p->value->ref_count;
+ link->mem_size = p->value->mem_size;
+ link->next = info->list;
+ info->list = link;
+ }
+ }
+
+ /* For each slot pending deletion */
+ for (p = cache->header->deleted_list; p != NULL; p = p->next) {
+ apc_cache_link_t* link = (apc_cache_link_t*) apc_emalloc(sizeof(apc_cache_link_t));
+
+ if(p->value->type == APC_CACHE_ENTRY_FILE) {
+ link->data.file.filename = apc_xstrdup(p->value->data.file.filename, apc_emalloc);
+ if(p->key.type == APC_CACHE_KEY_FILE) {
+ link->data.file.device = p->key.data.file.device;
+ link->data.file.inode = p->key.data.file.inode;
+ } else { /* This is a no-stat fullpath file entry */
+ link->data.file.device = 0;
+ link->data.file.inode = 0;
+ }
+ link->type = APC_CACHE_ENTRY_FILE;
+ } else if(p->value->type == APC_CACHE_ENTRY_USER) {
+ link->data.user.info = apc_xmemcpy(p->value->data.user.info, p->value->data.user.info_len, apc_emalloc);
+ link->data.user.ttl = p->value->data.user.ttl;
+ link->type = APC_CACHE_ENTRY_USER;
+ }
+ link->num_hits = p->num_hits;
+ link->mtime = p->key.mtime;
+ link->creation_time = p->creation_time;
+ link->deletion_time = p->deletion_time;
+ link->access_time = p->access_time;
+ link->ref_count = p->value->ref_count;
+ link->mem_size = p->value->mem_size;
+ link->next = info->deleted_list;
+ info->deleted_list = link;
+ }
+ }
+
+ UNLOCK(cache);
+ return info;
+}
+/* }}} */
+
+/* {{{ apc_cache_free_info */
+void apc_cache_free_info(apc_cache_info_t* info)
+{
+ apc_cache_link_t* p = info->list;
+ apc_cache_link_t* q = NULL;
+ while (p != NULL) {
+ q = p;
+ p = p->next;
+ if(q->type == APC_CACHE_ENTRY_FILE) apc_efree(q->data.file.filename);
+ else if(q->type == APC_CACHE_ENTRY_USER) apc_efree(q->data.user.info);
+ apc_efree(q);
+ }
+ p = info->deleted_list;
+ while (p != NULL) {
+ q = p;
+ p = p->next;
+ if(q->type == APC_CACHE_ENTRY_FILE) apc_efree(q->data.file.filename);
+ else if(q->type == APC_CACHE_ENTRY_USER) apc_efree(q->data.user.info);
+ apc_efree(q);
+ }
+ apc_efree(info);
+}
+/* }}} */
+
+/* {{{ apc_cache_unlock */
+void apc_cache_unlock(apc_cache_t* cache)
+{
+ UNLOCK(cache);
+}
+/* }}} */
+
+/* {{{ apc_cache_busy */
+zend_bool apc_cache_busy(apc_cache_t* cache)
+{
+ return cache->header->busy;
+}
+/* }}} */
+
+#if NONBLOCKING_LOCK_AVAILABLE
+/* {{{ apc_cache_write_lock */
+zend_bool apc_cache_write_lock(apc_cache_t* cache)
+{
+ return apc_lck_nb_lock(cache->header->wrlock);
+}
+/* }}} */
+
+/* {{{ apc_cache_write_unlock */
+void apc_cache_write_unlock(apc_cache_t* cache)
+{
+ apc_lck_unlock(cache->header->wrlock);
+}
+/* }}} */
+#endif
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: expandtab sw=4 ts=4 sts=4 fdm=marker
+ * vim<600: expandtab sw=4 ts=4 sts=4
+ */