/*	
 *	+-------------+
 *	|   ######    |
 *	|    #    #   |
 *	|    #####    |   Copyright (c) 2000/2001 Bware Technologies
 *	|    #    #   |
 *	|   ######    |
 *	+-------------+
 */

/*
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Lesser General Public
 *   License as published by the Free Software Foundation; either
 *   version 2.1 of the License, or (at your option) any later version.
 *	
 *   This library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *   Lesser General Public License for more details.
 *   
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this library; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/*
 * char rcsid[]     = "$Header: /usr/local/bware/CVS/wapzilla/src/bware_cache/bware_cache.c,v 1.19.4.11 2002/02/13 15:13:41 federico Exp $";
 * char copyright[] = "%Z% Bware Technologies, ** ALL RIGHTS RESERVED **";
 * char sccsid[]    = "%Z% %M% %I% (%H%-%T%)";
 */

/*
 * DEBUG and/or TEST_PERFORMANCE crash the CGI version of PHP
 * (eg. ``php -m'').  Do not use in production environment.
 */

/* #define DEBUG */
/* #define TEST_PERFORMANCE */

#if defined(DEBUG) || defined(TEST_PERFORMANCE)

#include <sys/time.h>
#include <ctype.h>
#include <stdio.h>
#include <unistd.h>

#include <zend_compile.h>

#define DEBUG_LOGFILE	"/tmp/bwlog"

static FILE *F_fp;

/*
 * Write a runtime key (runtime keys are built by the Zend function
 * build_runtime_defined_function_key()).
 * The first and last character of a runtime key are '\0'.
 */
static void
F_put_rkey(char *key, int len)
{
	int i;

	if (len > 20) {
		/*
		 * If the key is very long, write only
		 * the first and the last part.
		 */
		F_put_rkey(key, 7);
		fprintf(F_fp, " ... ");
		F_put_rkey(key+len-7, 7);
	} else {
		fprintf(F_fp, "\"");
		for (i = 0; i < len; i++)
			if (key[i] == '\0')
				fprintf(F_fp, "\\0");
			else
				fprintf(F_fp, "%c", key[i]);
		fprintf(F_fp, "\"");
	}
}

/*
 * Write the symbolic name of an opcode/extended-opcode.
 */
static void
F_put_opcode(int oc, int eoc, const char *funcname)
{
#define CASE_OP(x)	case x: fprintf(F_fp, "%s", #x); break
#define CASE_OPX(x)	case x: fprintf(F_fp, "  (%s)", #x); break

	fprintf(F_fp, "  %s ==> ", funcname ? funcname : "(top)");

	switch (oc) {
	CASE_OP(ZEND_NOP);
	CASE_OP(ZEND_ADD);
	CASE_OP(ZEND_SUB);
	CASE_OP(ZEND_MUL);
	CASE_OP(ZEND_DIV);
	CASE_OP(ZEND_MOD);
	CASE_OP(ZEND_SL);
	CASE_OP(ZEND_SR);
	CASE_OP(ZEND_CONCAT);
	CASE_OP(ZEND_BW_OR);
	CASE_OP(ZEND_BW_AND);
	CASE_OP(ZEND_BW_XOR);
	CASE_OP(ZEND_BW_NOT);
	CASE_OP(ZEND_BOOL_NOT);
	CASE_OP(ZEND_BOOL_XOR);
	CASE_OP(ZEND_IS_IDENTICAL);
	CASE_OP(ZEND_IS_NOT_IDENTICAL);
	CASE_OP(ZEND_IS_EQUAL);
	CASE_OP(ZEND_IS_NOT_EQUAL);
	CASE_OP(ZEND_IS_SMALLER);
	CASE_OP(ZEND_IS_SMALLER_OR_EQUAL);
	CASE_OP(ZEND_CAST);
	CASE_OP(ZEND_QM_ASSIGN);
	CASE_OP(ZEND_ASSIGN_ADD);
	CASE_OP(ZEND_ASSIGN_SUB);
	CASE_OP(ZEND_ASSIGN_MUL);
	CASE_OP(ZEND_ASSIGN_DIV);
	CASE_OP(ZEND_ASSIGN_MOD);
	CASE_OP(ZEND_ASSIGN_SL);
	CASE_OP(ZEND_ASSIGN_SR);
	CASE_OP(ZEND_ASSIGN_CONCAT);
	CASE_OP(ZEND_ASSIGN_BW_OR);
	CASE_OP(ZEND_ASSIGN_BW_AND);
	CASE_OP(ZEND_ASSIGN_BW_XOR);
	CASE_OP(ZEND_PRE_INC);
	CASE_OP(ZEND_PRE_DEC);
	CASE_OP(ZEND_POST_INC);
	CASE_OP(ZEND_POST_DEC);
	CASE_OP(ZEND_ASSIGN);
	CASE_OP(ZEND_ASSIGN_REF);
	CASE_OP(ZEND_ECHO);
	CASE_OP(ZEND_PRINT);
	CASE_OP(ZEND_JMP);
	CASE_OP(ZEND_JMPZ);
	CASE_OP(ZEND_JMPNZ);
	CASE_OP(ZEND_JMPZNZ);
	CASE_OP(ZEND_JMPZ_EX);
	CASE_OP(ZEND_JMPNZ_EX);
	CASE_OP(ZEND_CASE);
	CASE_OP(ZEND_SWITCH_FREE);
	CASE_OP(ZEND_BRK);
	CASE_OP(ZEND_CONT);
	CASE_OP(ZEND_BOOL);
	CASE_OP(ZEND_INIT_STRING);
	CASE_OP(ZEND_ADD_CHAR);
	CASE_OP(ZEND_ADD_STRING);
	CASE_OP(ZEND_ADD_VAR);
	CASE_OP(ZEND_BEGIN_SILENCE);
	CASE_OP(ZEND_END_SILENCE);
	CASE_OP(ZEND_INIT_FCALL_BY_NAME);
	CASE_OP(ZEND_DO_FCALL);
	CASE_OP(ZEND_DO_FCALL_BY_NAME);
	CASE_OP(ZEND_RETURN);
	CASE_OP(ZEND_RECV);
	CASE_OP(ZEND_RECV_INIT);
	CASE_OP(ZEND_SEND_VAL);
	CASE_OP(ZEND_SEND_VAR);
	CASE_OP(ZEND_SEND_REF);
	CASE_OP(ZEND_NEW);
	CASE_OP(ZEND_JMP_NO_CTOR);
	CASE_OP(ZEND_FREE);
	CASE_OP(ZEND_INIT_ARRAY);
	CASE_OP(ZEND_ADD_ARRAY_ELEMENT);
	CASE_OP(ZEND_INCLUDE_OR_EVAL);
	CASE_OP(ZEND_UNSET_VAR);
	CASE_OP(ZEND_UNSET_DIM_OBJ);
	CASE_OP(ZEND_ISSET_ISEMPTY);
	CASE_OP(ZEND_FE_RESET);
	CASE_OP(ZEND_FE_FETCH);
	CASE_OP(ZEND_EXIT);
	CASE_OP(ZEND_FETCH_R);
	CASE_OP(ZEND_FETCH_DIM_R);
	CASE_OP(ZEND_FETCH_OBJ_R);
	CASE_OP(ZEND_FETCH_W);
	CASE_OP(ZEND_FETCH_DIM_W);
	CASE_OP(ZEND_FETCH_OBJ_W);
	CASE_OP(ZEND_FETCH_RW);
	CASE_OP(ZEND_FETCH_DIM_RW);
	CASE_OP(ZEND_FETCH_OBJ_RW);
	CASE_OP(ZEND_FETCH_IS);
	CASE_OP(ZEND_FETCH_DIM_IS);
	CASE_OP(ZEND_FETCH_OBJ_IS);
	CASE_OP(ZEND_FETCH_FUNC_ARG);
	CASE_OP(ZEND_FETCH_DIM_FUNC_ARG);
	CASE_OP(ZEND_FETCH_OBJ_FUNC_ARG);
	CASE_OP(ZEND_FETCH_UNSET);
	CASE_OP(ZEND_FETCH_DIM_UNSET);
	CASE_OP(ZEND_FETCH_OBJ_UNSET);
	CASE_OP(ZEND_FETCH_DIM_TMP_VAR);
	CASE_OP(ZEND_FETCH_CONSTANT);
	CASE_OP(ZEND_DECLARE_FUNCTION_OR_CLASS);
	CASE_OP(ZEND_EXT_STMT);
	CASE_OP(ZEND_EXT_FCALL_BEGIN);
	CASE_OP(ZEND_EXT_FCALL_END);
	CASE_OP(ZEND_EXT_NOP);
	CASE_OP(ZEND_TICKS);
	CASE_OP(ZEND_SEND_VAR_NO_REF);
	default: fprintf(F_fp, "%-30s", "UNKNOWN");
	}

	/* print extended opcode */
	switch (eoc) {
	case 0: break;
	CASE_OPX(ZEND_DECLARE_CLASS);
	CASE_OPX(ZEND_DECLARE_FUNCTION);
	CASE_OPX(ZEND_DECLARE_INHERITED_CLASS);
	default: fprintf(F_fp, "(%s)", "UNKNOWN");
	}

	fprintf(F_fp, "\n");
	fflush(F_fp);
#undef CASE_OP
#undef CASE_OPX
}

void
show_elapsed_time(struct timeval *tvstart, struct timeval *tvend)
{
	long sec, usec;

	sec = tvend->tv_sec - tvstart->tv_sec;
	usec = tvend->tv_usec - tvstart->tv_usec;
	fprintf(F_fp, "\telapsed microseconds: %ld\n", sec * 1000000 + usec);
}

#endif	/* #if defined(DEBUG) || defined(TEST_PERFORMANCE) */

/*
 *	compile options.
 */
#undef	BWARE_CACHE_USE_HASH_COPY	/* define this to get core dumps */

#ifndef	ZEND_WIN32
#define	BWARE_CACHE_IS_ZEND_EXTENSION	/* use the zend extension interface */
#undef	BWARE_CACHE_IS_PHP_EXTENSION	/* use the php extension interface */
#endif

#ifdef	ZEND_WIN32
#  ifndef S_ISREG
#    define S_ISREG(mode)   (((mode)&S_IFMT) & S_IFREG)
#  endif
#endif

#include "zend.h"

#ifdef	REPLACE_ZVAL_VALUE
#  define	BWARE_CACHE_IS_404
#  include "zend_language_parser.h"
#else
#  include "zend-parser.h"
#endif

#include "zend_API.h"
#include "zend_globals.h"
#include "zend_compile.h"
#include "zend_list.h"
#include "zend_hash.h"
#include "zend_llist.h"
#include "zend_fast_cache.h"

#ifdef	BWARE_CACHE_IS_ZEND_EXTENSION
#  include "zend_extensions.h"
#endif

#if ZEND_MODULE_API_NO >= 20001222
#  include "config.h"
#else
#  include "php_config.h"
#endif

#if defined(BWARE_CACHE_IS_PHP_EXTENSION) || defined(BWARE_CACHE_IS_ZEND_EXTENSION)
#  include "php.h"
#  include "php_ini.h"
#  include "ext/standard/info.h"
#  include "SAPI.h"
#endif

#ifdef	HAVE_BWARE_CACHE

static ZEND_API zend_op_array *(*bw_saved_zend_compile_file)(zend_file_handle *file_handle, int type CLS_DC);

/*
 * Globals [make them thread safe].
 */
static int bw_enabled = 1;
static int bw_check_stamp = 1;
static int bw_verbose = 0;

/*
 * To cache functions and classes.
 */
typedef struct _bw_fc_entry {
	void	*fc;
	int	(*restore)(struct _bw_fc_entry *);
	void	(*free)(struct _bw_fc_entry *, int);
	void	*htabkey;
	int	htablen;	
	struct	_bw_fc_entry *next;
} bw_fc_entry;

/*
 * A bw_cache_entry caches one PHP file.  Each function/class defined in
 * the file goes in the list of bw_fc_entry.
 */
typedef struct _bw_cache_entry {
	char	*realfilename;
	int	welldone;
	zend_op_array *op_array;
	time_t	when;
	int	nhits;
	int	nreloads;
	struct	_bw_fc_entry *head, *tail;
} bw_cache_entry;
static HashTable *bw_filetable;

typedef struct _bw_cond_entry {
	char	*str;
	int	len;
	char	not;
	struct	_bw_cond_entry	*next;
} bw_cond_entry;
static bw_cond_entry *bw_cond_list;

/*
 * Keep track of defined functions and classes.
 */
static HashTable *bw_functable;
static HashTable *bw_classtable;

/*
 * To pass more than one parameter to hash_apply_argument.
 */
typedef struct _hash_apply_args {
	HashTable	*htable;
	int		persistent;
} hash_apply_args;

/*
 * Prototypes of static functions.
 */
static int store_function(zend_op_array *, bw_cache_entry *, void *, int);
static int mark_function(char *, int);
static int store_class(zend_class_entry *, bw_cache_entry *, void *, int);
static int mark_class(char *key, int keylen);
static void copy_zval(zval *, int);

ZEND_DLEXPORT ZEND_API zend_op_array *bware_cache_compile_file(
	zend_file_handle *file_handle, int type CLS_DC);

static void
debug_printf(char *format, ...)
{
	char output_buf[512];
	va_list args;

	va_start(args, format);
	vsnprintf(output_buf, sizeof(output_buf), format, args);
	va_end(args);

#ifdef ZEND_WIN32
	OutputDebugString(output_buf);
#else
	fprintf(stderr, "%s", output_buf);
#endif
}

static void *
bw_dup(void *p, size_t size, int persistent)
{
	void *dest = pemalloc(size, persistent);

	if (dest)
		memcpy(dest, p, size);
	return dest;
}

#ifdef BWARE_CACHE_USE_HASH_COPY
static void
bw_zval_copy(zval **pzvalue, int persistent)
{
	zval *from, *to;

	from = *pzvalue;
	if (persistent)
		to = bw_dup(from, sizeof(zval), persistent);
	else {
		ALLOC_ZVAL(to);
		memcpy(to, from, sizeof(zval));
	}
	copy_zval(to, persistent);
}

static void
apply_copy_zval_p0(zval **pzvalue)
{
	bw_zval_copy(pzvalue, 0);
}

static void
apply_copy_zval_p1(zval **pzvalue)
{
	bw_zval_copy(pzvalue, 1);
}
#endif	/* #ifdef BWARE_CACHE_USE_HASH_COPY */

static HashTable *
bw_alloc_hash_table(int persistent)
{
	HashTable *t;

	if (persistent)
		t = (HashTable *)pemalloc(sizeof(HashTable), persistent);
	else
		ALLOC_HASHTABLE(t);
	return t;
}

static int
apply_copy_zval(void *ppz, int na, va_list a, zend_hash_key *key)
{
	zval *from, *to;
	hash_apply_args	*args;

	args = va_arg(a, hash_apply_args *);

	from = *(zval **)ppz;
	if (args->persistent)
		to = bw_dup(from, sizeof(zval), args->persistent);
	else {
		ALLOC_ZVAL(to);
		memcpy(to, from, sizeof(zval));
	}
	copy_zval(to, args->persistent);

	if (key->nKeyLength) {
		zend_hash_update(args->htable, key->arKey, key->nKeyLength,
		    (void *)&to, sizeof(zval *), NULL);
	} else {
		zend_hash_index_update(args->htable, key->h, (void *)&to,
		    sizeof(zval *), NULL);
	}
	return 0;
}

static void
bw_zval_hash_copy(HashTable *to_ht, HashTable *from_ht, int persistent)
{
#ifdef BWARE_CACHE_USE_HASH_COPY
	zval *tmp;
	copy_ctor_func_t f;

	f = (copy_ctor_func_t) (persistent ? apply_copy_zval_p1 :
					     apply_copy_zval_p0);

	zend_hash_copy(to_ht, from_ht, f, (void *)&tmp, sizeof(zval *));
#else
	hash_apply_args	args;

	args.htable = to_ht;
	args.persistent = persistent;
	zend_hash_apply_with_arguments(from_ht, apply_copy_zval, 1, &args);
#endif	/* #ifdef BWARE_CACHE_USE_HASH_COPY */
}

static void
copy_zval(zval *zv, int persistent)
{
	zvalue_value *v;
	HashTable *orig_ht;

	v = &zv->value;

	switch (zv->type) {
	case IS_NULL:
	case IS_LONG:
	case IS_DOUBLE:
	case IS_RESOURCE:
	case IS_BOOL:
		break;

	case IS_CONSTANT:
	case IS_STRING:
		if (!v->str.val || v->str.val == empty_string)
			return;
		if (v->str.len == 0) {
			v->str.val = empty_string;
			return;
		}
		v->str.val = bw_dup(v->str.val, v->str.len+1, persistent);
		break;

	case IS_ARRAY:
	case IS_CONSTANT_ARRAY:
		ELS_FETCH();
		if (!v->ht || v->ht == &EG(symbol_table))
			return;
		orig_ht = v->ht;
		v->ht = bw_alloc_hash_table(persistent);
		zend_hash_init(v->ht, 0, NULL, ZVAL_PTR_DTOR, persistent);
		bw_zval_hash_copy(v->ht, orig_ht, persistent);
		break;

	case IS_OBJECT:
		if (!v->obj.properties)
			return;
		orig_ht = v->obj.properties;
		v->obj.properties = bw_alloc_hash_table(persistent);
		zend_hash_init(v->obj.properties, 0, NULL, ZVAL_PTR_DTOR,
		    persistent);
		bw_zval_hash_copy(v->obj.properties, orig_ht, persistent);
		break;
	}
}

/*
 * Each zend_op has two ``znode'' operands and a ``znode'' result.
 * when a ``zend_op'' is copied the znodes that it contains are copied
 * automatically except if they have the type IS_CONST (i.e they
 * are of type ``zval''.)
 *
 * TODO - what if the znode contains a zend_op_array?
 */
static void
copy_znode(znode *z, int persistent)
{
	if (z->op_type == IS_CONST)
		copy_zval(&z->u.constant, persistent);
}

/*
 * ``Deep'' copy of zend_op_array ``from'' to zend_op_array ``to''.
 */
static int
copy_op_array(zend_op_array **pto, zend_op_array *from,
    bw_cache_entry *cache_entry, int persistent)
{
	zend_op_array *to;
	zend_op *opline;
	zend_op *end;
	int welldone;

#ifdef DEBUG
	fprintf(F_fp, "copy_op_array called for function %s (%d oplines)\n",
		from->function_name? from->function_name : "(top)",
		(int)from->last);
	fflush(F_fp);
#endif

	welldone = 1;
	CLS_FETCH();
	if (*pto == NULL)
		*pto = pemalloc(sizeof(zend_op_array), persistent);
	/* TODO - no check ? */
	to = *pto;
	memcpy(to, from, sizeof(zend_op_array));

	if (from->arg_types != NULL) {
		to->arg_types = bw_dup(from->arg_types,
		    (from->arg_types[0]+1) * sizeof(zend_uchar), persistent);
	}

	if (from->function_name != NULL)
		to->function_name = pestrdup(from->function_name, persistent);

	if (from->refcount != NULL) {
		to->refcount = pemalloc(sizeof(*to->refcount), persistent);
		*to->refcount = *from->refcount;
	}

	if (from->opcodes != NULL) {
		to->opcodes = bw_dup(from->opcodes,
		    from->size * sizeof(zend_op), persistent);
	}

	if (from->brk_cont_array != NULL) {
		to->brk_cont_array = bw_dup(from->brk_cont_array,
		    sizeof(zend_brk_cont_element) * from->last_brk_cont,
		    persistent);
	}

	if (from->static_variables) {
		to->static_variables = bw_alloc_hash_table(persistent);
		zend_hash_init(to->static_variables, 2, NULL, ZVAL_PTR_DTOR,
		    persistent);

		bw_zval_hash_copy(to->static_variables, from->static_variables,
		    persistent);
	}

	if (persistent && from->filename != NULL)
		to->filename = pestrdup(from->filename, persistent);

	opline = to->opcodes;
	end = opline + to->last;

	for (; opline != NULL && opline < end; opline++) {
#ifdef DEBUG
		F_put_opcode(opline->opcode, opline->extended_value,
		    from->function_name);
#endif
		/*
		 * Copy the result and the operands
		 */
		copy_znode(&opline->result, persistent);
		copy_znode(&opline->op1, persistent);
		copy_znode(&opline->op2, persistent);
#if 0
		/*
		 * Run-time function declaration
		 */
		if (opline->opcode == ZEND_DECLARE_FUNCTION_OR_CLASS &&
		    opline->extended_value == ZEND_DECLARE_FUNCTION &&
		    persistent) {
			zend_op_array *oa;
			char *val1 = opline->op1.u.constant.value.str.val;
			int len1 = opline->op1.u.constant.value.str.len;

			if (zend_hash_find(CG(function_table),
			    val1, len1, (void *)&oa) != FAILURE) {
				store_function(oa, cache_entry, val1, len1);
			} else {
				if (bw_verbose > 0) {
					debug_printf(
					  "[%d] can't cache %s because of "
					  "run-time reference to function %s\n",
					  getpid(),
					  to->filename,
					  opline->op2.u.constant.value.str.val);
				}
				welldone = 0;
			}
		}
#endif
#if 0
		/*
		 * Run-time class declaration
		 */
		if (opline->opcode == ZEND_DECLARE_FUNCTION_OR_CLASS &&
		    (opline->extended_value == ZEND_DECLARE_CLASS ||
		     opline->extended_value == ZEND_DECLARE_INHERITED_CLASS) &&
		    persistent) {
			zend_class_entry *ce;
			char *val1 = opline->op1.u.constant.value.str.val;
			int len1 = opline->op1.u.constant.value.str.len;

			if (zend_hash_find(CG(class_table), val1, len1,
			    (void **)&ce) != FAILURE) {
			    store_class(ce, cache_entry, val1, len1);
			} else {
				if (bw_verbose > 0) {
					debug_printf(
					  "[%d] can't cache %s because of "
					  "run-time reference to class %s\n",
					  getpid(),
					  to->filename,
					  opline->op2.u.constant.value.str.val);
				}
				welldone = 0;
			}
		}
#endif
	}
	return welldone;
}

/*
 * Copy_op_array function called for each element of a hash table
 */
static int
apply_copy_op_array(zend_op_array *from, int num_args, va_list ag, 
    zend_hash_key *hash_key)
{
	zend_op_array *op_array = NULL;
	hash_apply_args *args = va_arg(ag, hash_apply_args *);

	if (copy_op_array(&op_array, from, NULL, args->persistent))
		zend_hash_update(args->htable,
		    hash_key->arKey, hash_key->nKeyLength, op_array,
		    sizeof(zend_op_array), NULL);
	return 0;
}

static void
free_op_array(zend_op_array *op_array, int persistent)
{
	;
}

/*
 * Copy classes.
 */
static int
copy_class_entry(zend_class_entry **pto, zend_class_entry *from,
    bw_cache_entry *cache_entry, int persistent)
{
	zend_class_entry *to;
	int welldone;
	hash_apply_args args;

	welldone = 1;
	CLS_FETCH();
	if (*pto == NULL)
		*pto = pemalloc(sizeof(zend_class_entry), persistent);
	to = *pto;
	/* TODO - no check ? */
	memcpy(to, from, sizeof(zend_class_entry));
	if (from->name != NULL)
		to->name = bw_dup(from->name, from->name_length+1, persistent);

	if (from->parent != NULL) {
		if (persistent) {
			if (from->parent->name) {
				to->parent = bw_dup(from->parent->name,
				    from->parent->name_length + 1, persistent);
			}
		} else {
			char *parent_name;
			zend_class_entry *parent_class;

			parent_name = (char *)from->parent;
			if (zend_hash_find(CG(class_table), (void *)parent_name,
			    strlen(parent_name)+1,
			    (void **)&parent_class) == SUCCESS) {
				to->parent = parent_class;
			} else {
				debug_printf("[%d] can't restore parent class "
				    "%s of class %s\n",
				    getpid(),
				    parent_name ? parent_name : "?",
				    to->name);
			}
		}
	}

	if (from->refcount != NULL) {
		to->refcount = pemalloc(sizeof(*to->refcount), persistent);
		*to->refcount = *from->refcount;
	}

	memset(&to->function_table, 0, sizeof(HashTable));
	zend_hash_init(&to->function_table, 10, NULL, ZEND_FUNCTION_DTOR,
	    persistent);
	args.htable = &to->function_table;
	args.persistent = persistent;
        zend_hash_apply_with_arguments(&from->function_table,
		    (apply_func_args_t) apply_copy_op_array, 1, &args);

	memset(&to->default_properties, 0, sizeof(HashTable));
	zend_hash_init(&to->default_properties, 10, NULL, ZVAL_PTR_DTOR,
	    persistent);
	bw_zval_hash_copy(&to->default_properties, &from->default_properties,
	    persistent);

	if (from->builtin_functions) {
		debug_printf("[%d] can't cache class %s because of it has "
		    "some builtin_functions ?\n", getpid(), to->name);
		welldone = 0;
	}

	return welldone;
}

static void
free_function(bw_fc_entry *p, int persistent)
{
	;
}

static int
restore_function(bw_fc_entry *p)
{
	zend_op_array *op_array;

	CLS_FETCH();
	op_array = NULL;
	if (copy_op_array(&op_array, (zend_op_array *)p->fc, NULL, 0)) {
		zend_hash_update(CG(function_table), p->htabkey, p->htablen,
		    op_array, sizeof(zend_op_array), NULL);
		return 1;
	}
	return 0;
}

/*
 * Add the function with the specified zend_op_array to the current
 * cache.  A new bw_fc_entry is created for the function and is
 * added to the bw_cache_entry.
 */
static int
store_function(zend_op_array *f, bw_cache_entry *cache_entry, void *htabkey,
    int htablen)
{
	bw_fc_entry *p;
	int result;

#ifdef DEBUG
	fprintf(F_fp, "store_function called: %s\n", f->function_name);
	fprintf(F_fp, "                  key: ");
	F_put_rkey(htabkey, htablen);
	fprintf(F_fp, "\n");
	fflush(F_fp);
#endif
	if (bw_verbose > 1) {
		debug_printf("[%d] store function: %s\n",
			getpid(), f->function_name);
	}
	if (f->type != ZEND_USER_FUNCTION)
		return 0;
	p = (bw_fc_entry *)pemalloc(sizeof(bw_fc_entry), 1);
	p->restore = restore_function;
	p->free = free_function;
	p->htabkey = bw_dup(htabkey, htablen, 1);
	p->htablen = htablen;
	/*
	 * Add the function to the function/class list
	 * of the bw_cache_entry
	 */
	p->next = NULL;
	if (cache_entry->tail == NULL) {
		cache_entry->head = cache_entry->tail = p;
	} else {
		cache_entry->tail->next = p;
		cache_entry->tail = p;
	}
	p->fc = NULL;
	result = copy_op_array((zend_op_array **)&p->fc, f, cache_entry, 1);
	mark_function(htabkey, htablen);
	return result;
}

/*
 * Add the runtime key of the function to bw_functable
 */
static int
mark_function(char *key, int keylen)
{
#ifdef DEBUG
	fprintf(F_fp, "mark_function called: ");
	F_put_rkey(key, keylen);
	fprintf(F_fp, "\n");
	fflush(F_fp);
#endif
	zend_hash_add(bw_functable, key, keylen, NULL, 0, NULL);
	return 0;
}

/*
 * "Apply" version of mark_function()
 */
static int
apply_mark_function(zend_op_array *f, int num_args, va_list ag,
    zend_hash_key *hash_key)
{
	mark_function(hash_key->arKey, hash_key->nKeyLength);
	return 0;
}

/*
 * Called for all elements of CG(function_table).  The function is added to
 * ``bw_functable'' if it doesn't already exist.
 */
static int
apply_store_function(zend_op_array *f, int num_args, va_list ag,
    zend_hash_key *hash_key)
{
	bw_cache_entry *p;
	void *junk;

	p = va_arg(ag, bw_cache_entry *);

#ifdef DEBUG
	fprintf(F_fp, "apply_store_function called:\n");
	fprintf(F_fp, "\tfunction: %s\n", f->function_name);
	fflush(F_fp);
#endif
	if (f->type != ZEND_USER_FUNCTION)
		return 0;
	if (bw_functable == NULL)
		return 0;

	if (zend_hash_find(bw_functable, hash_key->arKey,
	    hash_key->nKeyLength, &junk) == FAILURE)
		store_function(f, p, hash_key->arKey, hash_key->nKeyLength);
	return 0;
}

/*
 * Class handling.
 */
static void
free_class(bw_fc_entry *p, int persistent)
{
	;
}

static int
restore_class(bw_fc_entry *p)
{
	zend_class_entry *ce;

	CLS_FETCH();
	ce = NULL;
	if (copy_class_entry(&ce, (zend_class_entry *)p->fc, NULL, 0)) {
		zend_hash_update(CG(class_table),
		    p->htabkey,
		    p->htablen,
		    ce,
		    sizeof(zend_class_entry),
		    NULL);
		return 1;
	}
	return 0;
}

static int
store_class(zend_class_entry *ce, bw_cache_entry *cache_entry, void *htabkey,
    int htablen)
{
	bw_fc_entry *p;
	int rc;

#ifdef DEBUG
	fprintf(F_fp, "store_class called: %s\n", ce->name);
	fprintf(F_fp, "               key: ");
	F_put_rkey(htabkey, htablen);
	fprintf(F_fp, "\n");
	fflush(F_fp);
#endif

	if (bw_verbose > 1) {
		debug_printf("[%d] store class: %s\n", getpid(), ce->name);
	}
	p = (bw_fc_entry *)pemalloc(sizeof(bw_fc_entry), 1);
	p->restore = restore_class;
	p->free = free_class;
	p->htabkey = bw_dup(htabkey, htablen, 1);
	p->htablen = htablen;
	/*
	 * Add the class to the function/class list
	 * of the bw_cache_entry
	 */
	p->next = NULL;
	if (cache_entry->tail == NULL)
		cache_entry->head = cache_entry->tail = p;
	else {
		cache_entry->tail->next = p;
		cache_entry->tail = p;
	}
	p->fc = NULL;
	rc = copy_class_entry((zend_class_entry **)&p->fc, ce, cache_entry,1);
	mark_class(htabkey, htablen);
	return rc;
}

/*
 * Add the name of the class to bw_classtable
 */
static int
mark_class(char *key, int keylen)
{
#ifdef DEBUG
	fprintf(F_fp, "mark_class called: ");
	F_put_rkey(key, keylen);
	fprintf(F_fp, "\n");
	fflush(F_fp);
#endif
	zend_hash_add(bw_classtable, key, keylen, NULL, 0,NULL);
	return 0;
}

static int
apply_mark_class(zend_class_entry *ce, int num_args, va_list ag,
    zend_hash_key *hash_key)
{
	mark_class(hash_key->arKey, hash_key->nKeyLength);
	return 0;
}

static int
apply_store_class(zend_class_entry *ce, int num_args, va_list ag,
    zend_hash_key *hash_key)
{
	void *junk;
	bw_cache_entry *p;

#ifdef DEBUG
	fprintf(F_fp, "apply_store_class called:\n");
	fprintf(F_fp, "\tclass: %s\n", ce->name);
	fprintf(F_fp, "\tkey  : ");
	F_put_rkey(hash_key->arKey, hash_key->nKeyLength);
	fprintf(F_fp, "\n");
	fflush(F_fp);
#endif
	p = va_arg(ag, bw_cache_entry *);

	if (ce->type != ZEND_USER_CLASS)
		return 0;
	if (bw_classtable == NULL)
		return 0;
	if (zend_hash_find(bw_classtable, hash_key->arKey,
	    hash_key->nKeyLength, (void **)&junk) == FAILURE) {
		if (!store_class(ce, p, hash_key->arKey, hash_key->nKeyLength))
			p->welldone = 0;
	}
#ifdef DEBUG
	else {
		fprintf(F_fp, "apply_store_class: class already present\n");
		fflush(F_fp);
	}
#endif

	return 0;
}

/*
 * Initialize data structures needed for caching:
 *	bw_filetable
 *	bw_functable
 *	bw_classtable
 */
static void
bware_cache_store_start(void)
{
#ifdef DEBUG
	fprintf(F_fp, "bware_cache_store_start called:\n");
	fflush(F_fp);
#endif
	CLS_FETCH();
	if (bw_filetable == NULL) {
		bw_filetable = (HashTable *)malloc(sizeof(*bw_filetable));
		if (bw_filetable == NULL)
			return;
		zend_hash_init(bw_filetable, 100, NULL, NULL, 1);
	}
	if (bw_functable == NULL) {
		bw_functable = (HashTable *)pemalloc(sizeof(*bw_functable),0);
		if (bw_functable == NULL)
			return;
		zend_hash_init(bw_functable, 100, NULL, NULL, 0);
	}
#ifdef DEBUG
	fprintf(F_fp, "\tbware_cache_store_start: marking functions\n");
	fflush(F_fp);
#endif
	zend_hash_apply_with_arguments(CG(function_table),
	    (apply_func_args_t)apply_mark_function, 1, NULL);

	if (bw_classtable == NULL) {
		bw_classtable = (HashTable *)pemalloc(sizeof(*bw_classtable),0);
		if (bw_classtable == NULL)
			return;
		zend_hash_init(bw_classtable, 100, NULL, NULL, 0);
	}
#ifdef DEBUG
	fprintf(F_fp, "\tbware_cache_store_start: marking classes\n");
	fflush(F_fp);
#endif
	zend_hash_apply_with_arguments(CG(class_table),
	    (apply_func_args_t)apply_mark_class, 1, NULL);
#ifdef DEBUG
	fprintf(F_fp, "\tbware_cache_store_start: exiting\n");
	fflush(F_fp);
#endif
}

/*
 * Free data structure needed for caching:
 *	bw_functable
 *      bw_classtable
 */
static void
bware_cache_store_stop(void)
{
	zend_hash_destroy(bw_classtable);
	pefree(bw_classtable, 0);
	bw_classtable = NULL;

	zend_hash_destroy(bw_functable);
	pefree(bw_functable, 0);
	bw_functable = NULL;
}

/*
 * Free a bw_cache_entry
 */
static void
bware_cache_free(bw_cache_entry *p, int persistent)
{
	bw_fc_entry *e;

	free_op_array(p->op_array, persistent);
	p->op_array = NULL;
	for (e = p->head; e != NULL; e = e->next)
		e->free(e, persistent);
	p->head = p->tail = NULL;
}

static void
bware_cache_store(char *realname, zend_op_array *op_array,
    bw_cache_entry *pupdate, time_t when)
{
	bw_cache_entry *p;

#ifdef DEBUG
	fprintf(F_fp, "bware_cache_store called:\n");
	fflush(F_fp);
#endif
	CLS_FETCH();
	if (bw_filetable == NULL)
		return;

	if (pupdate != NULL) {
		p = pupdate;
		p->nreloads++;
	} else {
		p = pemalloc(sizeof(bw_cache_entry), 1);
		if (p == NULL)
			return;
		p->realfilename = pestrdup(realname, 1);
		p->nhits = 0;
		p->nreloads = 0;
	}
	p->when = when;
	p->head = p->tail = NULL;
	p->op_array = NULL;
	/*
	 * copy_op_array can set welldone to 0 and return true.
	 */
	p->welldone = 1;
	if (!copy_op_array(&p->op_array, op_array, p, 1))
		p->welldone = 0;

	/*
	 * Call apply_store_function() for each element of the hash table
	 */
#ifdef DEBUG
	fprintf(F_fp, "\tbware_cache_store: start scanning CG(function_table)\n");
	fflush(F_fp);
#endif
	zend_hash_apply_with_arguments(CG(function_table),
	    (apply_func_args_t)apply_store_function, 1, (void *)p);
#ifdef DEBUG
	fprintf(F_fp,"\tbware_cache_store: stop scanning CG(function_table)\n");
	fflush(F_fp);
#endif

	/*
	 * Call apply_store_class() for each element of the hash table
	 */
#ifdef DEBUG
	fprintf(F_fp, "\tbware_cache_store: start scanning CG(class_table)\n");
	fflush(F_fp);
#endif
	zend_hash_apply_with_arguments(CG(class_table),
	    (apply_func_args_t)apply_store_class, 1, (void *)p);
#ifdef DEBUG
	fprintf(F_fp, "\tbware_cache_store: stop scanning CG(class_table)\n");
	fflush(F_fp);
#endif

	if (pupdate == NULL) {
		zend_hash_add(bw_filetable, p->realfilename,
		    strlen(p->realfilename)+1, p, sizeof(bw_cache_entry), NULL);
	}

	if (!p->welldone)
		bware_cache_free(p, 1);
	else {
		if (bw_verbose > 0) {
			debug_printf("[%d] %s: %s\n", getpid(),
			    (pupdate == NULL) ? "cached" : "re-cached",
			    p->realfilename);
		}
	}
}

static zend_op_array *
bw_cache_restore(bw_cache_entry *cache_entry)
{
	zend_op_array *op_array;

	if (cache_entry->op_array == NULL)
		return NULL;		/* nothing to restore */

	op_array = NULL;
	if (copy_op_array(&op_array, cache_entry->op_array, cache_entry, 0))
		return op_array;
	return NULL;
}

/*
 * Only files matching user specified conditions should be cached.
 *
 * TODO - check the algorithm (fl)
 */
static int
bware_cache_ok_to_cache(char *realname)
{
	bw_cond_entry *p;
	int realnamelen;
	int match;

	if (bw_cond_list == NULL)
		return 1;

	realnamelen = strlen(realname);
	for (p = bw_cond_list; p != NULL; p = p->next) {
		if (p->str == NULL || p->len == 0)
			continue;
		match = 0;
		if (p->str[0] == '*') {
			if (realnamelen > p->len-1)
				match = (strncmp(realname + realnamelen -
				    (p->len-1), p->str+1, p->len-1) == 0);
		} else if (p->str[p->len-1] == '*') {
			match = (strncmp(realname, p->str, p->len-1) == 0);
		} else {
			match = (strcmp(realname, p->str) == 0);
		}
		if (match)
			return !p->not;
	}
	return 0;
}

/*
 * Check if the file on disk is more recent then the copy in the cache.
 * This feature must be enabled by the user.
 */
static int
bware_cache_changed(bw_cache_entry *p)
{
	struct stat buf;

	if (stat(p->realfilename, &buf) != 0)
		return 1;
	if (!S_ISREG(buf.st_mode))
		return 1;
	return buf.st_mtime >= p->when;
}

/*
 * Try to restore a file from the cache.
 */
static zend_op_array *
bware_cache_restore(char *realname, int *try_to_cache, bw_cache_entry **pp)
{
	bw_cache_entry *p;
	bw_fc_entry *e;
	zend_op_array *op_array;

	if (!bware_cache_ok_to_cache(realname)) {
		*try_to_cache = 0;
		return NULL;
	}

	*try_to_cache = 1;
	op_array = NULL;
	if (bw_filetable != NULL &&
	    zend_hash_find(bw_filetable, realname,
			   strlen(realname)+1, (void **)&p) != FAILURE) {
		if (p == NULL || !p->welldone) {
			*try_to_cache = 0;
		} else {
			if (bw_check_stamp && bware_cache_changed(p)) {
				*pp = p;
				bware_cache_free(p, 1);
			} else {
				if (bw_verbose > 0) {
					debug_printf("[%d] cache hit: %s\n",
					    getpid(), p->realfilename);
				}
				*try_to_cache = 0;
				op_array = bw_cache_restore(p);
				if (op_array != NULL)
					for (e = p->head; e!=NULL; e = e->next)
						e->restore(e);
			}
		}
	}
	return op_array;
}

/*
 * Intercept compilation of PHP file.  If we already have the file in
 * our cache, restore it.  Otherwise call the original Zend compilation
 * function and store the compiled zend_op_array in out cache.
 * This function is called again for each PHP file included in the
 * main PHP file.
 */
ZEND_DLEXPORT ZEND_API zend_op_array *
bware_cache_compile_file(zend_file_handle *file_handle, int type CLS_DC)
{
	zend_op_array *t;
	char realname[4096];
	int try_to_cache;
	time_t when;
	bw_cache_entry *p;
#ifdef TEST_PERFORMANCE
	struct timeval tv_start, tv_end;
	static int ncalls = 0;
#endif

#if defined(DEBUG) || defined(TEST_PERFORMANCE)
	fprintf(F_fp, "bware_cache_compile_file called: PID %d\n",
	    (int) getpid());
	fprintf(F_fp, "\tfile \"%s\"\n", file_handle->filename);
	fflush(F_fp);
#endif
#ifdef TEST_PERFORMANCE
	ncalls++;
	gettimeofday(&tv_start, NULL);
#endif

	if (!bw_enabled ||
	    file_handle == NULL ||
	    file_handle->filename == NULL ||
#if ZEND_MODULE_API_NO >= 20010901
	    VCWD_REALPATH(file_handle->filename, realname) == NULL) {
#else
	    V_REALPATH(file_handle->filename, realname) == NULL) {
#endif
		return bw_saved_zend_compile_file(file_handle, type CLS_CC);
	}
	p = NULL;
	try_to_cache = 0;

	t = bware_cache_restore(realname, &try_to_cache, &p);
	if (t != NULL) {
#ifdef DEBUG
		fprintf(F_fp, "\tbware_cache_compile_file: restored\n");
		fflush(F_fp);
#endif
#ifdef TEST_PERFORMANCE
		gettimeofday(&tv_end, NULL);
		fprintf(F_fp, "BWARE cache performance:\n");
		fprintf(F_fp, "\tRestoring: ");
		show_elapsed_time(&tv_start, &tv_end);
		fflush(F_fp);
#endif
		return t;
	}

#ifdef DEBUG
		fprintf(F_fp, "\tbware_cache_compile_file: not in cache\n");
		fflush(F_fp);
#endif

#ifdef TEST_PERFORMANCE
	/*
	 * We want to measure the time of compilation without storage
	 */
	try_to_cache = ncalls > 2;
#endif

	if (try_to_cache) {
		bware_cache_store_start();
		when = time(0);
		t = bw_saved_zend_compile_file(file_handle, type CLS_CC);
		if (t != NULL)
			bware_cache_store(realname, t, p, when);
		bware_cache_store_stop();
	} else
		t = bw_saved_zend_compile_file(file_handle, type CLS_CC);

#ifdef TEST_PERFORMANCE
	gettimeofday(&tv_end, NULL);
	fprintf(F_fp, "BWARE cache performance:\n");
	if (try_to_cache)
		fprintf(F_fp, "\tStoring in cache: ");
	else
		fprintf(F_fp, "\tCompiling only (without storing): ");
	show_elapsed_time(&tv_start, &tv_end);
	fflush(F_fp);
#endif
	return t;
}

#endif	/* #ifdef HAVE_BWARE_CACHE */

/*
 * Begin of dynamic loadable module interfaces.
 * There are two interfaces:
 *	- standard php module,
 *	- zend extension.
 */
#if defined(BWARE_CACHE_IS_PHP_EXTENSION) || defined(BWARE_CACHE_IS_ZEND_EXTENSION)

#if 0
ZEND_BEGIN_MODULE_GLOBALS(bware_cache)
        int global_variable;
ZEND_END_MODULE_GLOBALS(bware_cache)
ZEND_DECLARE_MODULE_GLOBALS(bware_cache)
#  ifdef ZTS
#    define BWARE_CACHEG(v) (bware_cache_globals->v)
#    define BWARE_CACHELS_FETCH() php_bware_cache_globals *bware_cache_globals = ts_resource(bware_cache_globals_id)
#  else
#    define BWARE_CACHEG(v) (bware_cache_globals.v)
#    define BWARE_CACHELS_FETCH()
#  endif	/* ZTS */
#endif	/* 0 */

PHP_INI_MH(bware_cache_change_enable)
{
	if (strcmp(sapi_module.name, "cgi") == 0)
		bw_enabled = 0;
	else
		bw_enabled = atoi(new_value);
	return SUCCESS;
}

PHP_INI_MH(bware_cache_check_stamp)
{
	bw_check_stamp = atoi(new_value);
	return SUCCESS;
}

PHP_INI_MH(bware_cache_log_level)
{
	bw_verbose = atoi(new_value);
	return SUCCESS;
}

PHP_INI_MH(bware_cache_only)
{
	bw_cond_entry *p, *q;
	char *s = new_value;
	char *ss;

	for (p = bw_cond_list; p != NULL; p = q) {
		q = p->next;
		if (p->str)
			pefree(p->str,1);
		pefree(p, 1);
	}
	bw_cond_list = NULL;
	while (*s) {
		p = (bw_cond_entry *)pemalloc(sizeof(bw_cond_entry), 1);
		if (p == NULL)
			break;
		for (; *s == ' ' || *s == '\t'; s++)
			;
		if (*s == 0)
			break;
		if (*s == '!') {
			s++;
			p->not = 1;
		} else {
			p->not = 0;
		}
		ss = s;
		for (; *s && *s != ' ' && *s != '\t'; s++)
			;
		p->len = s-ss;
		p->str = pemalloc(p->len+1, 1);
		memcpy(p->str, ss, p->len);
		p->str[p->len] = 0;
		p->next = NULL;
		if (bw_cond_list == NULL) {
			bw_cond_list = p;
		} else {
			for (q = bw_cond_list; q != NULL; q = q->next)
				if (q->next == NULL) {
					q->next = p;
					break;
				}
		}
	}
	return SUCCESS;
}

PHP_INI_BEGIN()
PHP_INI_ENTRY("bware_cache_enable", "0", PHP_INI_ALL, bware_cache_change_enable)
PHP_INI_ENTRY("bware_cache_check_stamp", "0", PHP_INI_ALL, bware_cache_check_stamp)
PHP_INI_ENTRY("bware_cache_log_level", "0", PHP_INI_ALL, bware_cache_log_level)
PHP_INI_ENTRY("bware_cache_only", "", PHP_INI_ALL, bware_cache_only)
PHP_INI_END()

#endif	/* #if defined(BWARE_CACHE_IS_PHP_EXTENSION) ... */

#if !defined(BWARE_CACHE_IS_PHP_EXTENSION) && !defined(BWARE_CACHE_IS_ZEND_EXTENSION)
#  define phpext_bware_cache_ptr NULL
#else

extern zend_module_entry bware_cache_module_entry;
#  define phpext_bware_cache_ptr &bware_cache_module_entry

#  ifdef PHP_WIN32
#    define PHP_BWARE_CACHE_API __declspec(dllexport)
#  else
#    define PHP_BWARE_CACHE_API
#  endif

PHP_MINIT_FUNCTION(bware_cache);
PHP_MSHUTDOWN_FUNCTION(bware_cache);
PHP_MINFO_FUNCTION(bware_cache);

PHP_FUNCTION(confirm_bware_cache_compiled);

/* True global resources - no need for thread safety here */
static int le_bware_cache;

function_entry bware_cache_functions[] = {
	PHP_FE(confirm_bware_cache_compiled, NULL)
	{NULL, NULL, NULL}
};

zend_module_entry bware_cache_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
	STANDARD_MODULE_HEADER,
#endif
	"afterburner",
	bware_cache_functions,
	PHP_MINIT(bware_cache),
	PHP_MSHUTDOWN(bware_cache),
	NULL,
	NULL,
	PHP_MINFO(bware_cache),
#if ZEND_MODULE_API_NO >= 20010901
	"0.14",          /* extension version number (string) */
#endif
	STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_BWARE_CACHE
ZEND_GET_MODULE(bware_cache)
#endif

PHP_MINIT_FUNCTION(bware_cache)
{
	REGISTER_INI_ENTRIES();
	bw_saved_zend_compile_file = zend_compile_file;
	zend_compile_file = bware_cache_compile_file;
	return SUCCESS;
}

PHP_MSHUTDOWN_FUNCTION(bware_cache)
{
	UNREGISTER_INI_ENTRIES();
	zend_compile_file = bw_saved_zend_compile_file;
	return SUCCESS;
}

PHP_MINFO_FUNCTION(bware_cache)
{
	php_info_print_table_start();
	php_info_print_table_header(2, "Afterburner support", "enabled");
	php_info_print_table_row(2, "cvsid", "$Header: /usr/local/bware/CVS/wapzilla/src/bware_cache/bware_cache.c,v 1.19.4.11 2002/02/13 15:13:41 federico Exp $");
	php_info_print_table_end();

	DISPLAY_INI_ENTRIES();
}

/* {{{ proto string confirm_bware_cache_compiled(string arg)
   Return a string to confirm that the module is compiled in */
PHP_FUNCTION(confirm_bware_cache_compiled)
{
	zval **arg;
	int len;
	char string[256];

	if (ZEND_NUM_ARGS() != 1 ||
	    zend_get_parameters_ex(1, &arg) == FAILURE) {
		WRONG_PARAM_COUNT;
	}

	convert_to_string_ex(arg);

	len = snprintf(string, sizeof(string),
	    "Congratulations, you have successfully modified "
	    "ext/bware_cache/config.m4, module %s is compiled into PHP",
	    Z_STRVAL_PP(arg));
	if (len == -1)
		len = sizeof(string);
	RETURN_STRINGL(string, len, 1);
}
/* }}} */

#endif	/* #if !defined(BWARE_CACHE_IS_PHP_EXTENSION) ... */

#ifdef BWARE_CACHE_IS_ZEND_EXTENSION

ZEND_DLEXPORT int
bware_cache_zend_startup(zend_extension *extension)
{
#if defined(DEBUG) || defined(TEST_PERFORMANCE)
	F_fp = fopen(DEBUG_LOGFILE, "a");
	fprintf(F_fp, "\n");
	fprintf(F_fp, "=======================================\n");
	fprintf(F_fp, "bware_cache_zend_startup: CACHE STARTED\n");
	fprintf(F_fp, "=======================================\n");
	fflush(F_fp);
#endif
	return zend_startup_module(&bware_cache_module_entry);
}

ZEND_DLEXPORT void
bware_cache_zend_shutdown(zend_extension *extension)
{
#if defined(DEBUG) || defined(TEST_PERFORMANCE)
	fprintf(F_fp, "========================================\n");
	fprintf(F_fp, "bware_cache_zend_shutdown: CACHE STOPPED\n");
	fprintf(F_fp, "========================================\n");
	fprintf(F_fp, "\n");
	fclose(F_fp);
	F_fp = NULL;
#endif
	;
}

#ifndef	ZEND_EXT_API
#  define ZEND_EXT_API		ZEND_DLEXPORT
#endif
ZEND_EXTENSION();

ZEND_DLEXPORT zend_extension zend_extension_entry = {
	"Afterburner*Cache",
	"0.14",
	"Bware Technologies",
	"http://www.bware.it",
	"Copyright (c) 2000-2002 Bware Technologies",
	bware_cache_zend_startup,
	bware_cache_zend_shutdown,
	NULL,		/* void (*activate)() */
	NULL,		/* void (*deactivate)() */
	NULL,		/* void (*message_handle)(int message, void *arg) */
	NULL,		/* void (*op_array_handler)(zend_op_array *o_a); */
	NULL,		/* void (*statement_handler)(zend_op_array *o_a); */
	NULL,		/* void (*fcall_begin_handler)(zend_op_array *o_a); */
	NULL,		/* void (*fcall_end_handler)(zend_op_array *o_a); */
	NULL,		/* void (*op_array_ctor)(zend_op_array *o_a); */
	NULL,		/* void (*op_array_dtor)(zend_op_array *o_a); */
	STANDARD_ZEND_EXTENSION_PROPERTIES
};

#endif	/* #ifdef BWARE_CACHE_IS_ZEND_EXTENSION */
