/* * 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. */ /* * fastpath.c -- * * Get page possibly from a file cache. */ static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/aolserver/nsd/fastpath.c,v 1.26 2006/04/19 17:48:47 jgdavidson Exp $, compiled: " __DATE__ " " __TIME__; #include "nsd.h" /* * The following constants are defined for this file */ #ifdef MAP_FAILED #undef MAP_FAILED #endif #define MAP_FAILED ((void *) (-1)) /* * The following structure defines the contents of a file * stored in the file cache. */ typedef struct { time_t mtime; int size; int refcnt; char bytes[1]; /* Grown to actual file size. */ } File; /* * Local functions defined in this file */ static Ns_Callback FreeEntry; static void DecrEntry(File *); static int UrlIs(char *server, char *url, int dir); static int FastStat(char *file, struct stat *stPtr); static int FastReturn(NsServer *servPtr, Ns_Conn *conn, int status, char *type, char *file, struct stat *stPtr); /* *---------------------------------------------------------------------- * NsFastpathCache -- * * Initialize the fastpath cache. * * Results: * Pointer to Ns_Cache. * * Side effects: * None. * *---------------------------------------------------------------------- */ Ns_Cache * NsFastpathCache(char *server, int size) { Ns_DString ds; Ns_Cache *fpCache; int keys; #ifdef _WIN32 keys = TCL_STRING_KEYS; #else keys = FILE_KEYS; #endif Ns_DStringInit(&ds); Ns_DStringVarAppend(&ds, "nsfp:", server, NULL); fpCache = Ns_CacheCreateSz(ds.string, keys, (size_t) size, FreeEntry); Ns_DStringFree(&ds); return fpCache; } /* *---------------------------------------------------------------------- * * Ns_ConnReturnFile -- * * Send the contents of a file out the conn. * * Results: * NS_OK/NS_ERROR * * Side effects: * See FastReturn. * *---------------------------------------------------------------------- */ int Ns_ConnReturnFile(Ns_Conn *conn, int status, char *type, char *file) { struct stat st; char *server; NsServer *servPtr; if (!FastStat(file, &st)) { return Ns_ConnReturnNotFound(conn); } server = Ns_ConnServer(conn); servPtr = NsGetServer(server); return FastReturn(servPtr, conn, status, type, file, &st); } /* *---------------------------------------------------------------------- * Ns_PageRoot -- * * Return path name of the server pages directory. * * Results: * Server pageroot or NULL on invalid server. * * Side effects: * None. * *---------------------------------------------------------------------- */ char * Ns_PageRoot(char *server) { NsServer *servPtr = NsGetServer(server); return servPtr->fastpath.pageroot; } /* *---------------------------------------------------------------------- * Ns_SetUrlToFileProc -- * * Set pointer to custom routine that acts like Ns_UrlToFile(); * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void Ns_SetUrlToFileProc(char *server, Ns_UrlToFileProc *procPtr) { NsServer *servPtr = NsGetServer(server); servPtr->fastpath.url2file = procPtr; } /* *---------------------------------------------------------------------- * Ns_UrlToFile -- * * Construct the filename that corresponds to a URL. * * Results: * Return NS_OK on success or NS_ERROR on failure. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Ns_UrlToFile(Ns_DString *dsPtr, char *server, char *url) { NsServer *servPtr = NsGetServer(server); return NsUrlToFile(dsPtr, servPtr, url); } /* *---------------------------------------------------------------------- * Ns_UrlIsFile, Ns_UrlIsDir -- * * Check if a file/directory that corresponds to a URL exists. * * Results: * Return NS_TRUE if the file exists and NS_FALSE otherwise. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Ns_UrlIsFile(char *server, char *url) { return UrlIs(server, url, 0); } int Ns_UrlIsDir(char *server, char *url) { return UrlIs(server, url, 1); } static int UrlIs(char *server, char *url, int dir) { Ns_DString ds; int is = NS_FALSE; struct stat st; Ns_DStringInit(&ds); if (Ns_UrlToFile(&ds, server, url) == NS_OK && (stat(ds.string, &st) == 0) && ((dir && S_ISDIR(st.st_mode)) || (dir == NS_FALSE && S_ISREG(st.st_mode)))) { is = NS_TRUE; } Ns_DStringFree(&ds); return is; } /* *---------------------------------------------------------------------- * FastGetRestart -- * * Construct the full URL and redirect internally. * * Results: * See Ns_ConnRedirect(). * * Side effects: * See Ns_ConnRedirect(). * *---------------------------------------------------------------------- */ static int FastGetRestart(Ns_Conn *conn, char *page) { int status; Ns_DString ds; Ns_DStringInit(&ds); Ns_MakePath(&ds, conn->request->url, page, NULL); status = Ns_ConnRedirect(conn, ds.string); Ns_DStringFree(&ds); return status; } /* *---------------------------------------------------------------------- * Ns_FastPathOp -- * * Return the contents of a URL. * * Results: * Return NS_OK for success or NS_ERROR for failure. * * Side effects: * Contents of file may be cached in file cache. * *---------------------------------------------------------------------- */ int Ns_FastPathOp(void *arg, Ns_Conn *conn) { Conn *connPtr = (Conn *) conn; NsServer *servPtr = connPtr->servPtr; char *url = conn->request->url; Ns_DString ds; struct stat st; int result, i; Ns_DStringInit(&ds); if (NsUrlToFile(&ds, servPtr, url) != NS_OK || !FastStat(ds.string, &st)) { goto notfound; } if (S_ISREG(st.st_mode)) { /* * Return ordinary files as with Ns_ConnReturnFile. */ result = FastReturn(servPtr, conn, 200, NULL, ds.string, &st); } else if (S_ISDIR(st.st_mode)) { /* * For directories, search for a matching directory file and * restart the connection if found. */ for (i = 0; i < servPtr->fastpath.dirc; ++i) { Ns_DStringTrunc(&ds, 0); if (NsUrlToFile(&ds, servPtr, url) != NS_OK) { goto notfound; } Ns_DStringVarAppend(&ds, "/", servPtr->fastpath.dirv[i], NULL); if ((stat(ds.string, &st) == 0) && S_ISREG(st.st_mode)) { if (url[strlen(url) - 1] != '/') { Ns_DStringTrunc(&ds, 0); Ns_DStringVarAppend(&ds, url, "/", NULL); result = Ns_ConnReturnRedirect(conn, ds.string); } else { result = FastGetRestart(conn, servPtr->fastpath.dirv[i]); } goto done; } } /* * If no index file was found, invoke a directory listing * ADP or Tcl proc if configured. */ if (servPtr->fastpath.diradp != NULL) { result = Ns_AdpRequest(conn, servPtr->fastpath.diradp); } else if (servPtr->fastpath.dirproc != NULL) { result = Ns_TclRequest(conn, servPtr->fastpath.dirproc); } else { goto notfound; } } else { notfound: result = Ns_ConnReturnNotFound(conn); } done: Ns_DStringFree(&ds); return result; } /* *---------------------------------------------------------------------- * FreeEntry -- * * Logically remove a cached file from file cache. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void FreeEntry(void *arg) { File *filePtr = (File *) arg; DecrEntry(filePtr); } /* *---------------------------------------------------------------------- * DecrEntry -- * * Decrement reference count of cached file. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void DecrEntry(File *filePtr) { if (--filePtr->refcnt == 0) { ns_free(filePtr); } } /* *---------------------------------------------------------------------- * * FastStat -- * * Stat a file, logging an error on unexpected results. * * Results: * 1 if stat ok, 0 otherwise. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int FastStat(char *file, struct stat *stPtr) { if (stat(file, stPtr) != 0) { if (errno != ENOENT && errno != EACCES) { Ns_Log(Error, "fastpath: stat(%s) failed: %s", file, strerror(errno)); } return 0; } return 1; } /* *---------------------------------------------------------------------- * * FastReturn -- * * Return an open file, possibly from cache. * * Results: * Standard Ns_Request result. * * Side effects: * May map, cache, open, and/or send file out connection. * *---------------------------------------------------------------------- */ static int FastReturn(NsServer *servPtr, Ns_Conn *conn, int status, char *type, char *file, struct stat *stPtr) { int result = NS_ERROR, fd, new, nread; File *filePtr; char *key; Ns_Entry *entPtr; void *map, *arg; #ifndef _WIN32 FileKey ukey; #endif /* * Determine the mime type if not given. */ if (type == NULL) { type = Ns_GetMimeType(file); } /* * Set the last modified header if not set yet and, if not * modified since last request, return now. */ Ns_ConnSetLastModifiedHeader(conn, &stPtr->st_mtime); if (Ns_ConnModifiedSince(conn, stPtr->st_mtime) == NS_FALSE) { return Ns_ConnReturnNotModified(conn); } /* * For no output (i.e., HEAD request), just send required * headers. */ if (conn->flags & NS_CONN_SKIPBODY) { Ns_ConnSetRequiredHeaders(conn, type, (int) stPtr->st_size); return Ns_ConnFlushHeaders(conn, status); } if (servPtr->fastpath.cache == NULL || stPtr->st_size > servPtr->fastpath.cachemaxentry) { /* * Caching is disabled or the entry is too large for the cache * so just open, mmap, and send the content directly. */ fd = open(file, O_RDONLY|O_BINARY); if (fd < 0) { Ns_Log(Warning, "fastpath: open(%s) failed: %s", file, strerror(errno)); goto notfound; } if (servPtr->fastpath.mmap) { map = NsMap(fd, 0, stPtr->st_size, 0, &arg); if (map != NULL) { close(fd); fd = -1; result = Ns_ConnReturnData(conn, status, map, (int) stPtr->st_size, type); NsUnMap(map, arg); } } if (fd != -1) { result = Ns_ConnReturnOpenFd(conn, status, type, fd, (int) stPtr->st_size); close(fd); } } else { /* * Search for an existing cache entry for this file, validating * the contents against the current file mtime and size. */ #ifdef _WIN32 key = file; #else ukey.dev = stPtr->st_dev; ukey.ino = stPtr->st_ino; key = (char *) &ukey; #endif filePtr = NULL; Ns_CacheLock(servPtr->fastpath.cache); entPtr = Ns_CacheCreateEntry(servPtr->fastpath.cache, key, &new); if (!new) { while (entPtr != NULL && (filePtr = Ns_CacheGetValue(entPtr)) == NULL) { Ns_CacheWait(servPtr->fastpath.cache); entPtr = Ns_CacheFindEntry(servPtr->fastpath.cache, key); } if (filePtr != NULL && (filePtr->mtime != stPtr->st_mtime || filePtr->size != stPtr->st_size)) { Ns_CacheUnsetValue(entPtr); new = 1; } } if (new) { /* * Read and cache new or invalidated entries in one big chunk. */ Ns_CacheUnlock(servPtr->fastpath.cache); fd = open(file, O_RDONLY|O_BINARY); if (fd < 0) { filePtr = NULL; Ns_Log(Warning, "fastpath: failed to open '%s': '%s'", file, strerror(errno)); } else { filePtr = ns_malloc(sizeof(File) + (size_t) stPtr->st_size); filePtr->refcnt = 1; filePtr->size = stPtr->st_size; filePtr->mtime = stPtr->st_mtime; nread = read(fd, filePtr->bytes, (size_t)filePtr->size); close(fd); if (nread != filePtr->size) { Ns_Log(Warning, "fastpath: failed to read '%s': '%s'", file, strerror(errno)); ns_free(filePtr); filePtr = NULL; } } Ns_CacheLock(servPtr->fastpath.cache); entPtr = Ns_CacheCreateEntry(servPtr->fastpath.cache, key, &new); if (filePtr != NULL) { Ns_CacheSetValueSz(entPtr, filePtr, (size_t)filePtr->size); } else { Ns_CacheFlushEntry(entPtr); } Ns_CacheBroadcast(servPtr->fastpath.cache); } if (filePtr != NULL) { ++filePtr->refcnt; Ns_CacheUnlock(servPtr->fastpath.cache); result = Ns_ConnReturnData(conn, status, filePtr->bytes, filePtr->size, type); Ns_CacheLock(servPtr->fastpath.cache); DecrEntry(filePtr); } Ns_CacheUnlock(servPtr->fastpath.cache); if (filePtr == NULL) { goto notfound; } } return result; notfound: return Ns_ConnReturnNotFound(conn); } int NsUrlToFile(Ns_DString *dsPtr, NsServer *servPtr, char *url) { int status; if (servPtr->fastpath.url2file != NULL) { status = (*servPtr->fastpath.url2file)(dsPtr, servPtr->server, url); } else { Ns_MakePath(dsPtr, servPtr->fastpath.pageroot, url, NULL); status = NS_OK; } if (status == NS_OK) { while (dsPtr->length > 0 && dsPtr->string[dsPtr->length-1] == '/') { Ns_DStringTrunc(dsPtr, dsPtr->length-1); } } return status; }