/*
 * The contents of this file are subject to the AOLserver Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://aolserver.com/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is AOLserver Code and related documentation
 * distributed by AOL.
 * 
 * The Initial Developer of the Original Code is America Online,
 * Inc. Portions created by AOL are Copyright (C) 1999 America Online,
 * Inc. All Rights Reserved.
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU General Public License (the "GPL"), in which case the
 * provisions of GPL are applicable instead of those above.  If you wish
 * to allow use of your version of this file only under the terms of the
 * GPL and not to allow others to use your version of this file under the
 * License, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the GPL.
 * If you do not delete the provisions above, a recipient may use your
 * version of this file under either the License or the GPL.
 */

/* 
 * adpeval.c --
 *
 *	ADP string and file eval.
 */

static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/aolserver/nsd/adpeval.c,v 1.49 2006/06/28 17:41:06 shmooved Exp $, compiled: " __DATE__ " " __TIME__;

#include "nsd.h"

/*
 * The following structure defines a cached ADP page result.  A cached
 * object is created by executing the non-cached code and saving the
 * resulting output which may include embedded non-cached components
 * (see NsTclAdpIncludeObjCmd for details).
 */

typedef struct AdpCache {
    int	      	   refcnt;	/* Current interps using cached results. */ 
    Ns_Time	   expires;	/* Expiration time of cached results. */
    AdpCode	   code;	/* ADP code for cached result. */
} AdpCache;

/*
 * The following structure defines a shared page in the ADP cache.  The
 * size of the object is extended to include the filename bytes.
 */

typedef struct Page {
    NsServer	  *servPtr;	/* Page server context (reg tags, etc.) */
    Tcl_HashEntry *hPtr;	/* Entry in shared table of all pages. */
    time_t    	   mtime;	/* Original modify time of file. */
    off_t     	   size;	/* Original size of file. */
    int		   flags;	/* Flags used on last compile, e.g., SAFE. */
    int	      	   refcnt;	/* Refcnt of current interps using page. */
    int		   evals;	/* Count of page evaluations. */
    int		   locked;	/* Page locked for cache update. */
    int		   cacheGen;	/* Cache generation id. */
    AdpCache	  *cachePtr;	/* Cached output. */
    AdpCode	   code;	/* ADP code blocks. */
    char	   file[1];	/* First known filename for stats reporting. */
} Page;

/*
 * The following structure holds per-interp script byte codes.  The
 * size of the object is extended based on the number of script objects.
 */

typedef struct Objs {
    int nobjs;			/* Number of scripts objects. */
    Tcl_Obj *objs[1];		/* Scripts to be compiled and reused. */
} Objs;

/*
 * The following structure defines a per-interp page entry with
 * a pointer to the shared Page and private Objs for cached and
 * non-cached page results.
 */

typedef struct InterpPage {
    Page     *pagePtr;		/* Pointer to shared page text. */
    Objs     *objs;		/* Non-cache ADP code script. */
    int	      cacheGen;		/* Cache generation id. */
    Objs     *cacheObjs;	/* Cache results ADP code scripts. */
} InterpPage;

/*
 * Local functions defined in this file.
 */

static Page *ParseFile(NsInterp *itPtr, char *file, struct stat *stPtr,
		       int flags);
static int AdpEval(NsInterp *itPtr, int objc, Tcl_Obj *objv[], int flags,
		   char *resvar);
static int AdpExec(NsInterp *itPtr, int objc, Tcl_Obj *objv[], char *file,
		   AdpCode *codePtr, Objs *objsPtr, Tcl_DString *outputPtr);
static int AdpSource(NsInterp *itPtr, int objc, Tcl_Obj *objv[], char *file,
		     Ns_Time *ttlPtr, int flags, Tcl_DString *outputPtr);
static int AdpDebug(NsInterp *itPtr, char *ptr, int len, int nscript);
static void DecrCache(AdpCache *cachePtr);
static Objs *AllocObjs(int nobjs);
static void FreeObjs(Objs *objsPtr);
static void AdpTrace(NsInterp *itPtr, char *ptr, int len);
static Ns_Callback FreeInterpPage;


/*
 *----------------------------------------------------------------------
 *
 * NsAdpEval, NsAdpSource --
 *
 *	Evaluate an ADP string or file and return the output
 *	as the interp result.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	Variable named by resvar, if any, is updated with results of
 *	Tcl interp before being replaced with ADP output.
 *
 *----------------------------------------------------------------------
 */

int
NsAdpEval(NsInterp *itPtr, int objc, Tcl_Obj *objv[], int flags, char *resvar)
{
    return AdpEval(itPtr, objc, objv, flags, resvar);
}

int
NsAdpSource(NsInterp *itPtr, int objc, Tcl_Obj *objv[], int flags, char *resvar)
{
    return AdpEval(itPtr, objc, objv, (flags | ADP_EVAL_FILE), resvar);
}

static int
AdpEval(NsInterp *itPtr, int objc, Tcl_Obj *objv[], int flags, char *resvar)
{
    Tcl_Interp	     *interp = itPtr->interp;
    AdpCode	      code;
    Tcl_DString       output;
    Tcl_Obj	     *objPtr;
    int               result;
    char	     *obj0;
    
    /*
     * If the ADP object is a file, simply source it. Otherwise, parse
     * the script as a temporary ADP code object and execute it directly.
     */

    Tcl_DStringInit(&output);
    obj0 = Tcl_GetString(objv[0]);
    if (flags & ADP_EVAL_FILE) {
    	result = AdpSource(itPtr, objc, objv, obj0, NULL, flags, &output);
    } else {
    	NsAdpParse(&code, itPtr->servPtr, obj0, flags);
    	result = AdpExec(itPtr, objc, objv, NULL, &code, NULL, &output);
    	NsAdpFreeCode(&code);
    }

    /*
     * Set the interp result with the ADP output, saving the last interp
     * result first if requested.
     */

    if (result == TCL_OK) {
	if (resvar != NULL) {
	    objPtr = Tcl_GetObjResult(interp);
	    if (Tcl_SetVar2Ex(interp, resvar, NULL, objPtr, TCL_LEAVE_ERR_MSG)
			      == NULL) {
		result = TCL_ERROR;
	    }
	}
	if (result == TCL_OK) {
	    objPtr = Tcl_NewStringObj(output.string, output.length);
	    Tcl_SetObjResult(interp, objPtr);
	}
    }
    Tcl_DStringFree(&output);
    return result;
}


/*
 *----------------------------------------------------------------------
 *
 * NsAdpInclude --
 *
 *	Evaluate an ADP file, utilizing per-thread byte-code pages.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	Output is either left in current ADP buffer.
 *
 *----------------------------------------------------------------------
 */

int
NsAdpInclude(NsInterp *itPtr, int objc, Tcl_Obj *objv[], char *file,
		Ns_Time *ttlPtr)
{
    Ns_DString *outputPtr;
    int flags = itPtr->adp.flags;

    /*
     * If an ADP execution is already active, use the current output
     * buffer. Otherwise, use the top-level buffer in the ADP struct.
     */

    if (itPtr->adp.framePtr != NULL) {
	outputPtr = itPtr->adp.framePtr->outputPtr;
    } else {
	outputPtr = &itPtr->adp.output;
    }
    return AdpSource(itPtr, objc, objv, file, ttlPtr, flags, outputPtr);
}


/*
 *----------------------------------------------------------------------
 *
 * NsAdpInit, NsAdpFree --
 *
 *	Initialize or free the NsInterp ADP data structures.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *  	None.
 *
 *----------------------------------------------------------------------
 */

void
NsAdpInit(NsInterp *itPtr)
{
    Tcl_DStringInit(&itPtr->adp.output);
    NsAdpReset(itPtr);
}

void
NsAdpFree(NsInterp *itPtr)
{
    if (itPtr->adp.cache != NULL) {
	Ns_CacheDestroy(itPtr->adp.cache);
    }
    Tcl_DStringFree(&itPtr->adp.output);
}


/*
 *----------------------------------------------------------------------
 *
 * NsAdpReset --
 *
 *	Reset the NsInterp ADP data structures for the next
 *	execution request.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
NsAdpReset(NsInterp *itPtr)
{
    itPtr->adp.exception = ADP_OK;
    itPtr->adp.debugLevel = 0;
    itPtr->adp.debugInit = 0;
    itPtr->adp.debugFile = NULL;
    itPtr->adp.chan = NULL;
    itPtr->adp.conn = NULL;
    itPtr->adp.bufsize = itPtr->servPtr->adp.bufsize;
    itPtr->adp.flags = itPtr->servPtr->adp.flags;
    Tcl_DStringTrunc(&itPtr->adp.output, 0);
}

/*
 *----------------------------------------------------------------------
 *
 * AdpSource --
 *
 *	Execute ADP code in a file with results returned in given
 *	dstring.
 *
 * Results:
 *	TCL_ERROR if the file could not be parsed, result of
 *	AdpExec otherwise.
 *
 * Side effects:
 *	Page text and ADP code results may be cached up to given time
 *	limit, if any.
 *
 *----------------------------------------------------------------------
 */

static int
AdpSource(NsInterp *itPtr, int objc, Tcl_Obj *objv[], char *file,
       Ns_Time *ttlPtr, int flags, Tcl_DString *outputPtr)
{
    NsServer  *servPtr = itPtr->servPtr;
    Tcl_Interp *interp = itPtr->interp;
    Tcl_HashEntry *hPtr;
    struct stat st;
    Ns_DString tmp, path;
    InterpPage *ipagePtr;
    Page *pagePtr, *oldPagePtr;
    AdpCache *cachePtr;
    AdpCode *codePtr;
    Ns_Time now;
    Ns_Entry *ePtr;
    Objs *objsPtr;
    int new, cacheGen;
    char *p, *key;
    FileKey ukey;
    int result;

    ipagePtr = NULL;
    pagePtr = NULL; 
    result = TCL_ERROR;   /* assume error until accomplished success */
    Ns_DStringInit(&tmp);
    Ns_DStringInit(&path);
    key = (char *) &ukey;
    
    /*
     * Construct the full, normalized path to the ADP file.
     */

    if (!Ns_PathIsAbsolute(file)) {
	file = Ns_MakePath(&tmp, itPtr->adp.cwd, file, NULL);
    }
    file = Ns_NormalizePath(&path, file);
    Ns_DStringTrunc(&tmp, 0);

    /*
     * Check for TclPro debugging.
     */

    if (itPtr->adp.debugLevel > 0) {
	++itPtr->adp.debugLevel;
    } else if ((flags & ADP_DEBUG) &&
	itPtr->adp.debugFile != NULL &&
	(p = strrchr(file, '/')) != NULL &&
	Tcl_StringMatch(p+1, itPtr->adp.debugFile)) {
	Ns_Set *hdrs;
	char *host, *port, *procs;

	hdrs = Ns_ConnGetQuery(itPtr->conn);
	host = Ns_SetIGet(hdrs, "dhost");
	port = Ns_SetIGet(hdrs, "dport");
	procs = Ns_SetIGet(hdrs, "dprocs");
	if (NsAdpDebug(itPtr, host, port, procs) != TCL_OK) {
	    Ns_ConnReturnNotice(itPtr->conn, 200, "Debug Init Failed",
				(char *) Tcl_GetStringResult(interp));
	    itPtr->adp.exception = ADP_ABORT;
	    goto done;
	}
    }

    if (itPtr->adp.cache == NULL) {
	Ns_DStringPrintf(&tmp, "nsadp:%p", itPtr);
#ifdef _WIN32
	itPtr->adp.cache = Ns_CacheCreateSz(tmp.string, TCL_STRING_KEYS,
				itPtr->servPtr->adp.cachesize, FreeInterpPage);
#else
	itPtr->adp.cache = Ns_CacheCreateSz(tmp.string, FILE_KEYS,
				itPtr->servPtr->adp.cachesize, FreeInterpPage);
#endif
	Ns_DStringTrunc(&tmp, 0);
    }

    /*
     * Verify the file is an existing, ordinary file and get page code.
     */

    if (stat(file, &st) != 0) {
	Tcl_AppendResult(interp, "could not stat \"",
	    file, "\": ", Tcl_PosixError(interp), NULL);
    } else if (S_ISREG(st.st_mode) == 0) {
    	Tcl_AppendResult(interp, "not an ordinary file: ", file, NULL);
    } else {
	/*
	 * Check for valid code in interp page cache.
	 */
	 
#ifdef _WIN32
	key = file;
#else
	ukey.dev = st.st_dev;
	ukey.ino = st.st_ino;
#endif
        ePtr = Ns_CacheFindEntry(itPtr->adp.cache, key);
	if (ePtr != NULL) {
    	    ipagePtr = Ns_CacheGetValue(ePtr);
    	    if (ipagePtr->pagePtr->mtime != st.st_mtime
			|| ipagePtr->pagePtr->size != st.st_size
			|| ipagePtr->pagePtr->flags != flags) {
		Ns_CacheFlushEntry(ePtr);
		ipagePtr = NULL;
	    }
	}
	if (ipagePtr == NULL) {
	    /*
	     * Find or create valid page in server table.
	     */
	     
    	    Ns_MutexLock(&servPtr->adp.pagelock);
	    hPtr = Tcl_CreateHashEntry(&servPtr->adp.pages, key, &new);
	    while (!new && (pagePtr = Tcl_GetHashValue(hPtr)) == NULL) {
		/* NB: Wait for other thread to read/parse page. */
		Ns_CondWait(&servPtr->adp.pagecond, &servPtr->adp.pagelock);
		hPtr = Tcl_CreateHashEntry(&servPtr->adp.pages, key, &new);
	    }
	    if (!new && (pagePtr->mtime != st.st_mtime
			|| pagePtr->size != st.st_size
			|| pagePtr->flags != flags)) {
		/* NB: Clear entry to indicate read/parse in progress. */
		Tcl_SetHashValue(hPtr, NULL);
		pagePtr->hPtr = NULL;
		new = 1;
	    }
	    if (new) {
		Ns_MutexUnlock(&servPtr->adp.pagelock);
		pagePtr = ParseFile(itPtr, file, &st, flags);
		Ns_MutexLock(&servPtr->adp.pagelock);
		if (pagePtr == NULL) {
		    Tcl_DeleteHashEntry(hPtr);
		} else {
#ifdef _WIN32
            	    if (pagePtr->mtime != st.st_mtime
				|| pagePtr->size != st.st_size)
#else
            	    if (ukey.dev != st.st_dev || ukey.ino != st.st_ino)
#endif
		    {
			/* NB: File changed between stat above and ParseFile. */
		    	Tcl_DeleteHashEntry(hPtr);
#ifndef _WIN32
                	ukey.dev = st.st_dev;
		    	ukey.ino = st.st_ino;
#endif
		    	hPtr = Tcl_CreateHashEntry(&servPtr->adp.pages, key,
						   &new);
		    	if (!new) {
			    oldPagePtr = Tcl_GetHashValue(hPtr);
			    oldPagePtr->hPtr = NULL;
		    	}
		    }
		    pagePtr->hPtr = hPtr;
		    Tcl_SetHashValue(hPtr, pagePtr);
		}
		Ns_CondBroadcast(&servPtr->adp.pagecond);
	    }
	    if (pagePtr != NULL) {
	    	++pagePtr->refcnt;
	    }
	    Ns_MutexUnlock(&servPtr->adp.pagelock);
	    if (pagePtr != NULL) {
	    	ipagePtr = ns_malloc(sizeof(InterpPage));
		ipagePtr->pagePtr = pagePtr;
		ipagePtr->cacheGen = 0;
		ipagePtr->objs = AllocObjs(pagePtr->code.nscripts);
		ipagePtr->cacheObjs = NULL;
        	ePtr = Ns_CacheCreateEntry(itPtr->adp.cache, key, &new);
		if (!new) {
		    Ns_CacheUnsetValue(ePtr);
		}
        	Ns_CacheSetValueSz(ePtr, ipagePtr,
				   (size_t) ipagePtr->pagePtr->size);
	    }
	}
    }
    
    /*
     * If valid page was found, evaluate it in a new call frame.
     */
         
    if (ipagePtr != NULL) {
	pagePtr = ipagePtr->pagePtr;
	if (ttlPtr == NULL || (flags & ADP_NOCACHE)) {
	   cachePtr = NULL;
	} else {
	    Ns_MutexLock(&servPtr->adp.pagelock);

	    /*
	     * First, wait for an initial cache if already executing.
	     */

	    while ((cachePtr = pagePtr->cachePtr) == NULL && pagePtr->locked) {
		Ns_CondWait(&servPtr->adp.pagecond, &servPtr->adp.pagelock);
	    }

	    /*
	     * Next, if a cache exists and isn't locked, check expiration.
	     */

	    if (cachePtr != NULL && !pagePtr->locked) {
		Ns_GetTime(&now);
		if (Ns_DiffTime(&cachePtr->expires, &now, NULL) < 0) {
		    pagePtr->locked = 1;
		    cachePtr = NULL;
		}
	    }

	    /*
	     * Create the cached page if necessary.
	     */

	    if (cachePtr == NULL) {
	    	Ns_MutexUnlock(&servPtr->adp.pagelock);
		codePtr = &pagePtr->code;
		++itPtr->adp.refresh;
		result = AdpExec(itPtr, objc, objv, file, codePtr,
				 ipagePtr->objs, &tmp);
		--itPtr->adp.refresh;
		if (result == TCL_OK) {
		    cachePtr = ns_malloc(sizeof(AdpCache));
		    NsAdpParse(&cachePtr->code, itPtr->servPtr, tmp.string,
			       flags);
		    Ns_GetTime(&cachePtr->expires);
		    Ns_IncrTime(&cachePtr->expires, ttlPtr->sec, ttlPtr->usec);
	    	    cachePtr->refcnt = 1;
		}
		Ns_DStringTrunc(&tmp, 0);
	    	Ns_MutexLock(&servPtr->adp.pagelock);
		if (cachePtr != NULL) {
		    if (pagePtr->cachePtr != NULL) {
		    	DecrCache(pagePtr->cachePtr);
		    }
		    ++pagePtr->cacheGen;
		    pagePtr->cachePtr = cachePtr;
		}
		pagePtr->locked = 0;
		Ns_CondBroadcast(&servPtr->adp.pagecond);
	    }
	    cacheGen = pagePtr->cacheGen;
	    ++cachePtr->refcnt;
	    Ns_MutexUnlock(&servPtr->adp.pagelock);
	}
	if (cachePtr == NULL) {
	   codePtr = &pagePtr->code;
	   objsPtr = ipagePtr->objs;
	} else {
	    codePtr = &cachePtr->code;
	    if (ipagePtr->cacheObjs != NULL && cacheGen != ipagePtr->cacheGen) {
		FreeObjs(ipagePtr->cacheObjs);
		ipagePtr->cacheObjs = NULL;
	    }
	    if (ipagePtr->cacheObjs == NULL) {
		ipagePtr->cacheObjs = AllocObjs(AdpCodeScripts(codePtr));
		ipagePtr->cacheGen = cacheGen;
	    }
	    objsPtr = ipagePtr->cacheObjs;
	}
	result = AdpExec(itPtr, objc, objv, file, codePtr, objsPtr, outputPtr);
	Ns_MutexLock(&servPtr->adp.pagelock);
	++ipagePtr->pagePtr->evals;
	if (cachePtr != NULL) {
	    DecrCache(cachePtr);
	}
	Ns_MutexUnlock(&servPtr->adp.pagelock);
    }
    if (itPtr->adp.debugLevel > 0) {
	--itPtr->adp.debugLevel;
    }

done:
    Ns_DStringFree(&path);
    Ns_DStringFree(&tmp);
    return result;
}


/*
 *----------------------------------------------------------------------
 *
 * NsAdpDebug --
 *
 *	Initialize the debugger by calling the debug init proc with
 *	the hostname and port of the debugger and a pattern of procs
 *	to auto-instrument.
 *
 * Results:
 *	TCL_OK if debugger initialized, TCL_ERROR otherwise.
 *
 * Side effects:
 *	Interp is marked for delete on next deallocation.
 *
 *----------------------------------------------------------------------
 */

int
NsAdpDebug(NsInterp *itPtr, char *host, char *port, char *procs)
{
    Tcl_Interp *interp = itPtr->interp;
    Tcl_DString ds;
    int code;

    code = TCL_OK;
    if (!itPtr->adp.debugInit) {
	itPtr->delete = 1;
	Tcl_DStringInit(&ds);
	Tcl_DStringAppendElement(&ds, itPtr->servPtr->adp.debuginit);
	Tcl_DStringAppendElement(&ds, procs ? procs : "");
	Tcl_DStringAppendElement(&ds, host ? host : "");
	Tcl_DStringAppendElement(&ds, port ? port : "");
	code = Tcl_EvalEx(interp, ds.string, ds.length, 0);
        Tcl_DStringFree(&ds);
	if (code != TCL_OK) {
	    NsAdpLogError(itPtr);
	    return TCL_ERROR;
	}

	/*
	 * Link the ADP output buffer result to a global variable
	 * which can be monitored with a variable watch.
	 */

	if (Tcl_LinkVar(interp, "ns_adp_output",
			(char *) &itPtr->adp.output.string,
		TCL_LINK_STRING | TCL_LINK_READ_ONLY) != TCL_OK) {
	    NsAdpLogError(itPtr);
	}

	itPtr->adp.debugInit = 1;
	itPtr->adp.debugLevel = 1;
    }
    return code;
}


/*
 *----------------------------------------------------------------------
 *
 * NsTclAdpStatsCmd --
 *
 *	Implement the ns_adp_stats command to return stats on cached
 *	ADP pages.
 *
 * Results:
 *	Standard Tcl result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
NsTclAdpStatsCmd(ClientData arg, Tcl_Interp *interp, int argc, char **argv)
{
    NsInterp *itPtr = arg;
    NsServer *servPtr = itPtr->servPtr;
    FileKey *keyPtr;
    char buf[200];
    Tcl_HashSearch search;
    Tcl_HashEntry *hPtr;
    Page *pagePtr;

    Ns_MutexLock(&servPtr->adp.pagelock);
    hPtr = Tcl_FirstHashEntry(&servPtr->adp.pages, &search);
    while (hPtr != NULL) {
	pagePtr = Tcl_GetHashValue(hPtr);
	keyPtr = (FileKey *) Tcl_GetHashKey(&servPtr->adp.pages, hPtr);
	Tcl_AppendElement(interp, pagePtr->file);
	sprintf(buf, "dev %ld ino %ld mtime %ld refcnt %d evals %d "
		     "size %ld blocks %d scripts %d",
		(long) keyPtr->dev, (long) keyPtr->ino, (long) pagePtr->mtime,
		pagePtr->refcnt, pagePtr->evals, (long) pagePtr->size,
		pagePtr->code.nblocks, pagePtr->code.nscripts);
	Tcl_AppendElement(interp, buf);
	hPtr = Tcl_NextHashEntry(&search);
    }
    Ns_MutexUnlock(&servPtr->adp.pagelock);
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * ParseFile --
 *
 *	Read and parse text from a file.  The code is complicated
 *	somewhat to account for changing files.
 *
 * Results:
 *	Pointer to new Page structure or NULL on error.
 *
 * Side effects:
 *	Error message will be left in interp on failure.
 *
 *----------------------------------------------------------------------
 */

static Page *
ParseFile(NsInterp *itPtr, char *file, struct stat *stPtr, int flags)
{
    Tcl_Interp *interp = itPtr->interp;
    Tcl_Encoding encoding;
    Tcl_DString utf;
    char *page, *buf;
    int fd, n, trys;
    size_t size;
    Page *pagePtr;
    
    fd = open(file, O_RDONLY | O_BINARY);
    if (fd < 0) {
	Tcl_AppendResult(interp, "could not open \"",
	    file, "\": ", Tcl_PosixError(interp), NULL);
	return NULL;
    }

    pagePtr = NULL;
    buf = NULL;
    trys = 0;
    do {
	/*
	 * fstat the open file to ensure it has not changed or been
	 * replaced since the original stat.
	 */

	if (fstat(fd, stPtr) != 0) {
	    Tcl_AppendResult(interp, "could not fstat \"", file,
	   	"\": ", Tcl_PosixError(interp), NULL);
	    goto done;
	}
    	size = stPtr->st_size;
    	buf = ns_realloc(buf, size + 1);

	/*
	 * Attempt to read +1 byte to catch the file growing.
	 */

    	n = read(fd, buf, size + 1);
    	if (n < 0) {
	    Tcl_AppendResult(interp, "could not read \"", file,
	    	"\": ", Tcl_PosixError(interp), NULL);
	    goto done;
	}
	if (n != size) {
	    /*
	     * File is not expected size, rewind and fstat/read again.
	     */
	
	    if (lseek(fd, (off_t) 0, SEEK_SET) != 0) {
	    	Tcl_AppendResult(interp, "could not lseek \"", file,
	    	    "\": ", Tcl_PosixError(interp), NULL);
	        goto done;
	    }
	    Ns_ThreadYield();
	}
    } while (n != size && ++trys < 10);

    if (n != size) {
	Tcl_AppendResult(interp, "inconsistant file: ", file, NULL);
    } else {
	buf[n] = '\0';
	Tcl_DStringInit(&utf);
	encoding = Ns_GetFileEncoding(file);
	if (encoding == NULL) {
	    page = buf;
	} else {
	    Tcl_ExternalToUtfDString(encoding, buf, n, &utf);
	    page = utf.string;
	}
	pagePtr = ns_malloc(sizeof(Page) + strlen(file));
	strcpy(pagePtr->file, file);
	pagePtr->servPtr = itPtr->servPtr;
	pagePtr->flags = flags;
	pagePtr->refcnt = 0;
	pagePtr->evals = 0;
	pagePtr->locked = 0;
	pagePtr->cacheGen = 0;
	pagePtr->cachePtr = NULL;
	pagePtr->mtime = stPtr->st_mtime;
	pagePtr->size = stPtr->st_size;
	NsAdpParse(&pagePtr->code, itPtr->servPtr, page, flags);
	Tcl_DStringFree(&utf);
    }

done:
    ns_free(buf);
    close(fd);
    return pagePtr;
}


/*
 *----------------------------------------------------------------------
 *
 * NsAdpLogError --
 *
 *	Log an ADP error, possibly invoking the log handling ADP
 *	file if configured.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Depends on log handler.
 *
 *----------------------------------------------------------------------
 */

void
NsAdpLogError(NsInterp *itPtr)
{
    Tcl_Interp *interp = itPtr->interp;
    Ns_Conn *conn = itPtr->conn; 
    Ns_DString ds;
    Tcl_Obj *objv[2];
    AdpFrame *framePtr;
    char *adp, *inc, *dot, *err;
    int i, len;
    
    framePtr = itPtr->adp.framePtr;
    Ns_DStringInit(&ds);

    if (framePtr != NULL) {
        Ns_DStringPrintf(&ds, "\n    at line %d of ",
                        framePtr->line + interp->errorLine);
    }
    inc = "";
    while (framePtr != NULL) {
	if (framePtr->file != NULL) {
	    Ns_DStringPrintf(&ds, "%sadp file \"%s\"", inc, framePtr->file);
	    if (framePtr->ident != NULL) {
		Ns_DStringPrintf(&ds, " {%s}", Tcl_GetString(framePtr->ident));
	    }
	} else {
	    adp = Tcl_GetStringFromObj(framePtr->objv[0], &len);
	    dot = "";
	    if (len > 150) {
		len = 150;
		dot = "...";
	    }
	    while ((adp[len] & 0xC0) == 0x80) {
		/* NB: Avoid truncating multi-byte UTF-8 character. */
		len--;
		dot = "...";
	    }
	    Ns_DStringPrintf(&ds, "%sadp script:\n\"%.*s%s\"",
			     inc, len, adp, dot);
	}
	framePtr = framePtr->prevPtr;
	inc = "\n    included from ";
    }
    if (conn != NULL && (itPtr->adp.flags & ADP_DETAIL)) {
	Ns_DStringPrintf(&ds, "\n    while processing connection #%d:\n%8s%s",
			 Ns_ConnId(conn), "",
			 conn->request->line);
	for (i = 0; i < Ns_SetSize(conn->headers); ++i) {
	    Ns_DStringPrintf(&ds, "\n        %s: %s",
			     Ns_SetKey(conn->headers, i),
			     Ns_SetValue(conn->headers, i));
	}
    }
    Tcl_AddErrorInfo(interp, ds.string);
    err = Ns_TclLogError(interp);
    if (itPtr->adp.flags & ADP_DISPLAY) {
	Ns_DStringTrunc(&ds, 0);
	Ns_DStringAppend(&ds, "<br><pre>\n");
	Ns_QuoteHtml(&ds, err);
	Ns_DStringAppend(&ds, "\n<br></pre>\n");
	NsAdpAppend(itPtr, ds.string, ds.length);
    }
    Ns_DStringFree(&ds);
    adp = itPtr->servPtr->adp.errorpage;
    if (adp != NULL && itPtr->adp.errorLevel == 0) {
	++itPtr->adp.errorLevel;
	objv[0] = Tcl_NewStringObj(adp, -1);
	Tcl_IncrRefCount(objv[0]);
	objv[1] = Tcl_GetVar2Ex(interp, "errorInfo", NULL, TCL_GLOBAL_ONLY);
	if (objv[1] == NULL) {
	    objv[1] = Tcl_GetObjResult(interp);
	}
	(void) NsAdpInclude(itPtr, 2, objv, adp, NULL);
	Tcl_DecrRefCount(objv[0]);
	--itPtr->adp.errorLevel;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * AdpExec --
 *
 *	Execute ADP code.
 *
 * Results:
 *	TCL_OK unless there is an ADP error exception, stack overflow,
 *	or script error when the ADP_STRICT option is set.
 *	
 * Side Effects:
 *	Depends on page.
 *
 *----------------------------------------------------------------------
 */

static int
AdpExec(NsInterp *itPtr, int objc, Tcl_Obj *objv[], char *file,
	AdpCode *codePtr, Objs *objsPtr, Tcl_DString *outputPtr)
{
    Tcl_Interp *interp = itPtr->interp;
    AdpFrame frame;
    Ns_DString cwd;
    Tcl_Obj *objPtr;
    int nscript, nblocks, result, len, i;
    char *ptr, *slash, *savecwd;

    /*
     * Check for stack overflow.
     */

    if (Ns_CheckStack() == NS_BREAK) {
	Tcl_SetResult(interp, "adp stack overflow", TCL_STATIC);
	return TCL_ERROR;
    }

    /*
     * Setup the new call frame.
     */

    Ns_DStringInit(&cwd);
    frame.file = file;
    frame.objc = objc;
    frame.objv = objv;
    frame.outputPtr = outputPtr;
    frame.ident = NULL;
    savecwd = itPtr->adp.cwd;
    if (file != NULL && (slash = strrchr(file, '/')) != NULL) {
    	Ns_DStringNAppend(&cwd, file, slash - file);
	itPtr->adp.cwd = cwd.string;
    }
    frame.prevPtr = itPtr->adp.framePtr;
    itPtr->adp.framePtr = &frame;
    itPtr->adp.depth++;

    /*
     * Execute the ADP by copying text blocks directly to the output
     * stream and evaluating script blocks.
     */

    ptr = AdpCodeText(codePtr);
    nblocks = AdpCodeBlocks(codePtr);
    nscript = 0;
    result = TCL_OK;
    for (i = 0; itPtr->adp.exception == ADP_OK && i < nblocks; ++i) {
	frame.line = AdpCodeLine(codePtr, i);
	len = AdpCodeLen(codePtr, i);
	if (itPtr->adp.flags & ADP_TRACE) {
	    AdpTrace(itPtr, ptr, len);
	}
	if (len > 0) {
	    result = NsAdpAppend(itPtr, ptr, len);
	} else {
	    len = -len;
	    if (itPtr->adp.debugLevel > 0) {
    	        result = AdpDebug(itPtr, ptr, len, nscript);
	    } else if (objsPtr == NULL) {
		result = Tcl_EvalEx(interp, ptr, len, 0);
	    } else {
		objPtr = objsPtr->objs[nscript];
		if (objPtr == NULL) {
		    objPtr = Tcl_NewStringObj(ptr, len);
		    Tcl_IncrRefCount(objPtr);
		    objsPtr->objs[nscript] = objPtr;
		}
    	        result = Tcl_EvalObjEx(interp, objPtr, 0);
	    }
	    ++nscript;
	}

	/*
	 * Log an error message and optionally break from this ADP
	 * call frame unless the error was generated to signal
	 * and ADP exception.
	 */
	
	if (result != TCL_OK && itPtr->adp.exception == ADP_OK) {
	    if (!(itPtr->adp.flags & ADP_ERRLOGGED)) {
	    	NsAdpLogError(itPtr);
	    }
	    if (itPtr->adp.flags & ADP_STRICT) {
    	    	itPtr->adp.flags |= ADP_ERRLOGGED;
		break;
	    }
	}
	ptr += len;
    }

    /*
     * Clear the return exception and reset result.
     */

    switch (itPtr->adp.exception) {
    case ADP_OK:
	break;
    case ADP_RETURN:
	itPtr->adp.exception = ADP_OK;
	/* FALLTHROUGH */
    default: 
	result = TCL_OK;
	break;
    }

    /*
     * Restore the previous call frame.
     */

    itPtr->adp.framePtr = frame.prevPtr;
    itPtr->adp.cwd = savecwd;
    if (frame.ident != NULL) {
	Tcl_DecrRefCount(frame.ident);
    }
    Ns_DStringFree(&cwd);
    return result;
}


/*
 *----------------------------------------------------------------------
 *
 * AdpDebug --
 *
 *	Evaluate an ADP script block with the TclPro debugger.
 *
 * Results:
 *	Depends on script.
 *
 * Side effects:
 *	A unique temp file with header comments and the script is
 *	created and sourced, the effect of which is TclPro will
 *	instrument the code on the fly for single-step debugging.
 *
 *----------------------------------------------------------------------
 */

static int
AdpDebug(NsInterp *itPtr, char *ptr, int len, int nscript)
{
    int code, fd;
    Tcl_Interp *interp = itPtr->interp;
    int level = itPtr->adp.debugLevel;
    char *file = Tcl_GetString(itPtr->adp.framePtr->objv[0]);
    char buf[10], debugfile[255];
    Ns_DString ds;

    code = TCL_ERROR;
    Ns_DStringInit(&ds);
    sprintf(buf, "%d", level);
    Ns_DStringVarAppend(&ds,
	"#\n"
	"# level: ", buf, "\n", NULL);
    sprintf(buf, "%d", nscript);
    Ns_DStringVarAppend(&ds,
	"# chunk: ", buf, "\n"
	"# file:  ", file, "\n"
	"#\n\n", NULL);
    Ns_DStringNAppend(&ds, ptr, len);
    sprintf(debugfile, P_tmpdir "/adp%d.%d.XXXXXX", level, nscript);
    if (mktemp(debugfile) == NULL) {
	Tcl_SetResult(interp, "could not create adp debug file", TCL_STATIC);
    } else {
	fd = open(debugfile, O_WRONLY|O_TRUNC|O_CREAT, 0644);
	if (fd < 0) {
	    Tcl_AppendResult(interp, "could not create adp debug file \"",
		debugfile, "\": ", Tcl_PosixError(interp), NULL);
	} else {
	    if (write(fd, ds.string, (size_t)ds.length) < 0) {
		Tcl_AppendResult(interp, "write to \"", debugfile,
		    "\" failed: ", Tcl_PosixError(interp), NULL);
	    } else {
		Ns_DStringTrunc(&ds, 0);
		Ns_DStringVarAppend(&ds, "source ", debugfile, NULL);
		code = Tcl_EvalEx(interp, ds.string, ds.length, 0);
	    }
	    close(fd);
	    unlink(debugfile);
	}
    }
    Ns_DStringFree(&ds);
    return code;
}


/*
 *----------------------------------------------------------------------
 *
 * FreeInterpPage --
 *
 *  	Free a per-interp page cache entry.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
FreeInterpPage(void *arg)
{
    InterpPage *ipagePtr = arg;
    Page *pagePtr = ipagePtr->pagePtr;
    NsServer *servPtr = pagePtr->servPtr;

    FreeObjs(ipagePtr->objs);
    Ns_MutexLock(&servPtr->adp.pagelock);
    if (--pagePtr->refcnt == 0) {
	if (pagePtr->hPtr != NULL) {
	    Tcl_DeleteHashEntry(pagePtr->hPtr);
	}
	if (pagePtr->cachePtr != NULL) {
    	    FreeObjs(ipagePtr->cacheObjs);
	    DecrCache(pagePtr->cachePtr);
	}
	NsAdpFreeCode(&pagePtr->code);
	ns_free(pagePtr);
    }
    Ns_MutexUnlock(&servPtr->adp.pagelock);
    ns_free(ipagePtr);
}


/*
 *----------------------------------------------------------------------
 *
 * AllocObjs --
 *
 *  	Allocate new page script objects.
 *
 * Results:
 *	Pointer to new objects.
 *
 * Side Effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static Objs *
AllocObjs(int nobjs)
{
    Objs *objsPtr;

    objsPtr = ns_calloc(1, sizeof(Objs) + (nobjs * sizeof(Tcl_Obj *)));
    objsPtr->nobjs = nobjs;
    return objsPtr;
}


/*
 *----------------------------------------------------------------------
 *
 * FreeObjs --
 *
 *  	Free page objects, decrementing ref counts as needed.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
FreeObjs(Objs *objsPtr)
{
    int i;

    for (i = 0; i < objsPtr->nobjs; ++i) {
	if (objsPtr->objs[i] != NULL) {
	    Tcl_DecrRefCount(objsPtr->objs[i]);
	}
    }
    ns_free(objsPtr);
}


/*
 *----------------------------------------------------------------------
 *
 * DecrCache --
 *
 *  	Decrement ref count of a cache entry, potentially freeing
 *	the cache.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Will free cache on last reference count.
 *
 *----------------------------------------------------------------------
 */

static void
DecrCache(AdpCache *cachePtr)
{
    if (--cachePtr->refcnt == 0) {
	NsAdpFreeCode(&cachePtr->code);
	ns_free(cachePtr);
    }
}


/*
 *----------------------------------------------------------------------
 *
 * AdpTrace --
 *
 *	Trace execution of an ADP page.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Dumps tracing info, possibly truncated, via Ns_Log.
 *
 *----------------------------------------------------------------------
 */

static void
AdpTrace(NsInterp *itPtr, char *ptr, int len)
{
    char type;

    if (len >= 0) {
	type = 'T';
    } else {
	type = 'S';
	len = -len;
    }
    if (len > itPtr->servPtr->adp.tracesize) {
	len = itPtr->servPtr->adp.tracesize;
    }
    Ns_Log(Notice, "adp[%d%c]: %.*s", itPtr->adp.depth, type, len, ptr);
}
