X-Git-Url: http://ftp.carnet.hr/carnet-debian/scm?p=php5-apc.git;a=blobdiff_plain;f=apc_cache.c;fp=apc_cache.c;h=62d5f8b9822b20eaf65df393ecddd264bb2af78f;hp=0000000000000000000000000000000000000000;hb=3682e0a7a26931aabca2b6e54eb08efd7dc0430b;hpb=575ce08215526bb71a967d69d601e77e1afbcd11 diff --git a/apc_cache.c b/apc_cache.c new file mode 100644 index 0000000..62d5f8b --- /dev/null +++ b/apc_cache.c @@ -0,0 +1,1129 @@ +/* + +----------------------------------------------------------------------+ + | 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 | + | Rasmus Lerdorf | + | Arun C. Murthy | + | Gopal Vijayaraghavan | + +----------------------------------------------------------------------+ + + 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> ((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 + */