an extra stat() that's a waste.
*/
-#include <stdio.h>
+#include "apr.h"
+#include "apr_mmap.h"
+#include "apr_strings.h"
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+
+#if APR_HAVE_SYS_TYPES_H
#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <string.h>
+#endif
#define CORE_PRIVATE
#include "http_protocol.h"
#include "http_request.h"
#include "http_core.h"
-#include "apr_mmap.h"
-module MODULE_VAR_EXPORT file_cache_module;
-static ap_pool_t *context;
-static int once_through = 0;
+module AP_MODULE_DECLARE_DATA file_cache_module;
typedef struct {
-#if 1
- ap_file_t *file;
-#else
- ap_mmap_t *mm;
+#if APR_HAS_SENDFILE
+ apr_file_t *file;
+#endif
+ const char *filename;
+ apr_finfo_t finfo;
+ int is_mmapped;
+#if APR_HAS_MMAP
+ apr_mmap_t *mm;
#endif
- char *filename;
- ap_finfo_t finfo;
} a_file;
typedef struct {
- ap_array_header_t *files;
- ap_array_header_t *inode_sorted;
+ apr_array_header_t *files;
} a_server_config;
-static void *create_server_config(ap_pool_t *p, server_rec *s)
+static void *create_server_config(apr_pool_t *p, server_rec *s)
{
- a_server_config *sconf = ap_palloc(p, sizeof(*sconf));
+ a_server_config *sconf = apr_palloc(p, sizeof(*sconf));
- sconf->files = ap_make_array(p, 20, sizeof(a_file));
- sconf->inode_sorted = NULL;
+ sconf->files = apr_array_make(p, 20, sizeof(a_file));
return sconf;
}
-#if 0
-static void pre_config(ap_pool_t *pconf, ap_pool_t *plog, ap_pool_t *ptemp)
-{
- context = pconf;
-}
-#endif
-static ap_status_t open_file(ap_file_t **file, char* filename, int flg1, int flg2,
- ap_pool_t *context)
-{
- ap_status_t rv;
-#ifdef WIN32
- /* The Windows file needs to be opened for overlapped i/o, which APR doesn't
- * support.
- */
- HANDLE hFile;
- hFile = CreateFile(filename, /* pointer to name of the file */
- GENERIC_READ, /* access (read-write) mode */
- FILE_SHARE_READ, /* share mode */
- NULL, /* pointer to security attributes */
- OPEN_EXISTING, /* how to create */
- FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN, /* file attributes */
- NULL); /* handle to file with attributes to copy */
- if (hFile != INVALID_HANDLE_VALUE) {
- rv = ap_put_os_file(file, &hFile, context);
- }
- else {
- rv = GetLastError();
- *file = NULL;
- }
-#else
- rv = ap_open(file, filename, flg1, flg2, context);
-#endif
-
- return rv;
-}
-ap_status_t cleanup_mmap(void *sconfv)
+static apr_status_t cleanup_file_cache(void *sconfv)
{
a_server_config *sconf = sconfv;
size_t n;
n = sconf->files->nelts;
file = (a_file *)sconf->files->elts;
while(n) {
-#if 1
- ap_close(file->file);
-#else
- ap_mmap_delete(file->mm);
+#if APR_HAS_MMAP
+ if (file->is_mmapped) {
+ apr_mmap_delete(file->mm);
+ }
+ else
+#endif
+#if APR_HAS_SENDFILE
+ apr_file_close(file->file);
#endif
- ++file;
- --n;
+ ++file;
+ --n;
}
return APR_SUCCESS;
}
-static const char *cachefile(cmd_parms *cmd, void *dummy, char *filename)
+static const char *cachefile(cmd_parms *cmd, void *dummy, const char *filename)
+
{
+ /* ToDo:
+ * Disable the file cache on a Windows 9X box. APR_HAS_SENDFILE will be
+ * defined in an Apache for Windows build, but apr_sendfile returns
+ * APR_ENOTIMPL on Windows 9X because TransmitFile is not available.
+ */
+
+#if APR_HAS_SENDFILE
a_server_config *sconf;
a_file *new_file;
a_file tmp;
- ap_file_t *fd = NULL;
-#if 0
- caddr_t mm;
-#endif
- ap_status_t rc;
- /* canonicalize the file name */
+ apr_file_t *fd = NULL;
+ apr_status_t rc;
+
+ /* canonicalize the file name? */
/* os_canonical... */
- if (ap_stat(&tmp.finfo, filename, NULL) != APR_SUCCESS) {
- ap_log_error(APLOG_MARK, APLOG_WARNING, errno, cmd->server,
- "file_cache: unable to stat(%s), skipping", filename);
+ /* XXX: uh... yea, or expect them to be -very- accurate typists */
+ if ((rc = apr_stat(&tmp.finfo, filename, APR_FINFO_MIN,
+ cmd->temp_pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
+ "mod_file_cache: unable to stat(%s), skipping", filename);
return NULL;
}
if (tmp.finfo.filetype != APR_REG) {
- ap_log_error(APLOG_MARK, APLOG_WARNING, errno, cmd->server,
- "file_cache: %s isn't a regular file, skipping", filename);
+ ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, cmd->server,
+ "mod_file_cache: %s isn't a regular file, skipping", filename);
return NULL;
}
- /* Note: open_file should call ap_open for Unix and CreateFile for Windows.
- * The Windows file needs to be opened for async I/O to allow multiple threads
- * to serve it up at once.
- */
- rc = open_file(&fd, filename, APR_READ, APR_OS_DEFAULT, cmd->pool); //context);
+
+ rc = apr_file_open(&fd, filename, APR_READ | APR_XTHREAD, APR_OS_DEFAULT, cmd->pool);
if (rc != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
- "file_cache: unable to open(%s, O_RDONLY), skipping", filename);
+ "mod_file_cache: unable to open(%s, O_RDONLY), skipping", filename);
return NULL;
}
-#if 1
tmp.file = fd;
+ tmp.filename = apr_pstrdup(cmd->pool, filename);
+ sconf = ap_get_module_config(cmd->server->module_config, &file_cache_module);
+ new_file = apr_array_push(sconf->files);
+ *new_file = tmp;
+ if (sconf->files->nelts == 1) {
+ /* first one, register the cleanup */
+ apr_pool_cleanup_register(cmd->pool, sconf, cleanup_file_cache, apr_pool_cleanup_null);
+ }
+
+ new_file->is_mmapped = FALSE;
+
+ return NULL;
#else
- if (ap_mmap_create(&tmp.mm, fd, 0, tmp.finfo.st_size, context) != APR_SUCCESS) {
- int save_errno = errno;
- ap_close(fd);
- errno = save_errno;
- ap_log_error(APLOG_MARK, APLOG_WARNING, errno, cmd->server,
- "file_cache: unable to mmap %s, skipping", filename);
+ /* Sendfile not supported on this platform */
+ ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, cmd->server,
+ "mod_file_cache: unable to cache file: %s. Sendfile is not supported on this OS", filename);
+ return NULL;
+#endif
+}
+
+static const char *mmapfile(cmd_parms *cmd, void *dummy, const char *filename)
+{
+#if APR_HAS_MMAP
+ a_server_config *sconf;
+ a_file *new_file;
+ a_file tmp;
+ apr_file_t *fd = NULL;
+ apr_status_t rc;
+ const char *fspec;
+
+ fspec = ap_os_case_canonical_filename(cmd->pool, filename);
+ if ((rc = apr_stat(&tmp.finfo, fspec, APR_FINFO_MIN,
+ cmd->temp_pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
+ "mod_file_cache: unable to stat(%s), skipping", filename);
return NULL;
}
- ap_close(fd);
-#endif
- tmp.filename = ap_pstrdup(cmd->pool, filename);
+ if ((tmp.finfo.filetype) != APR_REG) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, 0, cmd->server,
+ "mod_file_cache: %s isn't a regular file, skipping", filename);
+ return NULL;
+ }
+ if ((rc = apr_file_open(&fd, fspec, APR_READ, APR_OS_DEFAULT,
+ cmd->temp_pool)) != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
+ "mod_file_cache: unable to open %s, skipping",
+ filename);
+ return NULL;
+ }
+ if ((rc = apr_mmap_create(&tmp.mm, fd, 0, tmp.finfo.size, APR_MMAP_READ, cmd->pool)) != APR_SUCCESS) {
+ apr_file_close(fd);
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
+ "mod_file_cache: unable to mmap %s, skipping", filename);
+ return NULL;
+ }
+ apr_file_close(fd);
+ tmp.filename = fspec;
sconf = ap_get_module_config(cmd->server->module_config, &file_cache_module);
- new_file = ap_push_array(sconf->files);
+ new_file = apr_array_push(sconf->files);
*new_file = tmp;
if (sconf->files->nelts == 1) {
/* first one, register the cleanup */
- ap_register_cleanup(cmd->pool, sconf, cleanup_mmap, ap_null_cleanup);
+ apr_pool_cleanup_register(cmd->pool, sconf, cleanup_file_cache, apr_pool_cleanup_null);
}
+
+ new_file->is_mmapped = TRUE;
+
return NULL;
+#else
+ /* MMAP not supported on this platform*/
+ return NULL;
+#endif
}
-#ifdef WIN32
-/* Windows doesn't have inodes. This ifdef should be changed to
- * something like HAVE_INODES
- */
+
static int file_compare(const void *av, const void *bv)
{
const a_file *a = av;
return strcmp(a->filename, b->filename);
}
-#else
-static int inode_compare(const void *av, const void *bv)
-{
- const a_file *a = *(a_file **)av;
- const a_file *b = *(a_file **)bv;
- long c;
- c = a->finfo.st_ino - b->finfo.st_ino;
- if (c == 0) {
- return a->finfo.st_dev - b->finfo.st_dev;
- }
- return c;
-}
-#endif
-static void file_cache_post_config(ap_pool_t *p, ap_pool_t *plog,
- ap_pool_t *ptemp, server_rec *s)
+static void file_cache_post_config(apr_pool_t *p, apr_pool_t *plog,
+ apr_pool_t *ptemp, server_rec *s)
{
a_server_config *sconf;
- ap_array_header_t *inodes;
a_file *elts;
int nelts;
- int i;
-
- context = p;
+
/* sort the elements of the main_server, by filename */
sconf = ap_get_module_config(s->module_config, &file_cache_module);
elts = (a_file *)sconf->files->elts;
nelts = sconf->files->nelts;
qsort(elts, nelts, sizeof(a_file), file_compare);
- /* build an index by inode as well, speeds up the search in the handler */
-#ifndef WIN32
- inodes = ap_make_array(p, nelts, sizeof(a_file *));
- sconf->inode_sorted = inodes;
- for (i = 0; i < nelts; ++i) {
- *(a_file **)ap_push_array(inodes) = &elts[i];
- }
- qsort(inodes->elts, nelts, sizeof(a_file *), inode_compare);
-#endif
/* and make the virtualhosts share the same thing */
for (s = s->next; s; s = s->next) {
ap_set_module_config(s->module_config, &file_cache_module, sconf);
/* If it's one of ours, fill in r->finfo now to avoid extra stat()... this is a
* bit of a kludge, because we really want to run after core_translate runs.
*/
-int core_translate_copy(request_rec *r)
-{
- void *sconf = r->server->module_config;
- core_server_config *conf = ap_get_module_config(sconf, &core_module);
-
- if (r->proxyreq) {
- return HTTP_FORBIDDEN;
- }
- if ((r->uri[0] != '/') && strcmp(r->uri, "*")) {
- ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
- "Invalid URI in request %s", r->the_request);
- return BAD_REQUEST;
- }
-
- if (r->server->path
- && !strncmp(r->uri, r->server->path, r->server->pathlen)
- && (r->server->path[r->server->pathlen - 1] == '/'
- || r->uri[r->server->pathlen] == '/'
- || r->uri[r->server->pathlen] == '\0')) {
- r->filename = ap_pstrcat(r->pool, conf->ap_document_root,
- (r->uri + r->server->pathlen), NULL);
- }
- else {
- /*
- * Make sure that we do not mess up the translation by adding two
- * /'s in a row. This happens under windows when the document
- * root ends with a /
- */
- if ((conf->ap_document_root[strlen(conf->ap_document_root)-1] == '/')
- && (*(r->uri) == '/')) {
- r->filename = ap_pstrcat(r->pool, conf->ap_document_root, r->uri+1,
- NULL);
- }
- else {
- r->filename = ap_pstrcat(r->pool, conf->ap_document_root, r->uri,
- NULL);
- }
-
- return OK;
- }
-}
static int file_cache_xlat(request_rec *r)
{
a_server_config *sconf;
a_file *match;
int res;
-#ifdef WIN32
-/*
- * This is really broken on Windows. The call to get the core_module config
- * in core_translate_copy seg faults because 'core_module' is not exported
- * properly and needs a thunk.
- * Will be fixed when we get API_VAR_EXPORTS working correctly again
- */
- return DECLINED;
-#endif
-
sconf = ap_get_module_config(r->server->module_config, &file_cache_module);
/* we only operate when at least one cachefile directive was used */
- if (ap_is_empty_table(sconf->files))
+ if (apr_is_empty_table(sconf->files))
return DECLINED;
- res = core_translate_copy(r);
- if (res == DECLINED || !r->filename) {
+ res = ap_core_translate(r);
+ if (res != OK || !r->filename) {
return res;
}
- if (!r->filename)
- return DECLINED;
+
tmp.filename = r->filename;
match = (a_file *)bsearch(&tmp, sconf->files->elts, sconf->files->nelts,
sizeof(a_file), file_compare);
+
if (match == NULL)
- return DECLINED;
+ return DECLINED;
+
+ /* pass bsearch results to handler */
+ ap_set_module_config(r->request_config, &file_cache_module, match);
/* shortcircuit the get_path_info() stat() calls and stuff */
r->finfo = match->finfo;
}
-static int file_cache_handler(request_rec *r)
+static int mmap_handler(request_rec *r, a_file *file)
+{
+#if APR_HAS_MMAP
+ ap_send_mmap (file->mm, r, 0, file->finfo.size);
+#endif
+ return OK;
+}
+
+static int sendfile_handler(request_rec *r, a_file *file)
+{
+#if APR_HAS_SENDFILE
+ apr_size_t nbytes;
+ apr_status_t rv = APR_EINIT;
+
+ /* A cached file handle (more importantly, its file pointer) is
+ * shared by all threads in the process. The file pointer will
+ * be corrupted if multiple threads attempt to read from the
+ * cached file handle. The sendfile API does not rely on the position
+ * of the file pointer instead taking explicit file offset and
+ * length arguments.
+ *
+ * We should call ap_send_fd with a cached file handle IFF
+ * we are CERTAIN the file will be served with apr_sendfile().
+ * The presense of an AP_FTYPE_FILTER in the filter chain nearly
+ * guarantees that apr_sendfile will NOT be used to send the file.
+ * Furthermore, AP_FTYPE_CONTENT filters will be at the beginning
+ * of the chain, so it should suffice to just check the first
+ * filter in the chain. If the first filter is not a content filter,
+ * assume apr_sendfile() will be used to send the content.
+ */
+ if (r->output_filters && r->output_filters->frec) {
+ if (r->output_filters->frec->ftype == AP_FTYPE_CONTENT)
+ return DECLINED;
+ }
+
+
+ rv = ap_send_fd(file->file, r, 0, file->finfo.size, &nbytes);
+ if (rv != APR_SUCCESS) {
+ /* ap_send_fd will log the error */
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+#endif
+ return OK;
+}
+
+static int file_cache_handler(request_rec *r)
{
- a_server_config *sconf;
- a_file tmp;
- a_file *ptmp;
- a_file **pmatch;
a_file *match;
- int rangestatus, errstatus;
+ int errstatus;
+ int rc = OK;
+
+ /* XXX: not sure if this is right yet
+ * see comment in http_core.c:default_handler
+ */
+ if (ap_strcmp_match(r->handler, "*/*")) {
+ return DECLINED;
+ }
/* we don't handle anything but GET */
if (r->method_number != M_GET) return DECLINED;
- /* file doesn't exist, we won't be dealing with it */
- if (r->finfo.protection == 0) return DECLINED;
+ /* did xlat phase find the file? */
+ match = ap_get_module_config(r->request_config, &file_cache_module);
- sconf = ap_get_module_config(r->server->module_config, &file_cache_module);
-#ifdef WIN32
- tmp.filename = r->filename;
-#else
- tmp.finfo.st_dev = r->finfo.st_dev;
- tmp.finfo.st_ino = r->finfo.st_ino;
-#endif
- ptmp = &tmp;
-#ifdef WIN32
- match = (a_file *)bsearch(ptmp, sconf->files->elts,
- sconf->files->nelts, sizeof(a_file), file_compare);
if (match == NULL) {
return DECLINED;
}
-#else
- pmatch = (a_file **)bsearch(&ptmp, sconf->inode_sorted->elts,
- sconf->inode_sorted->nelts, sizeof(a_file *), inode_compare);
- if (pmatch == NULL) {
- return DECLINED;
- }
- match = *pmatch;
-#endif
/* note that we would handle GET on this resource */
r->allowed |= (1 << M_GET);
ap_update_mtime(r, match->finfo.mtime);
ap_set_last_modified(r);
ap_set_etag(r);
- if (((errstatus = ap_meets_conditions(r)) != OK)
- || (errstatus = ap_set_content_length (r, match->finfo.size))) {
- return errstatus;
+ if ((errstatus = ap_meets_conditions(r)) != OK) {
+ return errstatus;
}
+ ap_set_content_length(r, match->finfo.size);
- rangestatus = ap_set_byterange(r);
ap_send_http_header(r);
- if (!r->header_only) {
- long length = match->finfo.size;
- ap_off_t offset = 0;
-#if 1
- /* ap_bflush(r->connection->client->); */
- struct iovec iov;
- ap_hdtr_t hdtr;
- ap_hdtr_t *phdtr = &hdtr;
-
- /* frob the client buffer */
- iov.iov_base = r->connection->client->outbase;
- iov.iov_len = r->connection->client->outcnt;
- r->connection->client->outcnt = 0;
-
- /* initialize the ap_hdtr_t struct */
- phdtr->headers = &iov;
- phdtr->numheaders = 1;
- phdtr->trailers = NULL;
- phdtr->numtrailers = 0;
-
- if (!rangestatus) {
- iol_sendfile(r->connection->client->iol,
- match->file,
- phdtr,
- &offset,
- &length,
- 0);
- }
- else {
- while (ap_each_byterange(r, &offset, &length)) {
- iol_sendfile(r->connection->client->iol,
- match->file,
- phdtr,
- &offset,
- &length,
- 0);
- phdtr = NULL;
- }
- }
-#else
- if (!rangestatus) {
- ap_send_mmap (match->mm, r, 0, match->finfo.st_size);
- }
- else {
- while (ap_each_byterange(r, &offset, &length)) {
- ap_send_mmap(match->mm, r, offset, length);
- }
- }
-#endif
+ /* Call appropriate handler */
+ if (!r->header_only) {
+ if (match->is_mmapped == TRUE)
+ rc = mmap_handler(r, match);
+ else
+ rc = sendfile_handler(r, match);
}
- return OK;
+ return rc;
}
-static command_rec mmap_cmds[] =
+static command_rec file_cache_cmds[] =
{
- {"cachefile", cachefile, NULL, RSRC_CONF, ITERATE,
- "A space seperated list of files to mmap at config time"},
+AP_INIT_ITERATE("cachefile", cachefile, NULL, RSRC_CONF,
+ "A space separated list of files to add to the file handle cache at config time"),
+AP_INIT_ITERATE("mmapfile", mmapfile, NULL, RSRC_CONF,
+ "A space separated list of files to mmap at config time"),
{NULL}
};
-static void register_hooks(void)
+static void register_hooks(apr_pool_t *p)
{
- /* static const char* const aszPre[]={"http_core.c",NULL}; */
- /* ap_hook_pre_config(pre_config,NULL,NULL,AP_HOOK_MIDDLE); */
- ap_hook_post_config(file_cache_post_config, NULL, NULL, AP_HOOK_MIDDLE);
- ap_hook_translate_name(file_cache_xlat, NULL, NULL, AP_HOOK_MIDDLE);
+ ap_hook_handler(file_cache_handler, NULL, NULL, APR_HOOK_LAST);
+ ap_hook_post_config(file_cache_post_config, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_translate_name(file_cache_xlat, NULL, NULL, APR_HOOK_MIDDLE);
/* This trick doesn't work apparently because the translate hooks
are single shot. If the core_hook returns OK, then our hook is
not called.
- ap_hook_translate_name(file_cache_xlat, aszPre, NULL, AP_HOOK_MIDDLE);
+ ap_hook_translate_name(file_cache_xlat, aszPre, NULL, APR_HOOK_MIDDLE);
*/
-};
-
-static const handler_rec file_cache_handlers[] =
-{
- { "*/*", file_cache_handler },
- { NULL }
-};
+}
-module MODULE_VAR_EXPORT file_cache_module =
+module AP_MODULE_DECLARE_DATA file_cache_module =
{
STANDARD20_MODULE_STUFF,
NULL, /* create per-directory config structure */
NULL, /* merge per-directory config structures */
create_server_config, /* create per-server config structure */
NULL, /* merge per-server config structures */
- mmap_cmds, /* command handlers */
- file_cache_handlers, /* handlers */
+ file_cache_cmds, /* command handlers */
register_hooks /* register hooks */
};