#ifdef HAVE_STRING_H
#include <string.h>
#endif
-/* What are these here for? rbb */
-#include <fcntl.h>
-#include <errno.h>
#define CORE_PRIVATE
#include "http_request.h"
#include "http_core.h"
#include "apr_mmap.h"
+#include "apr_strings.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 {
- ap_file_t *file;
- char *filename;
- ap_finfo_t finfo;
+#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
} 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_make_array(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)
+
+#if APR_HAS_SENDFILE
+static apr_status_t open_file(apr_file_t **file, const char *filename, int flg1, int flg2,
+ apr_pool_t *p)
{
- ap_status_t rv;
+ apr_status_t rv;
#ifdef WIN32
/* The Windows file needs to be opened for overlapped i/o, which APR doesn't
* support.
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);
+ rv = apr_put_os_file(file, &hFile, p);
}
else {
rv = GetLastError();
*file = NULL;
}
#else
- rv = ap_open(file, filename, flg1, flg2, context);
+ rv = apr_open(file, filename, flg1, flg2, p);
#endif
return rv;
}
+#endif /* APR_HAS_SENDFILE */
-ap_status_t cleanup_file_cache(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) {
- ap_close(file->file);
- ++file;
- --n;
+#if APR_HAS_MMAP
+ if (file->is_mmapped) {
+ apr_mmap_delete(file->mm);
+ }
+ else
+#endif
+#if APR_HAS_SENDFILE
+ apr_close(file->file);
+#endif
+ ++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)
+
{
+#if APR_HAS_SENDFILE
a_server_config *sconf;
a_file *new_file;
a_file tmp;
- ap_file_t *fd = NULL;
- ap_status_t rc;
+ apr_file_t *fd = NULL;
+ apr_status_t rc;
- /* canonicalize the file name */
+ /* 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);
+ if ((rc = apr_stat(&tmp.finfo, filename, 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.
+ /* Note: open_file should call apr_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);
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;
}
tmp.file = fd;
- tmp.filename = ap_pstrdup(cmd->pool, filename);
+ tmp.filename = apr_pstrdup(cmd->pool, filename);
sconf = ap_get_module_config(cmd->server->module_config, &file_cache_module);
- new_file = ap_push_array(sconf->files);
+ new_file = apr_push_array(sconf->files);
*new_file = tmp;
if (sconf->files->nelts == 1) {
/* first one, register the cleanup */
- ap_register_cleanup(cmd->pool, sconf, cleanup_file_cache, ap_null_cleanup);
+ apr_register_cleanup(cmd->pool, sconf, cleanup_file_cache, apr_null_cleanup);
}
+
+ new_file->is_mmapped = FALSE;
+
return NULL;
+#else
+ /* 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
}
-#ifdef WIN32
-/* Windows doesn't have inodes. This ifdef should be changed to
- * something like HAVE_INODES
- */
+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, 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|APLOG_NOERRNO, 0, cmd->server,
+ "mod_file_cache: %s isn't a regular file, skipping", filename);
+ return NULL;
+ }
+ if ((rc = apr_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_close(fd);
+ ap_log_error(APLOG_MARK, APLOG_WARNING, rc, cmd->server,
+ "mod_file_cache: unable to mmap %s, skipping", filename);
+ return NULL;
+ }
+ apr_close(fd);
+ tmp.filename = fspec;
+ sconf = ap_get_module_config(cmd->server->module_config, &file_cache_module);
+ new_file = apr_push_array(sconf->files);
+ *new_file = tmp;
+ if (sconf->files->nelts == 1) {
+ /* first one, register the cleanup */
+ apr_register_cleanup(cmd->pool, sconf, cleanup_file_cache, apr_null_cleanup);
+ }
+
+ new_file->is_mmapped = TRUE;
+
+ return NULL;
+#else
+ /* MMAP not supported on this platform*/
+ return NULL;
+#endif
+}
+
+
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;
a_file *elts;
int nelts;
-#ifndef WIN32
- ap_array_header_t *inodes;
- int i;
-#endif
-
- 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)
{
- a_server_config *sconf;
- a_file tmp;
- a_file *ptmp;
- a_file *match;
- int rangestatus, errstatus;
-#ifndef WIN32
- a_file **pmatch;
+#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;
+ apr_off_t offset = 0;
+
+ rv = apr_seek(file->file, APR_SET, &offset);
+ if (rv != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ "seek failed");
+ return HTTP_INTERNAL_SERVER_ERROR;
+ }
+
+ 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_file *match;
+ int errstatus;
+ int rc = OK;
+
+ if (strcmp(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;
- struct iovec iov;
- ap_hdtr_t hdtr;
- ap_hdtr_t *phdtr = &hdtr;
-
- /*
- * We want to send any data held in the client buffer on the
- * call to iol_sendfile. So hijack it then set outcnt to 0
- * to prevent the data from being sent to the client again
- * when the buffer is flushed to the client at the end of the
- * request.
- */
- 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;
- }
- }
+ /* 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 const char* const aszPre[]={"http_core.c",NULL}; */
- /* ap_hook_pre_config(pre_config,NULL,NULL,AP_HOOK_MIDDLE); */
+ ap_hook_handler(file_cache_handler, 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);
/* This trick doesn't work apparently because the translate hooks
ap_hook_translate_name(file_cache_xlat, aszPre, NULL, AP_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 */
};