]> granicus.if.org Git - apache/blobdiff - modules/cache/mod_file_cache.c
*) continued header revamping
[apache] / modules / cache / mod_file_cache.c
index c99f32f7b30cefcea2caecd35f1b44baa7ff426a..6667f1ce7fb078ccdf97181a7cb077ec9a8fcda2 100644 (file)
     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;
@@ -199,77 +165,134 @@ ap_status_t cleanup_mmap(void *sconfv)
     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;
@@ -277,45 +300,20 @@ static int file_compare(const void *av, const void *bv)
 
     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);
@@ -325,47 +323,6 @@ static void file_cache_post_config(ap_pool_t *p, ap_pool_t *plog,
 /* 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;
@@ -373,33 +330,26 @@ static int file_cache_xlat(request_rec *r)
     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;
@@ -407,43 +357,73 @@ static int file_cache_xlat(request_rec *r)
 }
 
 
-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);
@@ -457,103 +437,53 @@ static int file_cache_handler(request_rec *r)
     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 */
 };